Reputation: 38
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
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
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