How I can repeat an action X times with XSLT
Asked Answered
A

5

15

I've to populate a total of 20 elements with XSLT. In my XML code I have a <select> with the values, there is anyway to not to write 20 forms?

My XML:

<output>
    <select>
        <id>1</id>
        <name>One</name>
    </select>
    <select>
        <id>2</id>
        <name>Two</name>
    </select>
    <select>
        <id>3</id>
        <name>Three</name>
    </select>
    <!-- An more -->
</output>

My XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   
    <xsl:template match="/">
        <html>

            <body>
        <select name="values[]">
            <option value="0"> </option>
            <xsl:for-each select="output/select">
                <option>
                    <xsl:attribute name="value"><xsl:value-of select="id"></xsl:attribute>
                    <xsl:value-of select="name" />
                </option>
            </xsl:for-each>
        </select>
            </body>

        </html>
    </xsl:template>
</xsl:stylesheet>

Desired output:

<html>
    <body>

        <select name="values[]">
            <option value="0"> </option>
            <option value="1">One</option>
            <option value="2">Two</option>
            <option value="3">Three</option>
        </select>
        <!-- But 20 times -->   

    </body>
</html>
Anett answered 27/9, 2010 at 8:41 Comment(1)
Good question (+1). See my answer for a specific XSLT 1.0 solution and another, generic XSLT 2.0 solution.Coreycorf
L
13

First, use templates instead of for-each, then you can use a recursive template call to emulate a for loop (as seen here):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   
    <xsl:template match="/">
        <html>
        <body>

            <xsl:call-template name="selects">
               <xsl:with-param name="i">1</xsl:with-param>
               <xsl:with-param name="count">20</xsl:with-param>
            </xsl:call-template> 

        </body>    
        </html>
    </xsl:template>

    <xsl:template name="selects">
        <xsl:param name="i" />
        <xsl:param name="count" />

        <xsl:if test="$i &lt;= $count">
          <select name="values[]">
            <xsl:apply-template select="output/select" />
          </select>
        </xsl:if>

        <!--begin_: RepeatTheLoopUntilFinished-->
        <xsl:if test="$i &lt;= $count">
            <xsl:call-template name="selects">
                <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:template match="output/select">
      <option>
        <xsl:attribute name="value">
            <xsl:value-of select="id">
        </xsl:attribute>
        <xsl:value-of select="name" />
      </option>
    </xsl:template>
</xsl:stylesheet>
Litho answered 27/9, 2010 at 8:46 Comment(1)
if you count down to 0 then you don't need to pass in the $count parameterAssisi
C
5

I. XSLT 1.0 solution:

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

 <xsl:template match="/">
  <html>
    <body>
      <xsl:apply-templates select="*" mode="iter">
       <xsl:with-param name="pCount" select="20"/>
      </xsl:apply-templates>
    </body>
  </html>
 </xsl:template>

 <xsl:template match="/*" mode="iter">
  <xsl:param name="pCount" select="0"/>

  <xsl:if test="$pCount > 0">
      <select name="values[]">
      <xsl:apply-templates/>
      </select>
    <xsl:apply-templates select="." mode="iter">
      <xsl:with-param name="pCount" select="$pCount -1"/>
    </xsl:apply-templates>
  </xsl:if>
 </xsl:template>

 <xsl:template match="select">
  <option value="{id}"><xsl:value-of select="name"/></option>
 </xsl:template>
</xsl:stylesheet>

This is a specific, recursive solution.

When applied to the following XML document:

<output>
    <select>
        <id>0</id>
        <name> </name>
    </select>
    <select>
        <id>1</id>
        <name>One</name>
    </select>
    <select>
        <id>2</id>
        <name>Two</name>
    </select>
    <select>
        <id>3</id>
        <name>Three</name>
    </select>
</output>

the wanted, correct result is produced.

