Reputation: 111616
Imagine an XML tree with nodes in several depths
<?xml version="1.0" encoding="UTF-8"?>
<root>
<ELEM2>
<ELEM3/>
</ELEM2>
<ELEM2>
<ELEM3/>
</ELEM2>
<ELEM2>
<ELEM3/>
</ELEM2>
</root>
I would like to know the unique position of each element in the entire tree.
Desired output:
<?xml version="1.0"?>
<root position="1"/>
<ELEM2 position="2"/>
<ELEM3 position="3"/>
<ELEM2 position="4"/>
<ELEM3 position="5"/>
<ELEM2 position="6"/>
<ELEM3 position="7"/>
So, basically, I'd like to see the position in the list of all elements, and use that index to find it.
Attempt 1: position()
: I tried using position()
like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="position">
<xsl:value-of select="position()"/>
</xsl:attribute>
</xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
which gave me duplicated position attributes:
<?xml version="1.0"?>
<root position="1"/>
<ELEM2 position="2"/>
<ELEM3 position="2"/>
<ELEM2 position="4"/>
<ELEM3 position="2"/>
<ELEM2 position="6"/>
<ELEM3 position="2"/>
So, I cannot use position()
in the current nodeset, since that position is limited to the current branch in the tree.
Attempt 2: preceding::
:
Then I looked at using preceding::
like so:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="position">
<xsl:value-of select="count(preceding::*)"/>
</xsl:attribute>
</xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:template>
</xsl:stylesheet>
But unfortunately I got this:
<?xml version="1.0"?>
<root position="0"/>
<ELEM2 position="0"/>
<ELEM3 position="0"/>
<ELEM2 position="2"/>
<ELEM3 position="2"/>
<ELEM2 position="4"/>
<ELEM3 position="4"/>
So, preceding::
: counts siblings on the axis of the parents sibling and up to the root.
Attempt 3: preceding-sibling::
:
Not even worth it. This will only look through position of other sibling elements, which mean I will get same positions for each of the ELEM3 elements.
Attempt 4: generate-id()
:
This was a slightly different approach if we relax the limitation that the @position attribute reflects a node number. This doesn't matter in my setup as long as the generated value is unique.
This one will generate unique @position attributes. Unfortunately the ids are only guaranteed to be same within the same run. This means I cannot use this for tests and our CI/test setup.
Attempt 5: Find current node in given list?
I am thinking if I can in some way provide the list of elements via an XPATH like so: *
or ELEM2|ELEM3
. Now if I could then take the current()
node and find it's position in the list.
Is this possible?
Limitations:
Upvotes: 1
Views: 1246
Reputation: 243449
Or simply:
count(ancestor::*) + count(preceding::*) +1
Here is a complete transformation (just 10 lines):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="position" select="count(ancestor::*) + count(preceding::*) +1"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<root>
<ELEM2>
<ELEM3/>
</ELEM2>
<ELEM2>
<ELEM3/>
</ELEM2>
<ELEM2>
<ELEM3/>
</ELEM2>
</root>
the wanted, correct result is produced:
<root position="1">
<ELEM2 position="2">
<ELEM3 position="3"/>
</ELEM2>
<ELEM2 position="4">
<ELEM3 position="5"/>
</ELEM2>
<ELEM2 position="6">
<ELEM3 position="7"/>
</ELEM2>
</root>
Upvotes: 1
Reputation: 167506
Use <xsl:number count="*" level="any"/>
:
https://xsltfiddle.liberty-development.net/gVhEaiH
Upvotes: 2