XSLT How to do a classic for x to y loop?

13,322

Solution 1

XSLT is a functional programming language and as such it is very different to any procedural languages you already know.

Although for loops are possible in XSLT, they do not make use of the inherent strengths of XSLT (and functional programming in general).

for loops are routinely misused to address problems that are best solved with a functional approach instead (that is, matching templates). In other words, a loop is not really a "classic" in XSLT.

So, you might have to double back, identify the problem you are facing instead of discussion your solution. Then, the XSLT community might be able to suggest a solution that is more functional in nature. It might be that you've fallen victim to the XY problem.


Now, among the things XSLT is inherently good at is recursion. Often, problems that are solved with loops in procedural languages are solved with recursive templates in XSLT.

<xsl:template name="recursive-template">
   <xsl:param name="var" select="5"/>
   <xsl:choose>
     <xsl:when test="$var > 0">
       <xsl:value-of select="$var"/>
       <xsl:call-template name="recursive-template">
         <xsl:with-param name="var" select="$var - 1"/>
       </xsl:call-template>
     </xsl:when>
     <xsl:otherwise/>
   </xsl:choose>
</xsl:template>

To summarize, I suggest you look at "classic" recursion instead of "classic" for loops. You find more information about exactly this topic in an IBM article here.


EDIT as a response to your edited question. If your problem really boils down to outputting text content twice:

<?xml version="1.0" encoding="utf-8"?>

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

   <xsl:output method="text"/>

   <xsl:template match="/foo">
      <xsl:apply-templates select="bar"/>
      <xsl:apply-templates select="bar"/>
   </xsl:template>

</xsl:stylesheet>

This is not feasible of course for a dynamic number of iterations.

Solution 2

XSLT 2.0 has <xsl:for-each select="0 to 5"> but in XSLT 1.0 you can only for-each over node sets, not sequences of atomic values. The easiest way I've found to get around this is to use some kind of sufficiently generic selector expression that will match at least as many nodes as you want iterations, e.g.

<xsl:for-each select="/descendant::node()[position() &lt; 7]">
    <fo:block>
        <xsl:value-of select="position() - 1"/>
    </fo:block>
</xsl:for-each>

or if you don't necessarily know there will be at least 6 nodes in the input document then you can use document('') to treat the stylesheet itself as another input document.

<xsl:for-each select="document('')/descendant::node()[position() &lt; 7]">

In both cases the for-each will change the context node, so you'll need to save the outer context in a variable if you need to access it inside the for-each body

<xsl:variable name="dot" select="." />

Solution 3

Use a named template with parameters $i and $n; call the template with parameters {$i = 0, $N = 5}; have the template call itself recursively with parameters {$i + 1, $N} until $i > $N.

Example:

<xsl:template match="/">
<output>
    <!-- stuff before -->
    <xsl:call-template name="block-generator">
        <xsl:with-param name="N" select="5"/>
    </xsl:call-template>
    <!-- stuff after -->
</output>
</xsl:template>

<xsl:template name="block-generator">
    <xsl:param name="N"/>
    <xsl:param name="i" select="0"/>
    <xsl:if test="$N >= $i">
        <!-- generate a block -->
        <fo:block>
            <xsl:value-of select="$i"/>
        </fo:block>     
        <!-- recursive call -->
        <xsl:call-template name="block-generator">
            <xsl:with-param name="N" select="$N"/>
            <xsl:with-param name="i" select="$i + 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>
Share:
13,322
jpaoletti
Author by

jpaoletti

Updated on August 15, 2022

Comments

  • jpaoletti
    jpaoletti over 1 year

    I need to do a classic for i=0 to N loop, how can it be done in xstl 1.0?

    thanks.

    <xsl:for-each select="¿¿¿$i=0..5???">
        <fo:block>
            <xsl:value-of select="$i"/>
        </fo:block>
    </xsl:for-each>
    

    To give an example, I have

    <foo>
        <bar>Hey!</bar>
    </foo>
    

    And want an output of

    Hey!
    Hey!