Преобразование большого файла XML (через 3G) в файл с разделителями-запятыми - PullRequest
0 голосов
/ 04 января 2019

Мне нужно преобразовать большой файл XML (через 3G) в файл с разделителями-запятыми.Я создал XSL-файл для его преобразования.К сожалению, файл слишком велик для обработки с использованием XSLT 1.0.Я пытался использовать XSLT 3.0 (Saxon), но я получаю сообщение об ошибке «XTSE3430: шаблонное правило не может обрабатываться».

Сценарий:

java -cp saxon9ee.jar net.sf.saxon.Transform -t -s:costing.xml -xsl:costing.xsl -o:costing.csv

Сообщение об ошибке:

Java version 1.8.0_191        
Using license serial number         
Stylesheet compilation time: 345.113654ms        
Processing file:costing.xml        
Streaming file:costing.xml        
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser        
URIResolver.resolve href="" base="file:costing.xsl"        
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser        
Building tree for file:costing.xsl using class net.sf.saxon.tree.tiny.TinyBuilder        
Tree built in 5.206935ms        
Tree size: 237 nodes, 104 characters, 25 attributes        
Error on line 71 of costing.xsl:        
  XTSE3430: Template rule is not streamable        
  * Operand {($currNode/element())/element()} of {let $vv:v0 := ...} selects streamed        
  nodes in a context that allows arbitrary navigation (line 86)        
Template rule is not streamable        
  * Operand {($currNode/element())/element()} of {let $vv:v0 := ...} selects streamed nodes in a context that allows arbitrary navigation (line 86)    

Структура XML:

<?xml version="1.0" encoding="UTF-8"?>
<DATA_DS>
   <COSTREPORT>
   <DR>
      <PSU>ABC</PSU>
      <TRU>ABC</TRU>
      <CA>0</CA>
      <DA>0.00</DA>
      <UOM>ABC</UOM>
      <FN>0</FN>
      <RID>0</RID>
      <SD>2018-10-25</SD>
      <DN>ABC</DN>
      <ETD>2018-10-31</ETD>
      <DID>0</DID>
      <LN>ABC</LN>
      <LID>0</LID>
      <PN>ABC</PN>
      <EN>Jane Doe</EN>
      <EID>0</EID>
      <ELN>ABC</ELN>
      <ELV>ABC</ELV>
      <RELA>1234</RELA>
      <ETM>A0</ETM>
      <ASG>A0</ASG>
      <MN>ABC</MN>
      <CRY>ABC</CRY
      ><IVN>ABC</IVN>
      <AD>2018-10-31</AD>
      <CID>0</CID>
      <CCN>ABC</CCN
      ><BOC>ABC</BOC>
      <SG1>0</SG1>
      <SG2>0</SG2>
      <SG3>0</SG3>
      <SG4>0</SG4>
      <SG5>0</SG5>
      <SG9>0</SG9>
      <SG10>0</SG10>
      <TRUID>0</TRUID>
   </DR>
   <DR>
      [...]   
   </DR>
   [...]
   </COSTREPORT>
</DATA_DS>

