Book HomeXSLSearch this book

4.7. A Stylesheet That Emulates a for Loop

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.)

4.7.1. Template Design

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:

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. Implementation

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="&lt;="/>
  <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="&gt;"/>
  <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 Example

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!                                                        -->


Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.