将XML转换为CSV的高速XSLT

将XML转换为CSV的高速XSLT,xml,xslt,csv,transform,xslt-1.0,Xml,Xslt,Csv,Transform,Xslt 1.0,我一直在尝试使用XSLT以最高效的方式从XML文档中获取CSV数据。 下面是我的示例XML <?xml version="1.0" encoding="ISO-8859-1"?> <sObjects xmlns="urn:sobject.partner.soap.sforce.com"> <sObject> <Name>Raagu</Name> <BillingStreet>Hos

我一直在尝试使用XSLT以最高效的方式从XML文档中获取CSV数据。 下面是我的示例XML

<?xml version="1.0" encoding="ISO-8859-1"?>
 <sObjects xmlns="urn:sobject.partner.soap.sforce.com">
     <sObject>
        <Name>Raagu</Name>
        <BillingStreet>Hoskote</BillingStreet>
</sObject>
     <sObject>
        <Name>Rajath</Name>
         <BillingStreet>BTM</BillingStreet>
         <age>25</age>
</sObject>
     <sObject>
        <Name>Sarath</Name>
         <BillingStreet>Murgesh</BillingStreet>
         <location>Bangalore</location>
         <age>#N/A</age>
</sObject>
     <sObject>
         <Name>Bharath</Name>
         <BillingStreet>EGL</BillingStreet>
         <location>Bangalore</location>
             <shipping>Hoskote</Shipping>
</sObject>
     <sObject>
         <Id>12312321321</Id>
         <Name>Guru</Name>
         <location>Sirsi</location>
         <date>12-12-12</date>
</sObject>
     <sObject>
         <Name>Appa</Name>
         <BillingStreet>someStrrt</BillingStreet>
         <accountNo>213213</accountNo>
</sObject>
           <sObject>
          <Name>Sarath</Name>
          <BillingStreet>Murgesh</BillingStreet>
         <location>Bangalore</location>
</sObject>
     <sObject>
          <Name>Sarath</Name>
         <BillingStreet>Murgesh</BillingStreet>
          <location>Bangalore</location>
</sObject>
     <sObject>
          <Name>Sarath</Name>
          <BillingStreet>Murgesh</BillingStreet>
           <location>Bangalore</location>
</sObject>

拉古
霍斯科特
拉贾斯
BTM
25
萨拉斯
默格什
班加罗尔
#不适用
巴拉斯
EGL
班加罗尔
霍斯科特
12312321321
大师
西尔西
12-12-12
阿帕
某物
213213
萨拉斯
默格什
班加罗尔
萨拉斯
默格什
班加罗尔
萨拉斯
默格什
班加罗尔

我想要这种产品

 <?xml version="1.0" encoding="utf-8"?><csv xmlns="http://www.approuter.com/schemas/RootNode"><data>Name,BillingStreet,age,location,Shipping,Id,date,accountNo
Raagu,Hoskote,,,,,,
Rajath,BTM,25,,,,,
Sarath,Murgesh,#N/A,Bangalore,,,,
Bharath,EGL,,Bangalore,Hoskote,,,
Guru,,,Sirsi,,12312321321,12-12-12,
Appa,someStrrt,,,,,,213213
Sarath,Murgesh,,Bangalore,,,,
Sarath,Murgesh,,Bangalore,,,,
Sarath,Murgesh,,Bangalore,,,,</data></csv>
名称、账单列表、年龄、位置、装运、Id、日期、账号
拉古,霍斯科特,,,,,,
拉贾特,BTM,25,,,,,
Sarath,Murgesh,不适用,班加罗尔,,,,
巴拉斯、埃格尔、班加罗尔、霍斯科特、,,,
古鲁,Sirsi,12321321,12-12-12,
阿帕,一些,一些
Sarath,Murgesh,班加罗尔,,,,
Sarath,Murgesh,班加罗尔,,,,
Sarath,Murgesh,班加罗尔,,,,
为了做到这一点,我尝试了遵循XSLT

<xsl:stylesheet version="1.0" xmlns:p0="urn:sobject.partner.soap.sforce.com" xmlns:csv="csv:csv" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output encoding="utf-8"  method="xml"/>
    <xsl:strip-space elements="*" />
    <xsl:variable name="delimiter" select="','"/>

    <xsl:key name="field" match="p0:sObject/*" use="name()"/>

<!-- variable containing the first occurrence of each field -->
    <xsl:variable name="allFields"
    select="/*/*/*[generate-id()=generate-id(key('field', name())[1])]"/>

    <xsl:template match="/">
    <!-- Output the CSV header -->
        <xsl:element name="csv" namespace="http://www.approuter.com/schemas/RootNode">
            <xsl:element name="data" namespace="http://www.approuter.com/schemas/RootNode">

                <xsl:for-each select="$allFields">
                    <xsl:value-of select="name()" />
                    <xsl:if test="position() &lt; last()">
                        <xsl:value-of select="$delimiter" />
                    </xsl:if>
                </xsl:for-each>

                <xsl:text>&#xa;    </xsl:text>

                <xsl:apply-templates select="/*/p0:sObject" />

            </xsl:element>
        </xsl:element>

    </xsl:template>

    <xsl:template match="p0:sObject">
        <xsl:variable name="this" select="." />
        <xsl:for-each select="$allFields">
            <xsl:value-of select="$this/*[name() = name(current())]" />
            <xsl:if test="position() &lt; last()">
                <xsl:value-of select="$delimiter" />
            </xsl:if>
        </xsl:for-each>
        <xsl:if test="position() &lt; last()">
            <xsl:text>&#xa;    </xsl:text>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>