XSL-файл:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:mode streamable="yes" />
   <xsl:output method="text" />

   <xsl:variable name="delimiter" select="','" />

   <!-- define an array containing the fields we are interested in -->
   <xsl:variable name="fieldArray">
      <field>PSU</field>        <!-- string -->
      <field>TRU</field>        <!-- string -->
      <field>CA</field>         <!-- number -->
      <field>DA</field>         <!-- number -->
      <field>UOM</field>        <!-- string -->
      <field>FN</field>         <!-- number -->
      <field>RID</field>        <!-- number -->
      <field>SD</field>         <!-- date -->
      <field>DN</field>         <!-- string -->
      <field>ETD</field>        <!-- date -->
      <field>DID</field>        <!-- number -->
      <field>LN</field>         <!-- string -->
      <field>LID</field>        <!-- number -->
      <field>PN</field>         <!-- string -->
      <field>EN</field>         <!-- string -->
      <field>EID</field>        <!-- number -->
      <field>ELN</field>        <!-- string -->
      <field>ELV</field>        <!-- string -->
      <field>RELA</field>       <!-- number -->
      <field>ETM</field>        <!-- string -->
      <field>ASG</field>        <!-- string -->
      <field>MN</field>         <!-- string -->
      <field>CRY</field>        <!-- string -->
      <field>IVN</field>        <!-- string -->
      <field>AD</field>         <!-- date -->
      <field>CID</field>        <!-- number -->
      <field>CCN</field>        <!-- string -->
      <field>BOC</field>        <!-- string -->
      <field>SG1</field>        <!-- number -->
      <field>SG2</field>        <!-- number -->
      <field>SG3</field>        <!-- number -->
      <field>SG4</field>        <!-- number -->
      <field>SG5</field>        <!-- number -->
      <field>SG9</field>        <!-- number -->
      <field>SG10</field>       <!-- number -->
      <field>TRUID</field>      <!-- number -->
   </xsl:variable>

   <xsl:param name="fields" select="document('')/*/xsl:variable[@name='fieldArray']/*" />

   <!-- HEADER -->

   <xsl:template match="/">

      <!-- output the header row -->
      <xsl:for-each select="$fields">
         <xsl:if test="position() != 1">
            <xsl:value-of select="$delimiter"/>
         </xsl:if>
         <xsl:value-of select="." />
      </xsl:for-each>

      <!-- output newline -->
      <xsl:text>
</xsl:text>

      <xsl:apply-templates select="DATA_DS/COSTREPORT/DR"/>
   </xsl:template>

   <!-- BODY -->

   <xsl:template match="DR">
    <xsl:variable name="currNode" select="." />

    <!-- output the data row -->
    <!-- loop over the field names and find the value of each one in the xml -->
    <xsl:for-each select="$fields">
      <xsl:if test="position() != 1">
        <xsl:value-of select="$delimiter"/>
      </xsl:if>

      <xsl:value-of select="$currNode/*/*[name() = current()]" />

    </xsl:for-each>

    <!-- output newline -->
    <xsl:text>
</xsl:text>
  </xsl:template>
</xsl:stylesheet>

1 Ответ

0 голосов
/ 04 января 2019

Проблема в переменной:

<xsl:variable name="currNode" select="." />

Это связывает переменную с потоковым входным узлом, что недопустимо, потому что Саксон не может гарантировать, что ваш выбор из этого входного узла сделан "в правильном порядке";вы выбираете потомков / потомков этого узла по имени, и анализ стримабельности не может установить, что эти потомки выбираются в порядке их появления на входе.

Ответ на самом деле прост: измените переменную на

<xsl:variable name="currNode" select="copy-of(.)" />

Таким образом, каждый раз, когда вы нажимаете на элемент DR, Saxon будет читать поддерево, укорененное в этом элементе, и удерживать его как дерево в памяти.Поскольку переменная теперь является обычным узлом в памяти, а не потоковым узлом, нет никаких ограничений на то, как он используется.

Позвольте мне добавить еще пару комментариев к вашему коду.

* 1012Во-первых, конструкция document(''), которая была популярна в XSLT 1.0, теперь полностью устарела.Гораздо лучше поместить данные поиска в глобальную переменную и получить к ней прямой доступ, используя
<xsl:param name="fields" select="$fieldArray/*"/>

. Вызов document('') действительно не удастся, если вы попытаетесь скомпилировать таблицу стилей и выполнить ее где-то, кроме оригиналарасположение исходного кода.

Во-вторых, код для вывода строки заголовка:

  <xsl:for-each select="$fields">
     <xsl:if test="position() != 1">
        <xsl:value-of select="$delimiter"/>
     </xsl:if>
     <xsl:value-of select="." />
  </xsl:for-each> 

может быть упрощен до

<xsl:value-of select="$fields" separator="{$delimiter}"/>

Аналогично, код для строк данных:

<xsl:for-each select="$fields">
  <xsl:if test="position() != 1">
    <xsl:value-of select="$delimiter"/>
  </xsl:if>
  <xsl:value-of select="$currNode/*/*[name() = current()]" />
</xsl:for-each>

упрощается до

<xsl:value-of select="for $f in $fields return $currNode/*/*[name()=$f]"
              separator="{$delimiter}"/>
...