We stressed earlier that the xsl:for-each
element is not a for
loop; it’s merely an iterator across a group of nodes. However, if you simply must implement a for
loop, there’s a way to do it. (Get ready to use recursion, though.)
Our design here is to create a named template that will take some arguments, then act as a for
loop processor. If you think about a traditional for
loop, it has several properties:
One or more initialization statements. These statements are processed before the
for
loop begins. Typically the initialization statements refer to an index variable that is used to determine whether the loop should continue.An increment statement. This statement specifies how the index variable should be updated after each pass through the loop.
A boolean expression. If the expression is
true
, the loop continues; if it is everfalse
, the loop exits.
Let’s take a sample from the world of Java and C++:
for (int i=0; i<length; i++)
In this scintillating example, the initialization statement is i=0
, the index variable (the variable whose value determines whether we’re done or not) is i
, the boolean expression we use to test whether the loop should continue is i<length
, and the increment statement is i++
.
For our purposes here, we’re going to make several simplifying assumptions. (Feel free, dear reader, to make the example as complicated as you wish.) Here are the shortcuts we’ll take:
Rather than use an initialization statement, we’ll require the caller to set the value of the local variable
i
when it invokes ourfor
loop processor.Rather than specify an increment statement such as
i++
, we’ll require the caller to set the value of the local variableincrement
. The default value for this variable is1
; it can be any negative or positive integer, however. The value of this variable will be added to the current value ofi
after each iteration through our loop.Rather than allow any conceivable boolean expression, we’ll require the caller to pass in two parameters;
operator
andtestValue
. The allowable values for theoperator
variable are=
,<
(coded as<
),>
(coded as>
),<>
(coded as<>
),<=
(coded as<=
), and>=
(coded as>=
). We’re doing things this way because there isn’t a way to ask the XSLT processor to evaluate a literal (such asi<length
) as if it were part of the stylesheet.
Let’s look at the parameters for our for
loop template:
<xsl:param name="i" select="1"/> <xsl:param name="increment" select="1"/> <xsl:param name="operator" select="="/> <xsl:param name="testValue" select="1"/>
Our for
template uses four parameters: the index variable, the increment, the comparison operator, and the test value. To emulate this C++ statement:
for (int i=1; i<=10; i++)
You’d use this markup:
<xsl:call-template name="for-loop"> <xsl:with-param name="i" select="1"/> <xsl:with-param name="increment" select="1"/> <xsl:with-param name="operator" select="<="/> <xsl:with-param name="testValue" select="10"/> </xsl:call-template>
To demonstrate our stylesheet, our first version simply prints out the value of our index variable each time through the loop:
Transforming... Iteration 1: i=1 Iteration 2: i=2 Iteration 3: i=3 Iteration 4: i=4 Iteration 5: i=5 Iteration 6: i=6 Iteration 7: i=7 Iteration 8: i=8 Iteration 9: i=9 Iteration 10: i=10 transform took 260 milliseconds XSLProcessor: done
Here’s the markup you’d use to emulate the Java statement for (int i=10; i>0; i-=2)
:
<xsl:call-template name="for-loop"> <xsl:with-param name="i" select="10"/> <xsl:with-param name="increment" select="-2"/> <xsl:with-param name="operator" select=">"/> <xsl:with-param name="testValue" select="0"/> </xsl:call-template>
In this case, the values of i
decrease from 10
to 0
:
Transforming... Iteration 1: i=10 Iteration 2: i=8 Iteration 3: i=6 Iteration 4: i=4 Iteration 5: i=2 transform took 110 milliseconds XSLProcessor: done
Here’s our complete stylesheet:
<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="text"/> <xsl:variable name="newline"> <xsl:text> </xsl:text> </xsl:variable> <xsl:template name="for-loop"> <xsl:param name="i" select="1"/> <xsl:param name="increment" select="1"/> <xsl:param name="operator" select="="/> <xsl:param name="testValue" select="1"/> <xsl:param name="iteration" select="1"/> <xsl:variable name="testPassed"> <xsl:choose> <xsl:when test="starts-with($operator, '!=')"> <xsl:if test="$i != $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:when test="starts-with($operator, '<=')"> <xsl:if test="$i <= $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:when test="starts-with($operator, '>=')"> <xsl:if test="$i >= $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:when test="starts-with($operator, '=')"> <xsl:if test="$i = $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:when test="starts-with($operator, '<')"> <xsl:if test="$i < $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:when test="starts-with($operator, '>')"> <xsl:if test="$i > $testValue"> <xsl:text>true</xsl:text> </xsl:if> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes"> <xsl:text>Sorry, the for-loop emulator only </xsl:text> <xsl:text>handles six operators </xsl:text> <xsl:value-of select="$newline"/> <xsl:text>(< | > | = | <= | >= | !=). </xsl:text> <xsl:text>The value </xsl:text> <xsl:value-of select="$operator"/> <xsl:text> is not allowed.</xsl:text> <xsl:value-of select="$newline"/> </xsl:message> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:if test="$testPassed='true'"> <!-- Put your logic here, whatever it might be. For the purpose --> <!-- of our example, we'll just write some text to the output stream. --> <xsl:text>Iteration </xsl:text><xsl:value-of select="$iteration"/> <xsl:text>: i=</xsl:text> <xsl:value-of select="$i"/><xsl:value-of select="$newline"/> <!-- Your logic should end here; don't change the rest of this --> <!-- template! --> <!-- Now for the important part: we increment the index variable and --> <!-- loop. Notice that we're passing the incremented value, not --> <!-- changing the variable itself. --> <xsl:call-template name="for-loop"> <xsl:with-param name="i" select="$i + $increment"/> <xsl:with-param name="increment" select="$increment"/> <xsl:with-param name="operator" select="$operator"/> <xsl:with-param name="testValue" select="$testValue"/> <xsl:with-param name="iteration" select="$iteration + 1"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template match="/"> <xsl:call-template name="for-loop"> <xsl:with-param name="i" select="'10'"/> <xsl:with-param name="increment" select="'-2'"/> <xsl:with-param name="operator" select="'>'"/> <xsl:with-param name="testValue" select="'0'"/> </xsl:call-template> </xsl:template> </xsl:stylesheet>
If you want to modify the for
loop to do something useful, put your code between these comments:
<!-- Put your logic here, whatever it might be. For the purpose --> <!-- of our example, we'll just write some text to the output stream. --> <xsl:text>Iteration </xsl:text><xsl:value-of select="$iteration"/> <xsl:text>: i=</xsl:text> <xsl:value-of select="$i"/><xsl:value-of select="$newline"/> <!-- Your logic should end here; don't change the rest of this --> <!-- template! -->
Get XSLT now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.