To generate the xpath for each node in a xml file and add this path as attribute to each node, I found some help here. The xslt file should look like:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" encoding="UTF-8"/>
<xsl:strip-space elements="*"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="xpath">
<xsl:for-each select="ancestor-or-self::*">
<xsl:value-of select="concat('/',local-name())"/>
<!--Predicate is only output when needed.-->
<xsl:if
test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
<xsl:value-of
select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"
/>
</xsl:if>
</xsl:for-each>
</xsl:attribute>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
Now I'm interested in a more compact way using xslt 2.0. For example in the following xslt file I have two function createXPath and getXpath. The first one returns a path with node names and the second returns the corresponding number. Is it possible to combine those in smart way?
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:func="http://www.functx.com">
<xsl:output method="xml" encoding="utf-8"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:attribute name="xpath">
<xsl:value-of select="func:getXpath(.)"/>
</xsl:attribute>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:function name="func:createXPath" >
<xsl:param name="pNode" as="node()"/>
<xsl:value-of select="$pNode/ancestor-or-self::*/local-name()" separator="/"/>
</xsl:function>
<xsl:function name="func:getXpath">
<xsl:param name="pNode" as="node()"/>
<xsl:value-of select="$pNode/ancestor-or-self::*/(count(preceding-sibling::*) + 1)" separator="/" />
</xsl:function>
</xsl:stylesheet>
Combining the two functions is rather trivial - for example, you could do:
<xsl:function name="func:path" >
<xsl:param name="target" as="element()"/>
<xsl:value-of select="for $step in $target/ancestor-or-self::* return concat(name($step), '[', count($step/preceding-sibling::*[name() = name($step)]) + 1, ']')" separator="/"/>
</xsl:function>
However, this method is quite inefficient, as it has to traverse the tree repeatedly. Consider instead:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*">
<xsl:param name="path"/>
<xsl:variable name="my-path">
<xsl:value-of select="$path"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="name()"/>
<xsl:text>[</xsl:text>
<xsl:value-of select="count(preceding-sibling::*[name() = name(current())]) + 1"/>
<xsl:text>]</xsl:text>
</xsl:variable>
<xsl:copy>
<xsl:attribute name="xpath">
<xsl:value-of select="$my-path" />
</xsl:attribute>
<xsl:copy-of select="@*"/>
<xsl:apply-templates>
<xsl:with-param name="path" select="$my-path"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
which takes advantage of XSLT's recursive processing model.