2

I know that being a functional language, XSL doesn't have something like traditional for loops (but for-each).

I'm trying to create a table with a fixed number of (7) starting from a variable number of elements. In a word, I have

<items>
    <item />
    <item />
    <item />
</item>

How can I turn this into

<table>
    <tr><item /></tr>
    <tr><item /></tr>
    <tr><item /></tr>
    <tr></tr>
    <tr></tr>
    <tr></tr>
    <tr></tr>
</table>

? With count() it is easy to calculate that I need 4 more empty , but how to do this? With a for loop I could easily solve the problem, or maybe modifying <items> adding 4 empty elements to it, but, being new to xsl, i can't even do that.

Thanks

pistacchio
  • 56,889
  • 107
  • 278
  • 420
  • Good question, +1. See my answer for two different non-recursive ways to solve your problem. Non-recursive solutions should be preferred over recursive ones, as explained in my answer. – Dimitre Novatchev Mar 09 '11 at 14:19
  • possible duplicate of [How to show a character n times in xslt](http://stackoverflow.com/questions/5089096/how-to-show-a-character-n-times-in-xslt) –  Mar 09 '11 at 15:53

4 Answers4

3

You're looking for a recursion solution. The solution involves writing a template that calls itself when a passed in count is less than the number of times you need the template to run.

A good example is posted by IBM at:

http://www.ibm.com/developerworks/xml/library/x-tiploop.html

You're code may look something like:

<xsl:template name="itemLoop">

  <xsl:param name="count" select="0"/>

  <xsl:if test="$count &lt; 7">
    <tr><xsl:apply-templates select="/items/item[$count]"/></tr>
    <xsl:call-template name="itemLoop">
      <xsl:with-param name="count" select="$count + 1"/>
    </xsl:call-template>
  </xsl:if>

</xsl:template>
Jeff Fritz
  • 9,821
  • 7
  • 42
  • 52
1

It is best to always avoid recursion, when possible.

In XSLT 2.0 one simply writes:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:param name="pNumRows" select="7"/>
    <xsl:variable name="vDoc" select="/"/>

 <xsl:template match="items">
     <table>
       <xsl:for-each select="1 to $pNumRows">
        <tr><xsl:copy-of select="$vDoc/items/item[current()]"/></tr>
       </xsl:for-each>
     </table>
 </xsl:template>
</xsl:stylesheet>

and when this transformation is applied to the provided XML document:

<items>
    <item />
    <item />
    <item />
</items>

the wanted correct result is produced:

<table>
   <tr>
      <item/>
   </tr>
   <tr>
      <item/>
   </tr>
   <tr>
      <item/>
   </tr>
   <tr/>
   <tr/>
   <tr/>
   <tr/>
</table>

Very few people know that for a large number of cases one can avoid recursion in XSLT 1.0, too:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pNumRows" select="7"/>
 <xsl:param name="vDoc" select="/"/>


 <xsl:template match="items">
     <table>
       <xsl:for-each select=
             "(document('')//node())[not(position() > $pNumRows)]">
         <xsl:variable name="vPos" select="position()"/>

         <tr><xsl:copy-of select="$vDoc/items/item[position()=$vPos]"/></tr>
       </xsl:for-each>
   </table>
 </xsl:template>
</xsl:stylesheet>

This is called the method of Piez and one can read about it here.

Remember: Recursion is much slower than simple iteration -- it also tends to crash with stack overflow if the list has considerable length (around 1000 or more) and special programmatic measures are not taken.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
0

You should have a look at this question here:

How to count elements in categorys and compare it with other categorys in XSLT

There is the same problem discussed there (the title doesn't mention this) and there are many good answers on how to solve this.

Community
  • 1
  • 1
Chris
  • 7,675
  • 8
  • 51
  • 101
-1

Here is an example sheet which does what you want. I work out how many items there are using count and then how many empty rows I need to create. I use a for_loop template.

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

    <xsl:template match="/">
        <table>
            <xsl:for-each select="//items/item">
                <tr><item/></tr>
            </xsl:for-each>

            <xsl:variable name="itemCount" as="xs:integer" select="count(//items/item)" />
            <xsl:call-template name="for_loop">
                <xsl:with-param name="i">
                    <xsl:value-of select="0" />
                </xsl:with-param>
                <xsl:with-param name="count">
                    <xsl:value-of select="7-$itemCount" />
                </xsl:with-param>
            </xsl:call-template>

        </table>
    </xsl:template>

    <xsl:template name="for_loop">
        <xsl:param name="i" />
        <xsl:param name="count" />
        <xsl:if test="$i &lt; $count">
            <tr></tr>
        </xsl:if>
        <xsl:if test="$i &lt; $count">
            <xsl:call-template name="for_loop">
                <xsl:with-param name="i">
                    <xsl:value-of select="$i + 1" />
                </xsl:with-param>
                <xsl:with-param name="count">
                    <xsl:value-of select="$count" />
                </xsl:with-param>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>
dogbane
  • 266,786
  • 75
  • 396
  • 414