Ankur Jain
Ankur Jain

Reputation: 38

Get current index in XPath nested iteration

I need to iterate over a nested loop of 2 levels in XML using Xpath1.0.

While iterating, it should print element value, 'Y' if it is first element of second level and current index as a counter.

I am able to accomplish one of the things, but not both at the same time. e.g. If I iterate on first loop and use position() it gives me the current index, but I am not able to identify if it is first element of second level or not. Similarly if I iterate over second loop, I dont get actual index, but get if it is first element using position()=1

I have attached a sample request and expected response.

<root>
    <level1>
        <level2>name1</level2>
        <level2>name2</level2>
    </level1>
    <level1>
        <level2>name3</level2>
        <level2>name4</level2>
        <level2>name5</level2>
    </level1>
    <level1>
        <level2>name6</level2>
        <level2>name7</level2>
    </level1>
</root>

The result should be something like this:

name1_Y_1
name2_N_2
name3_Y_3
name4_N_4
name5_N_5
name6_Y_6
name7_N_7

Here is the existing code:

<root>
    <xsl:for-each select="$Map-Data/root/level1/level2">
        <xsl:choose>
            <xsl:when test="position() = 1">
                <names>
                    <xsl:value-of select="concat(current(), '_Y_', position())"/>
                </names>
            </xsl:when>
            <xsl:otherwise>
                <names>
                    <xsl:value-of select="concat(current(), '_N_', position())"/>
                </names>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>
</root>

Upvotes: 0

Views: 5288

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116993

The reason why your approach doesn't work is that in the selection of:

<xsl:for-each select="/root/level1/level2">

you are selecting all level2 elements as a single node-set - and only one member of the entire set can be at position #1.

OTOH, if you were to split your selection like this:

<xsl:for-each select="/root/level1">
    <xsl:for-each select="level2">
        <xsl:choose>
            <xsl:when test="position() = 1">
                <names>
                    <xsl:value-of select="concat(current(), '_Y_', position())"/>
                </names>
            </xsl:when>
            <xsl:otherwise>
                <names>
                    <xsl:value-of select="concat(current(), '_N_', position())"/>
                </names>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:for-each>
</xsl:for-each>

you would be processing a separate node-set for each level1 element and thus achieve your goal of having an individual level2 element at position #1 in each batch.

However, you cannot use the same position() function to number all elements consecutively, because it will restart at each level1 change (that's how you get the Y/N thing to work). For this you must use another numbering mechanism, for example xsl:number:

<xsl:for-each select="root/level1">
    <xsl:for-each select="level2">
        <names>
            <xsl:value-of select="."/>
            <xsl:choose>
                <xsl:when test="position() = 1">_Y_</xsl:when>
                <xsl:otherwise>_N_</xsl:otherwise>
            </xsl:choose>
            <xsl:number level="any"/>
        </names>
    </xsl:for-each>
</xsl:for-each>

Upvotes: 1

Martin Honnen
Martin Honnen

Reputation: 167571

With XPath 2.0 you could do

//level2/concat(., if (not(preceding-sibling::*)) then '_Y' else '_N', '_', position())

With XPath 1.0 you will need to make sure you check the preceding-sibling::* when iterating over your //level2 and make the string concatenation in your host language.

As the sample you have now shown uses XSLT I would do

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

<xsl:output indent="yes"/>

<xsl:template match="root">
  <xsl:copy>
    <!-- <xsl:apply-templates select="$Map-Data/root/level1/level2"/> -->
    <xsl:apply-templates select="level1/level2"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="level2[not(preceding-sibling::*)]">
  <names>
    <xsl:value-of select="concat(., '_Y_', position())"/>
  </names>
</xsl:template>

<xsl:template match="level2[preceding-sibling::*]">
  <names>
    <xsl:value-of select="concat(., '_N_', position())"/>
  </names>
</xsl:template>

</xsl:stylesheet>

Upvotes: 1

Related Questions