Jumping through loops with XSL

13 Jun 2009

I was recently faced with the need to express a loop in XSL. Not the build-in for-each that iterates over nodes matching an XPath, but one with a counter. In C# this kind of loop is easily expressed as

for (int i = 0; i < 5; i++)
    // do something

No such for-construct exists in XSL. In fact there’re no looping constructs of any kind in XSL, except for the one used to iterate nodes. However, since XSL and C# are both Turing complete languages, and since anything that can be expressed in one Turing complete language can be expressed in another, it follows that it must be possible to express a loop with a counter in XSL.

But as Alan Parlis puts it in Epigrams on Programming:

Beware of the Turing tar-pit in which everything is possible but nothing of interest is easy.

Although XSL and C# are both Turing complete languages, XSL is a functional programming language and therefore belongs to a strain of languages that forgo build-in looping constructs. Instead, looping is accomplished through recursion.

We can turn the iterative C# loop into a recursive one like so:

void Loop(int i) {
    if (i > 0) {
        // do something
        Loop(i - 1);
    }
}

and transform the recursive solution into an XSL template:

<xsl:template name="Loop">
    <xsl:param name="i" select="5"/>
    <xsl:if test="$i > 0">
        <!-- do something -->
        <xsl:call-template name="Loop">
            <xsl:with-param name="i" select="$i - 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

Okay, but what about nested loops then:

for (int i = 0; i < 5; i++)
    for (int j = 0; j < 5; j++)
        // do something

Giving it some thought, the nested loops (of any nesting level) can be expressed using recursion following a pattern of nested if-statements:

void NestedLoops(int i, int j, int k) {
    if (i > 0) {
        // do something
        if (j == 1)
            NestedLoops(i - 1, k, k);
        else
            NestedLoops(i, j - 1, k);
    }
}

Again, performing a one-to-one transformation to XSL:

<xsl:template name="NestedLoops">
    <xsl:param name="i"/>
    <xsl:param name="j"/>
    <xsl:param name="k"/>
    <xsl:if test="$i > 0">
        <!-- do something -->
        <xsl:choose>
            <xsl:when test="$j = 1">
                <xsl:call-template name="NestedLoops">
                    <xsl:with-param name="i" select="$i - 1"/>
                    <xsl:with-param name="j" select="$k"/>
                    <xsl:with-param name="k" select="$k"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="NestedLoops">
                    <xsl:with-param name="i" select="$i"/>
                    <xsl:with-param name="j" select="$j - 1"/>
                    <xsl:with-param name="k" select="$k"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:if>    
</xsl:template>

Wordy, but seeing through the angle brackets I find solution quite elegant.