XSLT 2.0 中的尾递归函数不起作用
Posted
技术标签:
【中文标题】XSLT 2.0 中的尾递归函数不起作用【英文标题】:Tail-recursive function in XSLT 2.0 is not working 【发布时间】:2012-12-03 14:30:54 【问题描述】:我正在尝试在 XSLT 2.0 中编写一个尾递归函数,它遍历日期的多值变量并返回最早的一个。由于某种原因,SaxonHE9.4 无法将我的函数识别为尾递归,当输入文件的条目超过 150-200 个左右时,我收到以下错误:
tail_rec_test.xsl 第 73 行出错:嵌套函数太多 来电。可能是由于无限递归。在内置模板规则中
这是我的 xml 输入:
<?xml version="1.0"?>
<Events>
<Event>
<Date>2004-01-01</Date>
</Event>
<Event>
<Date>2003-01-01</Date>
</Event>
<Event>
<Date>2002-01-01</Date>
</Event>
<Event>
<Date>2001-01-01</Date>
</Event>
<Event>
<Date>2005-01-01</Date>
</Event>
<Event>
<Date>2006-01-01</Date>
</Event>
<Event>
<Date>2007-01-01</Date>
</Event>
<Event>
<Date>2008-01-01</Date>
</Event>
</Events>
这是我的 xsl 文件的样子:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:own="http://ownfunctions">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:function name="own:findEarliestDate">
<xsl:param name="dates" as="xs:date*"/>
<xsl:variable name="size"><xsl:value-of select="count($dates)" /></xsl:variable>
<xsl:choose>
<xsl:when test="$size > 0">
<xsl:value-of select="own:findEarliestDate-helper($dates, $size, xs:date('2050-01-01'))" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:function name="own:findEarliestDate-helper" as="xs:date">
<xsl:param name="items" as="xs:date*"/>
<xsl:param name="i" as="xs:integer"/>
<xsl:param name="curMin" as="xs:date"/>
<xsl:choose>
<xsl:when
test="$i = 0">
<xsl:value-of select="xs:date($curMin)"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="item" as="xs:date">
<xsl:value-of select="xs:date($items[$i])"/>
</xsl:variable>
<xsl:variable name="next" as="xs:date">
<xsl:choose>
<xsl:when test="$item < $curMin">
<xsl:value-of select="$item"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$curMin"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="own:findEarliestDate-helper($items, $i - 1, $next)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="Events">
<xsl:variable name="items" as="xs:date*">
<xsl:for-each select="Event">
<xsl:value-of select="xs:date(Date)"/>
</xsl:for-each>
</xsl:variable>
<Test>
<EarliestDate>
<xsl:value-of select="own:findEarliestDate($items)"/>
</EarliestDate>
</Test>
</xsl:template>
</xsl:stylesheet>
如何将其转换为正确的尾递归函数? 我已经测试了这个例子,但我不能将它应用到我自己的代码中: http://www.nesterovsky-bros.com/weblog/2008/02/20/EfficientXslt20RecursionInSaxon9.aspx
【问题讨论】:
【参考方案1】:我无法重现这个。
使用 Saxon 9.4.06EE(评估副本)结果是:
<Test xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:own="http://ownfunctions">
<EarliestDate>2001-01-01</EarliestDate>
</Test>
Saxon-EE 9.4.0.6J from Saxonica
Java version 1.6.0_31
Using license serial number XXXXXXXX
Generating byte code...
Stylesheet compilation time: 2168 milliseconds
Processing file:/C:/Program%20Files/Java/jre6/bin/marrowtr.xml
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
Building tree for file:/C:/Program%20Files/Java/jre6/bin/marrowtr.xml using class net.sf.saxon.tree.tiny.TinyBuilder
Tree built in 10 milliseconds
Tree size: 27 nodes, 80 characters, 0 attributes
Execution time: 122ms
Memory used: 52169472
NamePool contents: 8 entries in 8 chains. 6 URIs
顺便说一句,你可以使用它来产生相同的结果:
<xsl:value-of select="min(/*/*/Date/xs:date(.))"/>
更新:
问题出在这一行:
<xsl:value-of select="own:findEarliestDate-helper($items, $i - 1, $next)"/>
因为函数的返回类型是xs:date
,所以上面这行并不是函数执行顺序的最后一行。它产生一个字符串(更准确地说,是一个文本节点),XSLT 处理器需要获取这个字符串并将其转换为xs:date
——这意味着该函数占用的内存是' 不会被丢弃,调用堆栈会继续增长,直到溢出。
解决方法很简单:
将以上内容替换为:
<xsl:sequence select="own:findEarliestDate-helper($items, $i - 1, $next)"/>
这会产生一个xs:date
,XSLT 处理器现在将该函数识别为尾递归。
我用 1000 个事件(原始代码在这些事件上崩溃)测试了更正后的代码,结果正常生成(而且速度更快)。
Saxon-EE 9.4.0.6J from Saxonica
Java version 1.6.0_31
Using license serial number XXXXXXXXXX
Generating byte code...
Stylesheet compilation time: 2002 milliseconds
Processing file:/C:/Program%20Files/Java/jre6/bin/marrowtr.xml
Using parser com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser
Building tree for file:/C:/Program%20Files/Java/jre6/bin/marrowtr.xml using class net.sf.saxon.tree.tiny.TinyBuilder
Tree built in 124 milliseconds
Tree size: 3032 nodes, 9800 characters, 0 attributes
Execution time: 364ms
Memory used: 55089048
NamePool contents: 8 entries in 8 chains. 6 URIs
【讨论】:
感谢您的快速回复 (+1)!我测试了简短版本,它绝对足够高效(适用于 5000 个事件)。虽然很高兴知道为什么我的函数没有表现得像尾递归函数。它停留在 150-200 个事件,但这可能取决于 VM 堆大小。你能尝试很多活动吗?我可以更新输入,但它会很大。只说你喜欢什么。再次感谢。 @Ivo,我发现了你的问题,并用问题、解释和简单的解决方案更新了我的答案。 请尝试改掉使用“xsl:value-of”的习惯。每次写这篇文章时,你应该问问自己 xsl:sequence 是否更合适。 xsl:value-of 指令创建一个文本节点;在此示例中,您将日期转换为文本节点,然后将其转换回日期。以上是关于XSLT 2.0 中的尾递归函数不起作用的主要内容,如果未能解决你的问题,请参考以下文章