XSLT3.0流处理(Saxon):用saxon:while处理多XML文件触发空指针异常
问题描述
文件夹中有多个XML文件,运行时向XSLT传入文件夹路径、文件名前缀及文件总数。使用saxon:while循环批量处理文件时触发空指针异常,但移除循环处理单个文件时代码可正常运行。使用的是Saxon 11.2 EE版本。
简化后的代码与文件
XSLT代码
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:f="http://www.test.com/function" version="3.0" exclude-result-prefixes="#all" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon" > <xsl:output method="text" omit-xml-declaration="no" indent="yes" /> <xsl:mode streamable="yes" on-no-match="shallow-skip" /> <xsl:param name="inputPath"/> <xsl:param name="filename"/> <xsl:param name="numberOfFiles"/> <xsl:variable name="fileCount" select="0" saxon:assignable="yes"/> <xsl:variable name="fullFileName" select="''" saxon:assignable="yes"/> <xsl:template match="Input"> <saxon:while test="$numberOfFiles > $fileCount"> <saxon:assign name="fullFileName" select="concat($inputPath,$filename,'_',$fileCount,'.xml')"/> <out><xsl:value-of select="$fullFileName"/></out> <xsl:apply-templates select="doc($fullFileName)/Input/InData/InfoArray/copy-of(.)" mode="s" /> <saxon:assign name="fileCount" select="$fileCount+1"/> </saxon:while> </xsl:template> <xsl:template match="Info/UnitSellPrice" mode="s"> <price><xsl:value-of select="."/></price> </xsl:template> </xsl:stylesheet>
示例XML文件
XML 1
<?xml version="1.0" encoding="UTF-8"?> <Input> <InData> <InfoArray> <Info> <UnitSellPrice>100</UnitSellPrice> </Info> <Info> <UnitSellPrice>324.19</UnitSellPrice> </InfoArray> </InData> </Input>
XML 2
<?xml version="1.0" encoding="UTF-8"?> <Input> <InData> <InfoArray> <Info> <UnitSellPrice>500</UnitSellPrice> </Info> <Info> <UnitSellPrice>200</UnitSellPrice> </InfoArray> </InData> </Input>
错误信息
Looking for function Q{http://www.w3.org/2005/xpath-functions}concat#5 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet Looking for function Q{http://www.w3.org/2005/xpath-functions}doc#1 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet Looking for function Q{http://www.w3.org/2005/xpath-functions}copy-of#1 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet Looking for function Q{http://www.w3.org/2005/xpath-functions}concat#5 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet Looking for function Q{http://www.w3.org/2005/xpath-functions}doc#1 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet Looking for function Q{http://www.w3.org/2005/xpath-functions}copy-of#1 Trying net.sf.saxon.functions.registry.XSLT30FunctionSet java.lang.reflect.InvocationTargetException at sun.reflect.GeneratedMethodAccessor2720.invoke(Unknown Source) Caused by: java.lang.NullPointerException com.saxonica.ee.stream.Streamability.getItemType(Streamability.java:267)
问题分析与解决
核心原因
代码启用了流处理模式(<xsl:mode streamable="yes"/>),但流处理与saxon:while循环、可赋值变量(saxon:assignable="yes")的组合存在兼容性问题。Saxon的流处理引擎在分析循环内动态加载文档的路径表达式时,无法正确推导节点类型,导致空指针异常。
修复方案
方案1:禁用流处理模式
若文件体积不大无需流处理,直接移除流处理声明:
<!-- 移除该行 --> <xsl:mode streamable="yes" on-no-match="shallow-skip" />
替换为普通模式:
<xsl:mode on-no-match="shallow-skip" />
方案2:改用XSLT 3.0原生循环替代saxon:while
放弃Saxon扩展的循环和可赋值变量,使用for-each遍历文件序号,符合函数式编程范式:
<xsl:template match="Input"> <xsl:for-each select="1 to $numberOfFiles"> <xsl:variable name="currentCount" select=".-1"/> <!-- 从0开始计数 --> <xsl:variable name="fullFileName" select="concat($inputPath,$filename,'_',$currentCount,'.xml')"/> <out><xsl:value-of select="$fullFileName"/></out> <xsl:apply-templates select="doc($fullFileName)/Input/InData/InfoArray/copy-of(.)" mode="s" /> </xsl:for-each> </xsl:template>
同时删除可赋值变量的声明:
<!-- 移除这两个变量 --> <!-- <xsl:variable name="fileCount" select="0" saxon:assignable="yes"/> --> <!-- <xsl:variable name="fullFileName" select="''" saxon:assignable="yes"/> -->
若仍需保留流处理,可给doc()添加流处理参数:doc($fullFileName, map{'streamable': true()})。
方案3:封装文件处理逻辑到命名模板
将单个文件的处理逻辑隔离为命名模板,降低流处理引擎的分析复杂度:
<xsl:template match="Input"> <saxon:while test="$numberOfFiles > $fileCount"> <saxon:assign name="fullFileName" select="concat($inputPath,$filename,'_',$fileCount,'.xml')"/> <out><xsl:value-of select="$fullFileName"/></out> <xsl:call-template name="process-file"> <xsl:with-param name="file" select="$fullFileName"/> </xsl:call-template> <saxon:assign name="fileCount" select="$fileCount+1"/> </saxon:while> </xsl:template> <xsl:template name="process-file"> <xsl:param name="file"/> <xsl:apply-templates select="doc($file)/Input/InData/InfoArray/copy-of(.)" mode="s" /> </xsl:template>
内容的提问来源于stack exchange,提问作者Chandra_S
相关产品推荐
相关产品推荐

