Implementing a for loop in XSLT

XSLT is a language designed to transform XML documents. Instead of being a procedural programming language, XSLT models itself after functional programming, borrowing many of its fundamental design concepts. Functional programming deals with expressions rather than statements. Statements are executed. They may or may not return a value, and variables directly or indirect impacted by statements can be modified, resulting in potentially far reaching side effects once a particular statement has been executed. Expressions on the other hand, are evaluated. They can be nested, chained together as arguments to a parent expression, and combined together in various other ways. Expressions all are evaluated eventually though, simplifying down into a single return value. In a functional programming language, the parameters that an expression acts upon are immutable and thus there are no side effects when an expression is evaluated.

XSLT takes the same approach to its underlying design philosophy. In XSLT, templates are essentially expressions that can be combined together in various ways, eventually being evaluated and returning an output value that is typically, although not necessarily, a well formed XML document. Because XSLT uses immutable variables, there are no side effects when templates are evaluated. This means that an XSLT engine can optimize the transform, applying template rules in any order, sometimes even simultaneously.

Imagine what would happen if variables were mutable instead. A given template in an XSLT stylesheet can match multiple nodes in an XML document. The order in which those nodes are read would suddenly matter if say, the template output some text from the node and appended the value of an auto incrementing integer variable. Differing implementations of XSLT engines would result in different outputs, a clear violation of encapsulation. Worse, any implementation that has a non-deterministic order of evaluation will have race conditions and result in nondeterministic output every time the transform is run. This also holds true at the template level as well, as the side effects from one template that modify a variable that is used by another template could very well impact the output of the other. Nondeterministic output in a language designed specifically to output documents is a nonstarter from a design perspective, so XSLT would suddenly require some mechanism with which to specify the order of evaluation. The end result would be a language that would be quite cumbersome and awkward, as opposed to the clean and concise syntax that XSLT uses today.

Its important to have this functional programming mindset when working with XSLT. Trying to do procedural programming in XSLT is like trying to fit a square peg into a round hole. Its possible, but the end result is not pretty. Take for instance, a for loop. XSLT provides iteration capabilities via XPATH that lets it iterate over a set of nodes and return the ones that match a certain set of criteria. However, this is not technically not the same as a for loop in a procedural programming language, which specifies a counter, an increment/decrement operation, and a comparison operator that terminates the loop. Because variables are immutable in XSLT, something as simple as having a loop counter suddenly becomes a nontrivial nightmare.

It is indeed possible to simulate a for loop in XSLT, but whether or not you would want to is another question altogether. This is more an interesting exercise in how to map functional programming paradigm to a procedural one. The following solution is based on the mammoth O’Reilly XSLT book by Doug Tidwell. The important thing to keep in mind however, is that in a real world scenario, the need for a for loop might indicate that XSLT may not be the right tool for the job. I’ve encountered usage of such for loops in some hairy XSLT code, and it isn’t pretty. Its best to keep procedural programming tasks left to actual procedural programming languages. This is a shocking concept that can be lost on a lot of people who forget that they can either do any necessary pre or post processing in code instead of in XSLT, or write a code module in a more powerful language that can then be called in XSLT as an extension function.

But I digress. If you’re a glutton for punishment, here’s how you would go about implementing a for loop. The basic idea here is that because variables are immutable, we cannot increment the loop-counter directly. Instead, we can it as a parameter by recursively calling the template with the incremented value.

 
<xsl:template name="for-loop">
	<xsl:param name="loop-counter" />
	<xsl:param name="increment-value" /> 

        ......do some processing here.....

	<xsl:call-template name="for-loop">
		<xsl:with-param name="loop-counter"   select="$loop-counter + $increment-value" />
		<xsl:with-param name="increment-value" select="$increment-value"/> 
	</xsl:call-template>
</xsl:template>

Remember that any task that can be done iteratively can also be done with recursion (given a large enough call stack). The recursion will terminate once the conditional of the for-loop template has been satisfied. We will need to have some way of testing the conditional against the current value of the for-loop counter value. To make the template more flexible, we define a “$conditional-test” parameter that specifies what comparison operator to perform and a “$conditional-value” parameter to evaluate it against. A “$conditional-passed” variable will be used to store the result of the comparison. Now, because XSLT does not have a way to pass in an expression that can be dynamically evaluated at runtime, we will have to use to manually map each potential value of “$conditional-test” to the appropriate if statement. This requires writing a lot of tedious code:


  <xsl:variable name="conditional-passed">
      <xsl:choose>
        <xsl:when test="$conditional-test = '!='">
          <xsl:if test="$loop-counter != $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$conditional-test = '&gt;='">
          <xsl:if test="$loop-counter &gt;= $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        ... add support for other conditional tests here ...

        <xsl:otherwise>
          <xsl:message terminate="yes">
            Invalid conditional-test passed in
          </xsl:message>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

