1

I want to repeat the following lines in an XML document n times, n being set in the variable $n

<Cell ss:StyleID="s22">
    <Data ss:Type="String">WSCEAllergens[i]</Data>
</Cell>

and rather than writing clumsy cascades like

<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[0]</Data>
</Cell>
<xsl:if test="$n &gt; 1>
  <Cell ss:StyleID="s22">
    <Data ss:Type="String">WSCEAllergens[1]</Data>
  </Cell>
    <xsl:if test="$n &gt; 2>
      <Cell ss:StyleID="s22">
        <Data ss:Type="String">WSCEAllergens[2]</Data>
      </Cell>
    .
    .
    .
    </xsl:if>
  </xsl:if>

I'd like to solve this with an elegant template, but I have no idea how to iteratively glue XML and text strings together to get something like this:

n=3

<Cell ss:StyleID="s22">
    <Data ss:Type="String">WSCEAllergens[0]</Data>
</Cell>
<Cell ss:StyleID="s22">
    <Data ss:Type="String">WSCEAllergens[1]</Data>
</Cell>
<Cell ss:StyleID="s22">
    <Data ss:Type="String">WSCEAllergens[2]</Data>
</Cell>

I have applied Daniel's code to my XSLT with this template

<xsl:template name="repeater">
  <xsl:param name="string"/>
  <xsl:param name="n"/>
  <xsl:param name="count" select="0"/>
    <xsl:value-of select="normalize-space(
        concat(substring-before($string,'['),
        '[',$count,']',
        substring-after($string,']')))" disable-output-escaping="yes"/>
    <xsl:if test="$n - 1 > $count">
      <xsl:call-template name="copyXML">
        <xsl:with-param name="count" select="$count+1"/>
        <xsl:with-param name="string" select="$string"/>
        <xsl:with-param name="n" select="$n"/>
      </xsl:call-template>
    </xsl:if>
</xsl:template>

and with the following call

<xsl:call-template name="repeater">
  <xsl:with-param name="n" select="$nAllergens"/>
  <xsl:with-param name="string">
    <![CDATA[
      <Cell ss:StyleID="s22"><Data ss:Type="String">WSCEAllergens[i]</Data></Cell>
    ]]>
  </xsl:with-param>
  </xsl:call-template>

$nAllergens being 3, I get back

<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[0]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[1]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[2]</Data>
</Cell>

That's good. But what if I want to get

<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[0]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[0]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[1]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[1]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[2]</Data>
</Cell>
<Cell ss:StyleID="s22">
  <Data ss:Type="String">WSCEAllergens[2]</Data>
</Cell>

?

I have a) added a second line to my template b) changed the concat-call to concat(substring-before($string,'['), '[',$count,']',substring-after($string,']'), substring-before($string,'['), '[',$count,']',substring-after($string,']'))

Both ways I only get WSCEAllergens[0] WSCEAllergens[0] WSCEAllergens[1] WSCEAllergens[2]

3
  • 2
    What version of XSLT? Commented Dec 10, 2014 at 21:19
  • Your new requirement is not clear. What are the input parameters here? Is it repeat 3x2 times, or did you duplicate the original string or what? Commented Dec 15, 2014 at 9:43
  • The template shall return the CDATA string n x 2 times for i = 0 to n. Commented Dec 15, 2014 at 10:06

2 Answers 2

1

The template shall return the CDATA string n x 2 times for i = 0 to n.

Well, that's fairly trivial:

<xsl:template name="repeat">
    <xsl:param name="string"/>
    <xsl:param name="n"/>
    <xsl:param name="i" select="0"/>
    <xsl:if test="2 * $n > $i">
        <xsl:value-of select="substring-before($string,'[i]')" disable-output-escaping="yes"/>
        <xsl:value-of select="concat('[', floor($i div 2), ']')"/>
        <xsl:value-of select="substring-after($string,'[i]')" disable-output-escaping="yes"/>
        <xsl:call-template name="repeat">
            <xsl:with-param name="string" select="$string"/>
            <xsl:with-param name="n" select="$n"/>
            <xsl:with-param name="i" select="$i + 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>

Or, if you prefer:

<xsl:template name="repeat">
    <xsl:param name="string"/>
    <xsl:param name="n"/>
    <xsl:param name="i" select="0"/>
    <xsl:if test="$n > $i">
        <xsl:variable name="output">
            <xsl:value-of select="substring-before($string,'[i]')"/>
            <xsl:value-of select="concat('[', $i, ']')"/>
            <xsl:value-of select="substring-after($string,'[i]')"/>
        </xsl:variable> 
        <xsl:value-of select="concat($output, $output)" disable-output-escaping="yes"/>
        <xsl:call-template name="repeat">
            <xsl:with-param name="string" select="$string"/>
            <xsl:with-param name="n" select="$n"/>
            <xsl:with-param name="i" select="$i + 1"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>
Sign up to request clarification or add additional context in comments.

Comments

0

Here's one option. In the example, the element generate will be replaced by the string $n number of times. I'm using DOE (disable-output-escaping) so the string is actual XML in the output. If it's supposed to be a string, just remove the DOE attribute.

XML Input

<doc>
    <generate/>
</doc>

XSLT 1.0

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

    <xsl:param name="n" select="3"/>
    <xsl:param name="baseString">
        <![CDATA[
        <Cell ss:StyleID="s22"><Data ss:Type="String">WSCEAllergens[i]</Data></Cell>
        ]]>
    </xsl:param>

    <!--Identity transform-->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>    

    <xsl:template match="generate">
        <xsl:call-template name="copyXML">
            <xsl:with-param name="string" select="$baseString"/>
        </xsl:call-template>
    </xsl:template>

    <xsl:template name="copyXML">
        <xsl:param name="string"/>
        <xsl:param name="count" select="0"/>
        <xsl:value-of select="normalize-space(
            concat(substring-before($string,'['),
            '[',$count,']',
            substring-after($string,']')))" disable-output-escaping="yes"/>
        <xsl:if test="$n - 1 > $count">
            <xsl:call-template name="copyXML">
                <xsl:with-param name="count" select="$count+1"/>
                <xsl:with-param name="string" select="$string"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>

XML Output

(Formatted after transform. Actual output would be on a single line because of the normalize-space().)

<doc>
    <Cell ss:StyleID="s22">
        <Data ss:Type="String">WSCEAllergens[0]</Data>
    </Cell>
    <Cell ss:StyleID="s22">
        <Data ss:Type="String">WSCEAllergens[1]</Data>
    </Cell>
    <Cell ss:StyleID="s22">
        <Data ss:Type="String">WSCEAllergens[2]</Data>
    </Cell>
</doc>

Also note that the namespace prefix ss should be bound to a namespace in the output.

5 Comments

I'm using XSLT 1.0 and applying your code to an XML document containing only the data from my example works flawlessly. :-)
But I'm quite lost how to apply this to my project, because it shall only add certain elements with text inside certain elements and I haven't understood yet how to let your xslt know what to copy and where to put your xslt inside my xslt document.
I think, my question was not precise: The string I want to repeat is not part of the xml that the stylesheet has to transform, it's part of my xslt. So I'm looking for a template that I can hand this <Cell><DATA>Any String [i]</DATA></CELL> as parameter and get it back n times with i from 0 to n.
@FREDV - I think I just read the question wrong. I've updated my answer; hopefully it helps. Also note that you could use an extension in XSLT 1.0, like exslt, to treat the string as a node so you could do an apply-templates approach like my earlier answer.
Thanks, that helped a lot but I'm not finished with my problem yet and have edited my question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.