Tuesday, May 12, 2009

How to Get XPath to an XML Node

There are several XSL templates floating around that get a human readable path to a node. However, I found none that cover the case when there are multiple nodes of the same element at the same level. I came up with a complicated solution that kept the output human readable, but the solution for something machine readable is actually simple.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:attribute name="position"><xsl:call-template name="xpath.position"></xsl:call-template</xsl:attribute>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>

<xsl:template name="xpath.position">
<xsl:param name="node" select="."></xsl:param> <!-- default value is current node -->
<xsl:param name="path" select="''"></xsl:param> <!-- default value is empty string -->

<!-- prepend the current position to $path, save it as next.path -->
<xsl:variable name="next.path"><xsl:text>*[position()=</xsl:text><xsl:value-of select="$node/count(preceding-sibling::*)+1"/><xsl:text>]</xsl:text>
<xsl:if test="$path != ''">/</xsl:if>
<xsl:value-of select="$path"></xsl:value-of>
</xsl:variable>

<xsl:choose>
<xsl:when test="$node/parent::*"> <!-- when there is a parent node -->
<xsl:call-template name="xpath.position">
<xsl:with-param name="node" select="$node/parent::*"></xsl:with-param>
<xsl:with-param name="path" select="$next.path"></xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:text>/</xsl:text>
<xsl:value-of select="$next.path"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

</xsl:stylesheet>

No comments:

Labels

Blog Archive

Contributors