5

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>

そして、私はこの種の出力が欲しかった

 <?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>

これを行うために、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レコードを処理しようとしています。つまり、sObject 要素の 10000 インスタンスには、各 sObject の下に約 15 のフィールドが含まれます。

これらの多くのレコードを処理するために XSLT の上でこれを実行すると、トスになります。XSLT の処理と csv データの提供には約 20 分かかります。これを数秒で終わらせたかったのです。つまり、XSLT は 10,000 レコード (sObject エントリ) を処理して上記のように有効な CSV データを取得するのに 3 ~ 4 秒かかります。

これは、私が XSLT を拡張するのに行き詰まっており、この XSLT をより高速に動作させるために変更するのに助けが必要な場所です。

4

2 に答える 2

7

これは難しい問題だと思います。明らかなものは何も見えませんでした。マルチステップ ビルドを使用するのがコツです。出力ツリーをはるかに高速に作成する pass1.xsl と pass2.xsl を作成しました。

ツリー サイズが 252097 ノード (697768 文字) のテスト ファイルを作成しました。XSL には 21 秒かかり、以下の 2 つの xsl には数秒かかりました。

1 つの XSL を渡します

<xsl:stylesheet version="2.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" indent="no"/>
    <xsl:strip-space elements="*" />
    <xsl:variable name="delimiter" select="','"/>

    <xsl:variable name="allFields">
        <xsl:for-each select="distinct-values( /*/*/*/name() )">
            <xsl:element name="{.}" />
        </xsl:for-each>    
    </xsl:variable>

    <xsl:template match="p0:sObjects">
        <xsl:element name="{local-name(.)}" namespace="urn:sobject.partner.soap.sforce.com">
            <xsl:element name="order" namespace="urn:sobject.partner.soap.sforce.com">
                <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:element>

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

    <xsl:template match="p0:sObject">
        <xsl:variable name="this" select="." />
        <xsl:element name="{local-name(.)}" namespace="urn:sobject.partner.soap.sforce.com">
            <xsl:for-each select="$allFields/*">
                <xsl:element name="{local-name(.)}" namespace="urn:sobject.partner.soap.sforce.com">
                    <xsl:value-of select="$this/*[name() = name(current())]" />
                </xsl:element>
            </xsl:for-each>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

パス 2 XSL

<xsl:stylesheet version="2.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" indent="no"/>
    <xsl:strip-space elements="*" />
    <xsl:variable name="delimiter" select="','"/>

    <xsl:template match="/">
        <xsl:element name="csv" namespace="http://www.approuter.com/schemas/RootNode">
            <xsl:element name="data" namespace="http://www.approuter.com/schemas/RootNode">
                <xsl:apply-templates select="/p0:sObjects/*" />
            </xsl:element>
        </xsl:element>
    </xsl:template>

    <xsl:template match="p0:order">
        <xsl:value-of select="." /> 
    </xsl:template>

    <!-- use Michael's suggestion about using first instead of last() -->
    <xsl:template match="p0:sObject/*[ position() = 1 ]">
        <xsl:text>&#xa;    </xsl:text>
        <xsl:value-of select="." />
    </xsl:template>

    <xsl:template match="p0:sObject/*[ position() > 1 ]">
        <xsl:value-of select="$delimiter" />
        <xsl:value-of select="." />
    </xsl:template>
</xsl:stylesheet>

1出力を渡す

これは、パス 1 からの出力 xml です。これにより、次のような xml が作成されます (次のフェーズで解析するのが簡単です。sObject の下の新しい空の要素を参照してください)。

<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
于 2013-03-05T21:17:46.107 に答える
6

いくつかの提案:

  1. XSLT 2.0 プロセッサを使用している場合は、version="1.0" を設定しないでください。これにより、下位互換モードで実行され、より多くの実行時チェックが行われます。

  2. スタイルシートで 3 回出現するこの種のコード

    <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>
    

    position()!=last() の場合はアイテムの後にではなく、position()!=1 の場合はアイテムの前に区切り文字を挿入することで、常により適切に実行できます。これは、last() が先読みを伴う高価な操作であるためです。ただし、このような単純なケースでは、すべてを次のように置き換えることができます

    <xsl:value-of select="$allFields/name()" separator="{$delimiter}"/>
    
  3. キーを一度しか使用しない場合は、キーを定義しても意味がありません。一意のフィールド名を見つけるには、 を使用するのが最適distinct-values(/*/*/*/name())です。

ただし、これに 20 分かかる理由がまったくわかりません。また、本当の問題が何であるかを特定できていないと思います。

于 2013-03-05T21:38:37.713 に答える