This can happen when processing a very long sequence with primitive recursion.
Imagine implementing the sum()
function with a recursive named template:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:call-template name="sum">
<xsl:with-param name="pSeq" select="/*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="pAccum" select="0"/>
<xsl:param name="pSeq"/>
<xsl:choose>
<xsl:when test="not($pSeq)">
<xsl:value-of select="$pAccum"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum">
<xsl:with-param name="pAccum"
select="$pAccum+$pSeq[1]"/>
<xsl:with-param name="pSeq"
select="$pSeq[position() >1]"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
when applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the result is:
55
Now, imagine that nums
has 1000000 (1M) num
children. This would be a legitimate attempt to find the sum of one million numbers, however most XSLT processors typically crash at a recursion depth at or around 1000.
The solution:
Use tail-recursion (a special kind of recursion where the recursive call is the last instruction in the template). Some XSLT processors recognize tail recursion and optimize it internally to iteration, so there is no recursion and no stack overflow.
Use DVC-style recursion (Divide and Conquer). This works with all XSLT processors. The maximum recursion depth is log2(N) and is feasible for most practical purposes. For example, processing a sequence of 1M items requires a stack depth of only 19.
Here is a DVC implementation of the sum template:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:call-template name="sum">
<xsl:with-param name="pSeq" select="/*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="pSeq"/>
<xsl:variable name="vCnt" select="count($pSeq)"/>
<xsl:choose>
<xsl:when test="$vCnt = 0">
<xsl:value-of select="0"/>
</xsl:when>
<xsl:when test="$vCnt = 1">
<xsl:value-of select="$pSeq[1]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalf" select=
"floor($vCnt div 2)"/>
<xsl:variable name="vSum1">
<xsl:call-template name="sum">
<xsl:with-param name="pSeq" select=
"$pSeq[not(position() > $vHalf)]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vSum2">
<xsl:call-template name="sum">
<xsl:with-param name="pSeq" select=
"$pSeq[position() > $vHalf]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$vSum1+$vSum2"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Using this template to find the sum of one million numbers takes some time, but produces the correct result without crashing.