I have an input XML:
<root>
<child>somevalue</child>
<child>othervalue</child>
</root>
And a need this output:
<root>
<child>item1_somevalue</child>
<child>item2_othervalue</child>
</root>
I'm using this template:
<xsl:template match="*[local-name()='root' and namespace-uri()='']/*[local-name()='child' and namespace-uri()='']">
<xsl:element name="{local-name()}">
<xsl:text>item</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
And getting this result:
<child>item2_somevalue</child>
<child>item4_othervalue</child>
Why is position() returning double the expected value?
I'm not worried about the root tag right now.
Must use XSLT 1.0.
The position() function can return unexpected results sometimes. At first glance, it sounds like it should return the position of the context element with respect to its parent container. So for our sample input XML:
<root>
<child>somevalue</child>
<child>othervalue</child>
</root>
... we would expect to see that the position() for the first child element would be 1, and the position() for the second child element would be 2.
But when we run our sample XSL against that code, we get the following output:
<child>item2_somevalue</child>
<child>item4_othervalue</child>
The key is that we need to pay special attention to the wording. The position() element doesn't just count elements, it counts nodes -- and that includes text nodes. So in the sample input XML above, the first child element is actually the second node under root, because it's preceded by a text node (the newline and leading whitespace). And, for the same reason, the second child element is actually the fourth node under root. So position() is returning exactly what it should.
There are a couple ways of fixing this. One is to explicitly count child elements, as jpmo22's post does. Another approach, which is more flexible, is to tell the XSL processor to ignore whitespace-only text nodes. We can do this by adding a top-level element to our XSLT code:
<xsl:strip-space elements="*"/>
We can customize the list of elements if needed, by replacing the * with a list of element names.
If we just add that strip-space directive before the template in our sample XSL code, we can get the expected result:
<child>item1_somevalue</child>
<child>item2_othervalue</child>
The XPath in the match statement is convoluted.
<xsl:template match="*[local-name()='root' and namespace-uri()='']/*[local-name()='child' and namespace-uri()='']">
In cases where element names have no prefixes, local-name and name return identical results. Our sample input XML has no prefixes, so there's no reason to use local-name. In fact, we could simply use the desired element names rather than *. Moreover, our sample input XML has no namespace declarations at all, so there's no reason to use namespace-uri.
A suggested simplification:
<xsl:template match="root/child">
The use of element is also a bit convoluted.
<xsl:element name="{local-name()}">
We already know that we're working with child elements, since that's what we just matched in this template. We could more simply just use the element name in a literal element declaration, like this:
<child>
If we wanted to genericize our code, we could also just copy over the context element:
<xsl:copy>
I found the solution. I changed the position() to count(preceding::*[local-name()='child']) + 1
XSLT:
<xsl:template match="*[local-name()='child']">
<xsl:element name="{local-name()}">
<xsl:text>item</xsl:text>
<xsl:value-of select="count(preceding::*[local-name()='child']) + 1"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
Gives the right result:
<child>item1_somevalue</child>
<child>item2_othervalue</child>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With