Abr_7th
Abr_7th

Reputation: 17

XSLT: Repeat output of value by n times while "n" is given value of another attribute

I'm rather new to XSLT and currently looking for a solution for my problem:

I would like to output a node <artnr><xsl:value-of select="sku" /></artnr> n-times, where "n" is the value of another attribute <xsl:value-of select="qty" />. For example: "sku" has the value 12345, "qty" has the value 3. The output should look like this:

<products>
<artnr>12345</artnr>
<artnr>12345</artnr>
<artnr>12345</artnr>
</products>

So far I have prepared the following template, but it is not very efficient:

<products>
  <xsl:for-each select="items/item">
    <xsl:if test="qty = 1">
            <artnr><xsl:value-of select="sku" /></artnr>
    </xsl:if>
    <xsl:if test="qty = 2">
            <artnr><xsl:value-of select="sku" /></artnr>
            <artnr><xsl:value-of select="sku" /></artnr>
    </xsl:if>
    <!-- etc. -->
  </xsl:for-each>
</products>

To cover all realistic cases, this approach definitely isn't acceptable. This issue has been discussed here in similar cases a couple of times already, but I cannot make it work on my own example.

Thanks in advance!

Upvotes: 2

Views: 1041

Answers (2)

Daniel Haley
Daniel Haley

Reputation: 52858

Here's an example of a recursive template if you're limited to 1.0.

Thanks for the sample XML input @Valdi_Bo :-)

Note: This assumes that qty will be greater than zero. If a qty can be zero, you'll need to add a test before outputting artnr.

XML Input

<items>
    <item>
        <qty>3</qty>
        <sku>12345</sku>
    </item>
    <item>
        <qty>2</qty>
        <sku>23456</sku>
    </item>
    <item>
        <qty>1</qty>
        <sku>34567</sku>
    </item>
</items>

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:template match="items">
    <products>
      <xsl:apply-templates/>
    </products>
  </xsl:template>

  <xsl:template match="item">
    <xsl:call-template name="output_sku"/>
  </xsl:template>

  <xsl:template name="output_sku">
    <xsl:param name="qty" select="qty"/>
    <xsl:variable name="remaining_qty" select="$qty - 1"/>
    <artnr>
      <xsl:value-of select="sku"/>
    </artnr>
    <xsl:if test="$remaining_qty >= 1">
      <xsl:call-template name="output_sku">
        <xsl:with-param name="qty" select="$remaining_qty"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

Output

<products>
   <artnr>12345</artnr>
   <artnr>12345</artnr>
   <artnr>12345</artnr>
   <artnr>23456</artnr>
   <artnr>23456</artnr>
   <artnr>34567</artnr>
</products>

Fiddle: http://xsltfiddle.liberty-development.net/bnnZVQ/1

Upvotes: 0

Valdi_Bo
Valdi_Bo

Reputation: 30991

In XSLT 2.0, among variants of xsl:for-each loop there is select="1 to <final_value>".

In your case this final_value is given by qty element.

But one additional action is needed. Because inside xsl:for-each the context item is changed to the current element of the loop (in this case a number), you have to:

  • save the "outer" context (the current item element) in a variable (I called it itm),
  • use this variable as the start point of XPath expression referring to sku child element.

So the whole script can look like below:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />

  <xsl:template match="/">
    <products>
      <xsl:for-each select="items/item">
        <xsl:copy>
          <xsl:variable name="itm" select="."/>
          <xsl:for-each select="1 to qty">
            <artnr><xsl:value-of select="$itm/sku" /></artnr>
          </xsl:for-each>
        </xsl:copy>
      </xsl:for-each>
    </products>
  </xsl:template>
</xsl:transform>

For a working example, with source XML, see: http://xsltransform.net/pNEhB36

If you are forced to use XSLT 1.0, you have to use a recursive template, calling itself again and again, up to the defined depth (one of its parameters).

Edit

I wrote the above example assuming that both qty and sku are child elements. But now I realized that you actually wrote that at least qty is an attribute.

So item element can be e.g.:

<item qty="2">
  <sku>23456</sku>
</item>

Then instead of "plain" qty you should write @qty.

Upvotes: 1

Related Questions