3

For debugging purposes it would be handy to output the full path of the context node from within a template, is there unabbreviated xpath or function to report this ?

Example Template:

<xsl:template match="@first">
        <tr>
            <td>
                <xsl:value-of select="??WHAT TO PUT IN HERE??"/>
            </td>
        </tr>
</xsl:template>

Example (Abridged) input document:

<people>
<person>
<name first="alan">
...

The output from the template would be something like:

people / person / name / @first 

Or something similar.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
monojohnny
  • 5,894
  • 16
  • 59
  • 83
  • possible duplicate of [List every node in an XML file.](http://stackoverflow.com/questions/4051987/list-every-node-in-an-xml-file) or [HTML XPath - How to get the XPath of an element based on the value of another element?](http://stackoverflow.com/questions/5227330/html-xpath-how-to-get-the-xpath-of-an-element-based-on-the-value-of-another-ele) –  Apr 17 '11 at 20:42

2 Answers2

2

This transformation produces an XPath expression for the wanted node:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>
 <xsl:strip-space elements="*"/>

    <xsl:template match="/">
        <xsl:variable name="vNode" select=
        "/*/*[2]/*/@first"/>
        <xsl:apply-templates select="$vNode" mode="path"/>
    </xsl:template>

    <xsl:template match="*" mode="path">
        <xsl:value-of select="concat('/',name())"/>
        <xsl:variable name="vnumPrecSiblings" select=
        "count(preceding-sibling::*[name()=name(current())])"/>
        <xsl:variable name="vnumFollSiblings" select=
        "count(following-sibling::*[name()=name(current())])"/>
        <xsl:if test="$vnumPrecSiblings or $vnumFollSiblings">
            <xsl:value-of select=
            "concat('[', $vnumPrecSiblings +1, ']')"/>
        </xsl:if>
    </xsl:template>

    <xsl:template match="@*" mode="path">
     <xsl:apply-templates select="ancestor::*" mode="path"/>
     <xsl:value-of select="concat('/@', name())"/>
    </xsl:template>
</xsl:stylesheet>

when applied on the following XML document:

<people>
 <person>
  <name first="betty" last="jones"/>
 </person>
 <person>
  <name first="alan" last="smith"/>
 </person>
</people>

the wanted, correct result is produced:

/people/person[2]/name/@first
Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • This indeed works for the example XML given , thankyou. I'm now trying to get it to work with my more complicated real example, but it doesn't seem to be matching....I'll need to look through and work out why....[edit] I see it, there is a hardcoded ref to @first in there...let me change this round to suit my actual XML... – monojohnny Apr 18 '11 at 13:56
  • Yup , works great - thank you very much indeed. For my actual needs, I'll need to edit this a bit to get to work more generally, but this is a great template. Cheers! – monojohnny Apr 18 '11 at 14:08
  • Just as aside (I think somebody else also asked this somewhere else on stackoverflow) - is there a general 'xsl:pwd' or 'xsl:name-of' available which could be injected more generally to any document for debugging I wonder...I can imagine (perhaps) that the recursive nature of these things means that templates aren't 'aware' of the total depth within in the document (without having to traverse back upwards) so I guess that's why we don't have one off-the-shelf.... – monojohnny Apr 18 '11 at 14:15
  • @monojohnny: No, there isn't a special function that returns an XPath expression for a node -- maybe at least in part due to the fact that more than one such XPath expressions exist and there is no definition of a "normal form". – Dimitre Novatchev Apr 18 '11 at 14:35
1

Here's a stylesheet (of dubious value) that prints the path to every element and attribute in a document:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" />
    <xsl:strip-space elements="*" />
    <xsl:template match="*">
        <xsl:param name="pathToHere" select="''" />
        <xsl:variable name="precSiblings"
            select="count(preceding-sibling::*[name()=name(current())])" />
        <xsl:variable name="follSiblings"
            select="count(following-sibling::*[name()=name(current())])" />
        <xsl:variable name="fullPath"
            select="concat($pathToHere, '/', name(),
                substring(concat('[', $precSiblings + 1, ']'), 
                    1 div ($follSiblings or $precSiblings)))" />
        <xsl:value-of select="concat($fullPath, '&#xA;')" />
        <xsl:apply-templates select="@*|*">
            <xsl:with-param name="pathToHere" select="$fullPath" />
        </xsl:apply-templates>
    </xsl:template>
    <xsl:template match="@*">
        <xsl:param name="pathToHere" select="''" />
        <xsl:value-of select="concat($pathToHere, '/@', name(),  '&#xA;')" />
    </xsl:template>
</xsl:stylesheet>

When applied to this input:

<people>
    <person>
        <name first="betty" last="jones" />
    </person>
    <person>
        <name first="alan" last="smith" />
    </person>
    <singleElement />
</people>

Produces:

/people
/people/person[1]
/people/person[1]/name
/people/person[1]/name/@first
/people/person[1]/name/@last
/people/person[2]
/people/person[2]/name
/people/person[2]/name/@first
/people/person[2]/name/@last
/people/singleElement
Wayne
  • 59,728
  • 15
  • 131
  • 126