user2886121
user2886121

Reputation: 15

XSLT Loop within a range from XML

let's say I have following xml file:

<root>
    <header>
        <headernodes1/>
        <headernodes2/>
    </header>
    <items>
        <itemno>1</itemno>
        <itemtext>first item</itemtext>
        <loop>
            <range>
                <rangefrom>4711</rangefrom>
                <rangeto>4713</rangeto>
            </range>
            <content>content in the first loop</content>
        </loop>
        <loop>
            <range>
                <rangefrom>4715</rangefrom>
                <rangeto>4718</rangeto>
            </range>
            <content>content in the second loop</content>
        </loop>
    </items>
    <footernodes/>
</root>

The header, footer and nodes in <item> before <loop> should just be copied.

If <rangeto> not appears, then just copy the <loop> segment. Otherwise if it does appear, then replicate the <loop> segment like following example:

<loop>
    <range>
        <rangefrom>4711</rangefrom>
    </range>
    <content>content in the first loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4712</rangefrom>
    </range>
    <content>content in the first loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4713</rangefrom>
    </range>
    <content>content in the first loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4715</rangefrom>
    </range>
    <content>content in the second loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4716</rangefrom>
    </range>
    <content>content in the second loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4717</rangefrom>
    </range>
    <content>content in the second loop</content>
</loop>
<loop>
    <range>
        <rangefrom>4718</rangefrom>
    </range>
    <content>content in the second loop</content>
</loop>

Is there a solution in XSLT 1.0?

Thank you very much for your help & advice.

Greetings from Germany, L

Upvotes: 0

Views: 572

Answers (2)

Tomalak
Tomalak

Reputation: 338248

XSLT has no loops, since it does not have writable variables (i.e. there can't be a loop counter).

But it has something else: recursion!

<!-- process <loop> without <rangeto> directly as single item -->
<xsl:template match="loop[range/rangefrom and not(range/rangeto)]">
  <xsl:apply-templates select="." mode="single" />
</xsl:template>

<!-- process <loop> with <rangefrom>/<rangeto> as a succession of single items -->
<xsl:template match="loop[range/rangefrom &lt;= range/rangeto]">
  <xsl:param name="i" select="range/rangefrom" />

  <xsl:apply-templates select="." mode="single">
    <xsl:with-param name="i" select="$i" />
  </xsl:apply-templates>

  <xsl:if test="$i &lt; range/rangeto">
    <!-- recursive step: same node, incremented index -->
    <xsl:apply-templates select=".">
      <xsl:with-param name="i" select="$i + 1" />
    </xsl:apply-templates>
  </xsl:if>
</xsl:template>

<!-- single item: outputs its index as <rangefrom> --> 
<xsl:template match="loop" mode="single">
  <xsl:param name="i" select="range/rangefrom" />

  <xsl:copy>
    <range>
      <rangefrom><xsl:value-of select="$i" /></rangefrom>
    </range>
    <xsl:copy-of select="content" />
  </xsl:copy>
</xsl:template>

Modern XSLT engines recognize this as tail recursion and optimize it into iteration so you will not see a "recursion too deep" error for long loops.

Upvotes: 1

Saurav
Saurav

Reputation: 640

try out the following

  <xsl:template match="/root">
    <good>
      <header>
        <xsl:apply-templates select="header"/>
      </header>
      <footernodes>
        <xsl:apply-templates select="footernodes"/>
      </footernodes>
      <xsl:for-each select="items/loop">
        <xsl:if test="range/rangeto!=''">
          <xsl:call-template name="range">
            <xsl:with-param  name="rangedata" select="range"/>
            <xsl:with-param  name="contentdata" select="content"/>
            <xsl:with-param  name="vardata" select="range/rangefrom"/>
          </xsl:call-template>
        </xsl:if>
      </xsl:for-each>
    </good>
  </xsl:template>
  <xsl:template match="header">
    <xsl:copy-of select="."/>
  </xsl:template>
  <xsl:template match="footernodes">
    <xsl:copy-of select="."/>
  </xsl:template>
  <xsl:template name="range">
    <xsl:param name="rangedata"/>
    <xsl:param name="contentdata"/>
    <xsl:param name="vardata"/>
    <xsl:if test="$vardata &lt;=$rangedata/rangeto">
      <loop>
        <range>
          <rangefrom>
            <xsl:value-of select="$vardata"/>
          </rangefrom>
          <content>
            <xsl:value-of select="$contentdata"/>
          </content>
        </range>
      </loop>
      <xsl:call-template name="range">
        <xsl:with-param  name="rangedata" select="$rangedata"/>
        <xsl:with-param  name="contentdata" select="$contentdata"/>
        <xsl:with-param  name="vardata" select="$vardata+1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

You could use recursion to achieve what you desire

Upvotes: 0

Related Questions