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 ever false, 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 our for loop processor.
Rather than specify an increment statement such as i++, we'll require the caller to set the value of the local variable increment. The default value for this variable is 1; it can be any negative or positive integer, however. The value of this variable will be added to the current value of i after each iteration through our loop.
Rather than allow any conceivable boolean expression, we'll require the caller to pass in two parameters; operator and testValue. The allowable values for the operator 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 as i<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! -->
Copyright © 2002 O'Reilly & Associates. All rights reserved.