If the value of “$conditional-test” matches none of the pre-defined values, then we use xsl:message to write the error to output and terminate the template. Otherwise, the final step is to check the value of “$conditiona-passed”:

 
     <xsl:if test="$conditional-passed='true'">
         
         ... Add any custom code here that the for loop needs to execute.   In this case, we merely print the loop-counter
         <xsl:text>loop-counter: </xsl:text>
         <xsl:value-of select="$loop-counter" />
         <xsl:text> &#xA;</xsl:text>

      <xsl:call-template name="for-loop">
        <xsl:with-param name="loop-counter"   select="$loop-counter + $increment-value" />
        <xsl:with-param name="increment-value" select="$increment-value"/>
        <xsl:with-param name="conditional-test" select="$conditional-test"/>
        <xsl:with-param name="conditional-value" select="$conditional-value"/>
      </xsl:call-template>
    </xsl:if>

Note that the custom code inside this if-statement corresponds to the user-defined code found inside a standard procedural for-loop. If $testPassed is not equal to ‘true’, then we have reached the end of our loop and can exit the template. Otherwise, we execute the code and recursively call the template again, after having incremented $counter accordingly. This simulates the next iteration of our for-loop. Note that this section of the template will need to be changed every-time the for-loop needs execute a different set of code. Because there is no way in XSLT to pass in a set of instructions as a parameter to be later dynamically evaluated at runtime, this makes an XSLT for-loop template hard to re-use. Anytime new functionality is needed inside our for-loop construct, a new template must be created. The old template can be copy/pasted and the custom code inside the if-statement modified accordingly, but this is not an ideal solution. This further drives home the point that its best to use the right tool for the right job.

Putting it all together, we arrive at the following code in all its glory:

<?xml version="1.0"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:param name="loop-counter" select="10" />
  <xsl:param name="increment-value" select="-1"/>
  <xsl:param name="conditional-test" select="'&gt;='"/>
  <xsl:param name="conditional-value" select="0" />

  <xsl:template match="/">
    <xsl:call-template name="for-loop">
      <xsl:with-param name="loop-counter" select="$loop-counter" />
      <xsl:with-param name="increment-value" select="$increment-value"/>
      <xsl:with-param name="conditional-test" select="$conditional-test"/>
      <xsl:with-param name="conditional-value" select="$conditional-value"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template name="for-loop">
    <xsl:param name="loop-counter" />
    <xsl:param name="increment-value" />
    <xsl:param name="operator" />
    <xsl:param name="conditional-value" />

    <xsl:variable name="conditional-passed">
      <xsl:choose>
        <xsl:when test="$operator = '!='">
          <xsl:if test="$loop-counter != $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$operator = '&gt;='">
          <xsl:if test="$loop-counter &gt;= $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$operator = '&lt;='">
          <xsl:if test="$loop-counter &lt;= $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$operator = '&gt;'">
          <xsl:if test="$loop-counter &gt; $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$operator = '&lt;'">
          <xsl:if test="$loop-counter &lt; $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>

        <xsl:when test="$operator = '='">
          <xsl:if test="$loop-counter = $conditional-value">
            <xsl:text>true</xsl:text>
          </xsl:if>
        </xsl:when>
        <xsl:otherwise>
          <xsl:message terminate="yes">
            Invalid conditional-test passed in
          </xsl:message>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>


    <xsl:if test="$conditional-passed='true'">
      <xsl:text>loop-counter: </xsl:text>
      <xsl:value-of select="$loop-counter" />
      <xsl:text> &#xA;</xsl:text>

      <xsl:call-template name="for-loop">
        <xsl:with-param name="loop-counter"   select="$loop-counter + $increment-value" />
        <xsl:with-param name="increment-value" select="$increment-value"/>
        <xsl:with-param name="conditional-test" select="$conditional-test"/>
        <xsl:with-param name="conditional-value" select="$conditional-value"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>
 

A simple .net program can be whipped up to apply this transform:

using System.Xml;
using System.Xml.Xsl;
using System.Text;

namespace XSLTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            XslCompiledTransform stylesheet = new XslCompiledTransform(); 
            stylesheet.Load("Forloop.xslt"); 
            stylesheet.Transform("input.xml", "output.xml");
        }
    }
}

Since this template blindly prints out the loop-counter, the input xml is completely irrelevant. For a real working world example, one would likely define new parameters in the for-loop template that would read in various values that the for-loop would then operate on. In our case the output simply looks like the following:

loop-counter: 10 
loop-counter: 9 
loop-counter: 8 
loop-counter: 7 
loop-counter: 6 
loop-counter: 5 
loop-counter: 4 
loop-counter: 3 
loop-counter: 2 
loop-counter: 1 
loop-counter: 0 


One Comment on “Implementing a for loop in XSLT

  1. Thank you for your post about XSLT date convertions. It was very usfeul. I was looking for a way to convert a dates without using explicit Xpath date convertion functions and you gave just that. Thank you again.

Leave a Reply

Your email address will not be published. Required fields are marked *