praveen
praveen

Reputation: 57

How to use position() in XSLT for looping

I am new to XSLT and I have an XML like so;

<TerminalRoutes>
        <Segment>
            <From>
                <previous>DEN </previous>
                <current>DNV</current>
            </From>
            <From>
                <previous>LAX </previous>
                <current>LAS</current>
            </From>
        </Segment>
        <Segment>
            <To>
                <previous>ATL </previous>
                <current>ATN</current>
            </To>
            <To>
                <previous>JFK</previous>
                <current>LGA </current>
            </To>
        </Segment>
 </TerminalRoutes>

I am trying to transform the XML into HTML using XSLT and I am applying this template

<xsl:template match="TerminalRoutes">
            <tr>
                <td>
                    <b>Terminal Routes</b>
                </td>
                <td>&#160;</td>
                <td>&#160;</td>
            </tr>
            <tr>

            <td>TERMINAL ROUTES FROM</td>
                <td>
                    <xsl:value-of select="Segment/From/previous"></xsl:value-of>
                </td>
                <td>
                    <xsl:value-of select="Segment/From/current"></xsl:value-of>
                </td>
            </tr>
            <tr>
            <td>TERMINAL ROUTES TO</td>
                <td>
                    <xsl:value-of select="Segment/To/previous"></xsl:value-of>
                </td>
                <td>
                    <xsl:value-of select="Segment/To/current"></xsl:value-of>
                </td>
            </tr>
        </xsl:template>

and the resultant HTML gives me the first result set i.e the first "From" and the first "To".

What I want is to re-apply the same template, so I can capture the second result set i.e the second "From" and the second "To". I believe there is a position() function that can be used with but I am not sure how.

Upvotes: 1

Views: 6579

Answers (2)

matthias_h
matthias_h

Reputation: 11416

Just as example for how position() can be used in a for-each loop:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>

<xsl:template match="TerminalRoutes">
<table>
<xsl:for-each select="Segment/From">
<xsl:variable name="position" select="position()"/>
  <tr>
    <td>
      <b>Terminal Routes</b>
    </td>
    <td>&#160;</td>
    <td>&#160;</td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES FROM</td>
    <td>
      <xsl:value-of select="previous"></xsl:value-of>
    </td>
    <td>
      <xsl:value-of select="current"></xsl:value-of>
    </td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES TO</td>
    <td>
      <xsl:value-of select="//Segment/To[$position]/previous"></xsl:value-of>
    </td>
    <td>
      <xsl:value-of select="//Segment/To[$position]/current"></xsl:value-of>
    </td>
  </tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>

Output:

<table>
  <tr>
    <td><b>Terminal Routes</b></td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES FROM</td>
    <td>DEN </td>
    <td>DNV</td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES TO</td>
    <td>ATL </td>
    <td>ATN</td>
  </tr>
  <tr>
    <td><b>Terminal Routes</b></td>
    <td>&nbsp;</td>
    <td>&nbsp;</td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES FROM</td>
    <td>LAX </td>
    <td>LAS</td>
  </tr>
  <tr>
    <td>TERMINAL ROUTES TO</td>
    <td>JFK</td>
    <td>LGA </td>
  </tr>
</table>

position() automatically increments in the for-each loop, so select="//Segment/To[$position]/previous" will be //Segment/To[1]/previous for the first Segment/From and //Segment/To[2]/previous for the second one.
For reference: http://msdn.microsoft.com/en-US/en-en/library/ms256233%28v=vs.110%29.aspx

Upvotes: 0

JohnLBevan
JohnLBevan

Reputation: 24430

Try this:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="msxsl"

>

  <xsl:output method="html" encoding="UTF-8" indent="yes" />
  <xsl:template match="TerminalRoutes">
    <tr>
      <td>
        <b>Terminal Routes</b>
      </td>
      <td>&#160;</td>
      <td>&#160;</td>
    </tr>
    <xsl:apply-templates select=".//*" />
  </xsl:template>

  <xsl:template match="/TerminalRoutes/Segment/From">
    <tr>
      <td>TERMINAL ROUTES FROM</td>
      <td>
        <xsl:value-of select="./previous"></xsl:value-of>
      </td>
      <td>
        <xsl:value-of select="./current"></xsl:value-of>
      </td>
    </tr>
    <tr>
      <td>TERMINAL ROUTES TO</td>
      <td>
        <xsl:value-of select="../../Segment/To[1 + count(preceding-sibling::*)]/previous"></xsl:value-of>
      </td>
      <td>
        <xsl:value-of select="../../Segment/To[1 + count(preceding-sibling::*)]/current"></xsl:value-of>
      </td>
    </tr>
  </xsl:template>

  <xsl:template match="*" />

</xsl:stylesheet>

1 + count(preceding-sibling::*) gives you the index of the current FROM element compared to it's siblings by counting the number preceding it (NB: indexes start at 1 instead of 0 in XML technologies). We then use this number to specify that we want the To element with the same index as the From element we're currently matching on.

NB: Despite the above solution working, if possible I'd recommend a better approach: restructure your source XML to better group the data; i.e. put related FROM and TO values under the same parent, so that the data structure implies this relationship, making it simpler to read, validate, and work with.

e.g.

<TerminalRoutes>
  <Route>
    <From>
      <previous>DEN </previous>
      <current>DNV</current>
    </From>
    <To>
      <previous>ATL </previous>
      <current>ATN</current>
    </To>
  </Route>
  <Route>
    <From>
      <previous>LAX </previous>
      <current>LAS</current>
    </From>
    <To>
      <previous>JFK</previous>
      <current>LGA </current>
    </To>
  </Route>
</TerminalRoutes>

Upvotes: 1

Related Questions