;

;
从功能性的角度来看,上面的XSLT工作得非常好。 但我正在尝试处理大约10000条记录。i、 e sObject元素上的10000个实例每个sObject下将包含大约15个字段

如果我在XSLT上面运行这个程序来处理这么多的记录,那就太麻烦了。XSLT处理和提供csv数据大约需要20分钟。我想在几秒钟内完成这件事。也就是说,XSLT处理10k条记录(sObject条目)需要3-4秒的时间,以提供如上所示的有效CSV数据


这就是我一直致力于增强XSLT的地方,我需要帮助修改XSLT以更快地工作

我认为这是一个难题。我没有看到任何明显的东西。使用多步骤构建是一个诀窍:我创建了pass1.xsl和pass2.xsl,这将更快地创建输出树

我制作了一个树大小为252097个节点(697768个字符)的测试文件。XSL耗时21秒,下面两个XSL耗时几秒钟

通过1 XSL 一些建议:

  • 如果您使用的是XSLT2.0处理器,请不要设置version=“1.0”。这使得它在向后兼容模式下运行,这涉及到更多的运行时检查

  • 这种代码在样式表中出现三次

    <xsl:for-each select="$allFields">
        <xsl:value-of select="name()" />
        <xsl:if test="position() &lt; last()">
            <xsl:value-of select="$delimiter" />
        </xsl:if>
    </xsl:for-each>
    
    
    
    如果位置()为,则最好在项之前插入分隔符=1,而不是在项目后面,如果位置()=最后()。这是因为last()是一个涉及前瞻的昂贵操作。但在这种简单的情况下,整个事情可以用

    <xsl:value-of select="$allFields/name()" separator="{$delimiter}"/>
    
    
    
  • 如果只使用一次键,那么定义它就没有意义了。查找唯一字段名最好使用
    不同的值(/*/*/*/name())


  • 然而,我真的看不出这需要20分钟的原因,我也不认为我已经确定了真正的问题是什么。

    您使用的是哪个xslt处理器?使用xslt 2.0和saxon处理器还是不确定是否已经找到了Thansk@rene作为链接。你能帮我修改这个XSLT以使用2.0功能,并使用最佳编码来实现同样的功能吗?我做了一个150k行输入的示例,只花了20多秒,与20分钟相差甚远。我也是。@Michael Kay谢谢你帮助我理解其中的一些要点。我尝试了你的建议和一些修改。它起作用了。你能帮我了解更多关于distinct-values()函数在时间复杂度方面是如何工作的吗?我在w3schools上读到了关于其功能的文章。但我想从性能的角度进一步了解它在内部是如何工作的。例如,如果我有10k个sObject元素实例,每个实例下都有100个字段,那么让我们假设所有sObject元素都有相同的字段名。在这种情况下,distinct-values()函数是如何工作的?它是智能地查找唯一的字段名还是解析每个元素?显然,distinct-values的工作方式取决于实现。但是任何合理的实现都可能具有本质上是线性的性能(或者最坏情况下是O(n logn))以及评估其参数表达式的成本,这意味着在您的情况下,它与文档的大小是线性的。这不可能解释20分钟的问题,至少撒克逊人是这样。谢谢大家。解决方案非常好。
    <sObjects xmlns="urn:sobject.partner.soap.sforce.com">
       <order>Name,BillingStreet,age,location,shipping,Id,date,accountNo</order>
       <sObject>
          <Name>Raagu</Name>
          <BillingStreet>Hoskote</BillingStreet>
          <age/>
          <location/>
          <shipping/>
          <Id/>
          <date/>
          <accountNo/>
       </sObject>
    </xObjects>
    
    <csv xmlns="http://www.approuter.com/schemas/RootNode"><data>Name,BillingStreet,age,location,shipping,Id,date,accountNo
        Raagu,Hoskote,,,,,,
        Rajath,BTM,25,,,,,
    ...
    
    saxonb-xslt -t bigxml.xml pass1.xsl > intermediate.xml
    saxonb-xslt -t intermediate.xml pass2.xsl > res.xml
    
    <xsl:for-each select="$allFields">
        <xsl:value-of select="name()" />
        <xsl:if test="position() &lt; last()">
            <xsl:value-of select="$delimiter" />
        </xsl:if>
    </xsl:for-each>
    
    <xsl:value-of select="$allFields/name()" separator="{$delimiter}"/>