I am trying to write a tail-recursive function in XSLT 2.0, which iterates through a multivalued variable of dates and returns the earliest one. For some reason my function is not recognized by SaxonHE9.4 as tail-recursive and I get the following error, when the input file has more than 150-200 entries or so:
Error on line 73 of tail_rec_test.xsl: Too many nested function
calls. May be due to infinite recursion. in built-in template rule
Here’s my xml-input:
<?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>
This is what my xsl-file looks like:
<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>
How can I transform it into a correct tail-recursive function?
I have tested this example, but I cannot apply it to my own code:
http://www.nesterovsky-bros.com/weblog/2008/02/20/EfficientXslt20RecursionInSaxon9.aspx
I can’t repro this.
Using Saxon 9.4.06EE (evaluation copy) the result is:
BTW, one can use just this to produce the same result:
Update:
The problem is in this line:
Because the return type of the function is
xs:date, the above line isn’t the last line in the execution sequence of the function. It produces a string (more precisely, a text node), and the XSLT processor needs to get this string and to convert it to anxs:date— that means that the memory ocupied by the function isn’t discarded and the call-stack continues to grow, until it overflows.The solution is simple:
Replace the above with:
This produces an
xs:dateand the XSLT processor now recognizes the function as tail recursive.I tested the corrected code with 1000 events (on which the original code crashes) and the result is produced normally (and faster).