II. XSLT 2.0 solution using the f:repeat() function of FXSL:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:f="http://fxsl.sf.net/"
 exclude-result-prefixes="f xs"
 >
 <xsl:import href="../f/func-repeat.xsl"/>
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:variable name="vSelects" as="element()">
   <select name="values[]">
    <xsl:apply-templates select="/*/select"/>
   </select>
 </xsl:variable>

  <xsl:template match="/">
    <html>
      <body>
       <xsl:sequence select="f:repeat($vSelects, 20)"/>
      </body>
    </html>
  </xsl:template>

 <xsl:template match="select">
  <option value="{id}"><xsl:value-of select="name"/></option>
 </xsl:template>
</xsl:stylesheet>

Here we use a very generic function that will repeat its first argument N (the value of its second argument) times.

The function f:repeat() itself is very simple:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:f="http://fxsl.sf.net/"
 exclude-result-prefixes="xs f"
>

  <xsl:function name="f:repeat" as="item()+">
    <xsl:param name="pThis" as="item()"/>
    <xsl:param name="pTimes" as="xs:integer"/>

    <xsl:for-each select="1 to $pTimes">
      <xsl:sequence select="$pThis"/>
    </xsl:for-each>
  </xsl:function>
</xsl:stylesheet>
Coreycorf answered 27/9, 2010 at 13:37 Comment(2)
Hey @Dimitre Novatchev, why doesn't this solution work anymore if I use XSLT 2.0? I copy&pasted the code and just set version="2.0" and the loop does work until 8. If I set the loop to run 9 times it runs 90 times. If I set the loop to run 10 times, it runs 1 time.Solberg
@LennartWeitzenberger, You must have a broken (buggy) XSLT processor. I tried and couldn't reproduce the reported problems. I used both Saxon 6.5.3 for XSLT 1.0 and Saxon 9.1.01J for XSLT 2.0. In both cases I am getting the correct, expected results. Add the fact that for the past 12 years no one else has reported such problems, this most probably means that there is something unusual with your XSLT processor /setup.Coreycorf
T
3

Other solution with "you-will-go-to-hell-if-you-use-this-pattern":

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
        <xsl:variable name="vChilds" select="node()"/>
        <xsl:variable name="vStylesheet" select="document('')"/>
        <html>
            <body>
                <xsl:for-each select="($vStylesheet//node()|
                                       $vStylesheet//@*|
                                       $vStylesheet//namespace::*)
                                       [21 > position()]">
                    <xsl:apply-templates select="$vChilds"/>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="output">
        <select name="values[]">
            <option value="0"></option>
            <xsl:apply-templates/>
        </select>
    </xsl:template>
    <xsl:template match="select">
        <option value="{id}">
            <xsl:value-of select="name"/>
        </option>
    </xsl:template>
</xsl:stylesheet>
Tamica answered 27/9, 2010 at 14:43 Comment(0)
C
1

One way to solve this is by loading the option settings into a variable using the XPath document() function and then using a recursive template:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:variable name="options" select="document('options.xml')" />

  <xsl:template match="/">
    <html>
      <body>
        <xsl:call-template name="InsertOptions">
          <xsl:with-param name="count" select="20" />
        </xsl:call-template>
      </body>
    </html>
  </xsl:template>

  <xsl:template name="InsertOptions">
    <xsl:param name="index" select="1"/>
    <xsl:param name="count" select="1"/>

    <xsl:if test="$index &lt;= $count">
      <select name="{concat('values', count, '[]')}">
        <option value="0"> </option>
        <xsl:for-each select="$options/output/select">
          <option value="{id}"><xsl:value-of select="name" /></option>
        </xsl:for-each>
      </select>
      <xsl:call-template name="InsertOptions">
        <xsl:with-param name="index" select="$index + 1" />
        <xsl:with-param name="count" select="$count" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>
Corrigible answered 27/9, 2010 at 8:53 Comment(0)
H
0

If you have an xml structure with at least $n elements (even nested) in $structure:

<xsl:for-each select="$structure//*[position() < $n]">
   <!-- do whatever you want -->
</xsl:for-each>

Yes, it is hackish, but conceptually easier than a recursive function.

Hesperides answered 10/10, 2019 at 16:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.