我需要将一个大型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>
答案 0 :(得分:2)
问题是变量:
<xsl:variable name="currNode" select="." />
这将变量绑定到流输入节点,这是不允许的,因为Saxon无法确保您从该输入节点进行的选择是“以正确的顺序”进行的;您通过名称选择了该节点的子代/后代,并且流式分析无法确定这些后代是按其在输入中出现的顺序进行选择的。
答案实际上很简单:将变量更改为
<xsl:variable name="currNode" select="copy-of(.)" />
这样,每次您遇到DR元素时,Saxon都会读取以该元素为根的子树并将其作为树保存在内存中。由于变量现在是常规的内存节点,而不是流式节点,因此对其使用方式没有任何限制。
请允许我对您的代码进行其他一些评论。
首先,在XSLT 1.0中流行的document('')
构造现在已经完全过时了。最好将查找数据放在全局变量中,然后使用
<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}"/>