2

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!
jpaoletti
  • 745
  • 1
  • 11
  • 19

3 Answers3

6

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.

Community
  • 1
  • 1
Mathias Müller
  • 20,222
  • 13
  • 53
  • 68
  • I agree with you but the point is that i need to do the same thing 2 times. Just that, recursion is nice but looks like a bit complicated for the goal. However you gave me an idea with a parametrized template. Be right back! – jpaoletti Feb 14 '14 at 12:44
  • Granted, recursion requires a bit of thinking initially, but you've burdened yourself with understanding recursion the moment you decided to use XSLT. If you need something exactly 2 times you might not even need recursion. But is is crucial that you reveal your _problem_ instead of your solution. – Mathias Müller Feb 14 '14 at 12:48
  • Edited with an example. Almost solved it with a named template but not working so far, need to investigate a little more about the topic. Thanks for help – jpaoletti Feb 14 '14 at 13:13
  • Yeah, got it almost as you suggest. Thanks! – jpaoletti Feb 14 '14 at 13:36
3

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="." />
Ian Roberts
  • 114,808
  • 15
  • 157
  • 175
2

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>
michael.hor257k
  • 96,733
  • 5
  • 30
  • 46