Third Person

Nick Dunn is a technical lead and UX architect at Airlock, a digital creative agency in London. He is passionate about accessibility, user experience and code-indenting. He recently played Owl CitySky Diver.

First Person

I like to bookmark things that interest me. I also like to plan things, take photos of things I see, and tap my feet to a groove. I sometimes broadcast, and discuss too. I have an online CV and I'm partial to Symphony CMS and social coding

An alternative 'for' loop using XSLT and template callbacks

20th October 2009, 27 comments, tagged with , XSLT 1.0 has no native way of achieving a for loop, without iterating through XML. Until now.

XSLT is planted firmly in the declarative programming style — something that regularly frustrates newcomers from procedural languages such as PHP or JavaScript. You need to change your programming mindset to approach familiar concepts in new ways. One such example is the traditional for loop. In XSLT there exists a for-each element which iterates over an XML nodeset:

<xsl:for-each select="countries/country">
    <xsl:value-of select="text()"/>
</xsl:for-each>

As dandy as this is, it relies on having XML to iterate over. How can you achieve a for loop to provide, say, ten iterations, from 1 to 10. In PHP this would be a simple as:

for($i=1; $i<=10; $i++) {
    echo($i);
}

To achieve this using XSLT you need to take a different approach. Below I’ll outline two ideas for generating a for loop using XSLT 1.0 — one a simple hack, the other more advanced and elegant.

1. Tokens at the ready

Using the tokenize() function of the EXSLT strings library one can break a string of a given length into individual characters and loop through each:

<xsl:for-each select="str:tokenize('..........')/token">
    <xsl:value-of select="position()"/>
</xsl:for-each>

There are ten full-stops in the string, meaning that ten token nodes will be created. Not particularly elegant or maintainable. This can be improved on slightly by generating the string dynamically using the EXSLT padding() function, making it easier to set its length.

<xsl:for-each select="str:tokenize(str:padding(10,'.'))/token">
    <xsl:value-of select="position()"/>
</xsl:for-each>

2. Recursive to the max

The more ‘XSL-like’ approach is to use a recursive template for the iteration. The template is supplied with start and end conditions and repeatedly calls itself until the end condition is met. The finest explanation I’ve seen is Loop with recursion in XSLT on IBM developerWorks. Recommended reading.

Writing a recursive template to achieve the above requirement is relatively trivial:

<xsl:template name="for-loop">
    <xsl:param name="count" select="1"/>

    <xsl:if test="$count > 0">
        <xsl:value-of select="$count"/>
        <xsl:call-template name="for-loop">
            <xsl:with-param name="count" select="$count - 1"/>
        </xsl:call-template>
    </xsl:if>

</xsl:template>

The problem here is that the template output is muddled into the same template as the iteration/recursion logic. It would make more sense to have a single “iterator” template to perform the recursion, and have the output rendered in a separate template. Consider the following scenario:

You’re building a form and need to fill a select drop down with ten options (1–10), but you also need to render five checkboxes. Using the above example you would need to replicate the template twice, naming them select-loop and checkbox-loop. Inside each respective template you would change the output to return either an <option> element or a checkbox.

My colleague Paul Garrett and devised a cunning way for a call-template to execute a callback template which is where the custom rendering is achieved.

Consider this markup to render a select box with ten options, followed by five checkboxes:

<xsl:template match="data"> 

    <select>
        <xsl:call-template name="iterator">
            <xsl:with-param name="iterations" select="'10'"/>
            <xsl:with-param name="callback" select="'render-option'"/>
        </xsl:call-template>
    </select>

    <xsl:call-template name="iterator">
        <xsl:with-param name="iterations" select="'5'"/>
        <xsl:with-param name="callback" select="'render-checkbox'"/>
    </xsl:call-template>

</xsl:template>

<xsl:template match="render-option" mode="iterator-callback">
    <xsl:param name="position"/>
    <option>
        <xsl:value-of select="$position"/>
    </option>
