4.7. A Stylesheet That Emulates a for LoopWe 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.) 4.7.1. Template DesignOur 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:
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:
4.7.2. ImplementationLet'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 4.7.3. The Complete ExampleHere'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. |
|