Dan
Dan

Reputation: 5986

XSLT dynamic node name in loop

Does anyone know if it's possible to loop through a template and pull out node values based on an iterating number. So for example, I have the following XML strucutre:

<nodes>
  <node>
    <label1>Label a</label1>
    <value1>Value a</value1>
    <label2>Label b</label2>
    <value2>Value b</value2>
    <label3>Label c</label3>
    <value3>Value c</value3>
    etc...
  </node>
</nodes>

There are always 20 label/value pairs of data. I want to output these via XSLT in a table. By looping through a template 20 times (unless there's a better way).

The code I have below works, but it won't accept a dynamic number when outputting the values (e.g.

<xsl:value-of select="$node/label$index"/>

)

Here's the code so far:

<xsl:param name="currentPage"/>
<xsl:variable name="numberOfPairs" select="20" />

<xsl:template match="/">
  <table>
    <xsl:call-template name="outputData">
      <xsl:with-param name="node" select="$currentPage" />
    </xsl:call-template>
  </table>
</xsl:template>

<xsl:template name="outputData">
  <xsl:param name="node" select="." />
  <xsl:param name="index" select="1" />
  <tr>
    <td><xsl:value-of select="$node/label1"/></td>
    <td><xsl:value-of select="$node/value1"/></td>
  </tr>
  <xsl:if test="$index &lt;= $numberOfPairs">
    <xsl:call-template name="outputData">                         
      <xsl:with-param name="node" select="$node" />
      <xsl:with-param name="index" select="$index + 1" />                             
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Can anyone suggest a solution to this?

Upvotes: 4

Views: 6045

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

This transformation:

<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:param name="pLimit" select="20"/>

 <xsl:template match="/">
   <xsl:apply-templates select=
    "/*/*/*[starts-with(name(), 'label')
          and
            not(substring-after(name(), 'label') > $pLimit)
           ]"/>
 </xsl:template>

 <xsl:template match="*[starts-with(name(), 'label')]">
  <tr>
   <td><xsl:value-of select="."/></td>
   <td><xsl:value-of select="following-sibling::*[1]"/></td>
  </tr>
 </xsl:template>
</xsl:stylesheet>

when applied on this XML document (similar to the provided, but with 21 label-value pairs):

<nodes>
    <node>
        <label1>Label a</label1>
        <value1>Value a</value1>
        <label2>Label b</label2>
        <value2>Value b</value2>
        <label3>Label c</label3>
        <value3>Value c</value3>
        <label4>Label d</label4>
        <value4>Value d</value4>
        <label5>Label e</label5>
        <value5>Value e</value5>
        <label6>Label f</label6>
        <value6>Value f</value6>
        <label7>Label g</label7>
        <value7>Value g</value7>
        <label8>Label h</label8>
        <value8>Value h</value8>
        <label9>Label i</label9>
        <value9>Value i</value9>
        <label10>Label j</label10>
        <value10>Value j</value10>
        <label11>Label k</label11>
        <value11>Value k</value11>
        <label12>Label l</label12>
        <value12>Value l</value12>
        <label13>Label m</label13>
        <value13>Value m</value13>
        <label14>Label n</label14>
        <value14>Value n</value14>
        <label15>Label o</label15>
        <value15>Value o</value15>
        <label16>Label p</label16>
        <value16>Value p</value16>
        <label17>Label q</label17>
        <value17>Value q</value17>
        <label18>Label r</label18>
        <value18>Value r</value18>
        <label19>Label s</label19>
        <value19>Value s</value19>
        <label20>Label t</label20>
        <value20>Value t</value20>
        <label21>Label u</label21>
        <value21>Value u</value21>
    </node>
</nodes>

produces the wanted, correct result:

<tr>
   <td>Label a</td>
   <td>Value a</td>
</tr>
<tr>
   <td>Label b</td>
   <td>Value b</td>
</tr>
<tr>
   <td>Label c</td>
   <td>Value c</td>
</tr>
<tr>
   <td>Label d</td>
   <td>Value d</td>
</tr>
<tr>
   <td>Label e</td>
   <td>Value e</td>
</tr>
<tr>
   <td>Label f</td>
   <td>Value f</td>
</tr>
<tr>
   <td>Label g</td>
   <td>Value g</td>
</tr>
<tr>
   <td>Label h</td>
   <td>Value h</td>
</tr>
<tr>
   <td>Label i</td>
   <td>Value i</td>
</tr>
<tr>
   <td>Label j</td>
   <td>Value j</td>
</tr>
<tr>
   <td>Label k</td>
   <td>Value k</td>
</tr>
<tr>
   <td>Label l</td>
   <td>Value l</td>
</tr>
<tr>
   <td>Label m</td>
   <td>Value m</td>
</tr>
<tr>
   <td>Label n</td>
   <td>Value n</td>
</tr>
<tr>
   <td>Label o</td>
   <td>Value o</td>
</tr>
<tr>
   <td>Label p</td>
   <td>Value p</td>
</tr>
<tr>
   <td>Label q</td>
   <td>Value q</td>
</tr>
<tr>
   <td>Label r</td>
   <td>Value r</td>
</tr>
<tr>
   <td>Label s</td>
   <td>Value s</td>
</tr>
<tr>
   <td>Label t</td>
   <td>Value t</td>
</tr>

Explanation:

  1. Using the standard XPath functions name(), starts-with() and substring-after()

  2. The maximum number of pairs to display is provided in the global (external) parameter named pLimit.

  3. The core of the solution is applying templates exactly on the set of Labelxx elements that we want to display. These are any elements at depth 3 whose name starts with the string "label" and where the remaining part of the name that follows the starting string "label" is a number that is not greater than the specified limit $pLimit.

Upvotes: 2

Kirill Polishchuk
Kirill Polishchuk

Reputation: 56162

<xsl:output method="xml" indent="yes" />

<xsl:template match="/">        
    <table>
        <xsl:apply-templates select="nodes/node/*[starts-with(name(), 'label')]"/>
    </table>
</xsl:template>

<xsl:template match="*">

    <xsl:variable name="index" select="substring(name(), 6)"/>

    <tr>
        <td>
            <xsl:value-of select="."/>
        </td>
        <td>
            <xsl:value-of select="following-sibling::*[name() 
                          = concat('value', $index)]"/>
        </td>
    </tr>
</xsl:template>

Output:

<table>
  <tr>
    <td>Label a</td>
    <td>Value a</td>
  </tr>
  <tr>
    <td>Label b</td>
    <td>Value b</td>
  </tr>
  <tr>
    <td>Label c</td>
    <td>Value c</td>
  </tr>
</table>

Upvotes: 3

Related Questions