</xsl:template>

<xsl:template match="render-checkbox" mode="iterator-callback">
    <xsl:param name="position"/>
    <input type="checkbox" name="fields[checkbox-{$position}]"/>
</xsl:template>

We can call a generic iterator template passing the number of iterations required (as you would a traditional for loop) while providing the name of a callback template. The iterator template creates an XML node of the same name and selects it using an apply-templates. The callback template is passed the position in the loop providing context.

The full iterator template:

<xsl:template name="iterator">
    <xsl:param name="start" select="'1'"/>
    <xsl:param name="iterations" select="$iterations"/>
    <xsl:param name="direction" select="'+'"/>
    <xsl:param name="callback"/>
    <xsl:param name="callback-params"/>

    <xsl:param name="count" select="$iterations"/>

    <xsl:if test="$callback and $count > 0">

        <xsl:variable name="position">
            <xsl:choose>
                <xsl:when test="$direction='-'"><xsl:value-of select="$start - ($iterations - $count)"/></xsl:when>
                <xsl:otherwise><xsl:value-of select="$start + ($iterations - $count)"/></xsl:otherwise>
            </xsl:choose>
        </xsl:variable>

        <xsl:variable name="callback-node">
            <xsl:element name="{$callback}">
                <xsl:if test="$callback-params">
                    <xsl:copy-of select="$callback-params"/>
                </xsl:if>
            </xsl:element>
        </xsl:variable>

        <xsl:apply-templates select="exsl:node-set($callback-node)" mode="iterator-callback">
            <xsl:with-param name="position" select="$position"/>
        </xsl:apply-templates>

        <xsl:call-template name="iterator">
            <xsl:with-param name="start" select="$start"/>
            <xsl:with-param name="iterations" select="$iterations"/>
            <xsl:with-param name="direction" select="$direction"/>
            <xsl:with-param name="callback" select="$callback"/>
            <xsl:with-param name="callback-params" select="$callback-params"/>
            <xsl:with-param name="count" select="$count - 1"/>
        </xsl:call-template>

    </xsl:if> 

</xsl:template>

Additional parameters can be passed for more advanced iterations and callbacks:

start allows you to start the iteration from a specific number, defaults to 1:

<xsl:with-param name="start" select="'3'"/>

iterations is the number of iterations to perform:

<xsl:with-param name="iterations" select="'100'"/>

direction allows you to count down (“-“) rather than the default up (“+”)

<xsl:with-param name="direction" select="'-'"/>

callback is the name of the XML node created to match on with your callback template

<xsl:with-param name="callback" select="'my-template'"/>

callback-params accepts XML which are then passed to the callback template

<xsl:with-param name="callback-params">
    <name>Nick Dunn</name>
</xsl:with-param>

<xsl:template match="render-user" mode="iterator-callback">
    <xsl:param name="position"/>
    <p>Name: <xsl:value-of select="name"/></p>
</xsl:template>

(Note: you will need to include the EXSLT common namespace in your stylesheet to use the node-set function in the template above!)

Conclusion

We have seen the different ways of achieving a numeric for loop in XSLT, and in doing so created a re-usable recursive template with a dynamic callback. I’ve not seen this callback method documented anywhere else, so this may be a new technique that has novel applications beyond this example. Recurse to the max, don’t tokenize.

There have been 27 comments. Add yours »

RhinoMaster 20 May 2009 16:36

Oh my wowz! it’s so good. im love it. its real for us :)

sexy-naked-girls 17 February 2010 00:12

sexy naked girls

mvvlkhmhxh 28 February 2010 12:45

KPYBWJ xbpepwhdiata, [url=http://qfozbjubfkxj.com/]qfozbjubfkxj[/url], [link=http://uphkphrohvfe.com/]uphkphrohvfe[/link], http://ciartbraipkm.com/


Submit your comments

Orchestrated by Symphony CMS