An alternative 'for' loop using XSLT and template callbacks
20th October 2009, 27 comments, tagged with Symphony, XSLTXSLT 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 »
Oh my wowz! it’s so good. im love it. its real for us :)
sexy naked girls
sex offender in washington or sexy milf pics or free webcam sex or free asian sex videos or download free sex videos
free teen sex pictures or what is sex or monster’s ball sex scene or britney spears sex video or little girls 13 17 sex
sex wrestling or free animal sex videos or teen sex porn or adult live sex or sex quiz
weird sexy or sex pro or teen girls having sex or harry potter sex or scuba sex
free hardcore teen sex pics or sexy arena or sex shop or sex games or group sex
sex fetish or paris sex video or dwarf sex or sex hungry joes or free sex offender reports
fantasy sex stories free or sex shops or oregon sex offender registry or little girls having sex or sexy feet
anal sex pregnant or sex pussy or sexy emma watson or mom sex or sexy myspace graphics
viagra for sale or adipex diet pills or zanaflex or zyban or adipex generic
allme sex stories or forced sex stories or sex tapes or
order soma or zolpidem from canada or viagra for sale without a prescription or glucophage side effects or
birth defects caused by xanax or tramadol cod or paypal xanax or cheapest tadalafil or recent news on cymbalta
phentermine 37.5 mg or no prescription adderall or darvocet vs vicodin or snorting clonazepam or buy tramadol
lorazepam 1mg tablets or cheap 37 5 phentermine or buy cheap viagra online uk or buy hydrocodone or
blue phentermine 30mg or ativan withdrawal or paxil side effect or generic valium or
buy zyrtec or order phentermine or buy adderall pills or paxil side effects or
KPYBWJ xbpepwhdiata, [url=http://qfozbjubfkxj.com/]qfozbjubfkxj[/url], [link=http://uphkphrohvfe.com/]uphkphrohvfe[/link], http://ciartbraipkm.com/
phentermine cheap or order soma or phentermine without rx or acai berry products or viagra liver damage
description of soma or hydrocodone apap 5 500 or acyclovir side effects or zyrtec d 12 hour or generic paxil
fioricet side effects or is soma a barbiturate or lansoprazole or prescription drug called soma or buy meridia
online ambien or viagra without prescription or accutane side effects or viagra pills or phentermine free shipping
adderall online or discount valium online or doxycycline side effects or long term ambien or effexor withdrawl
paxil side effect or adipex p or what is xanax used for or cheapest viagra prices or how alcohol affects norvasc
viagra stories or soma cube or cheap xanax online or cheap phentermine online or celexa withdrawal
prozac suicide class action lawsuit or hydrocodone withdrawal or acai berry supplement for weight loss or birth defects caused by xanax or buy ativan