Difficulty: ★☆☆ (easy)
Keywords: XPath, debugging

Problem

You need the path to your current node—the XPath—to output it as debugging information.

Solution

The DocBook XSL stylesheet offers the xpath.location template for this purpose. To output the current XPath, use the following procedure:

  1. Import the xpath.location template from the DocBook XSL stylesheets in your customization layer:

    <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/lib/lib.xsl"/>
  2. Add the following code in your template:

    <xsl:message>
      <xsl:text>Current XPath: </xsl:text>
      <xsl:call-template name="xpath.location"/>
    </xsl:message>

If you need the XPath for a different node, extend the previous code:

<xsl:message>
  <xsl:text>Current XPath: </xsl:text>
  <xsl:call-template name="xpath.location">
    <xsl:with-param name="node" select="YOUR_XPath_Expression"/>
  </xsl:call-template>
</xsl:message>

Discussion

Currently, the template in lib/lib.xsl is limited: it walks from the current node up to the root node and outputs just the element name. No namespace prefixes, no predicates. If you have a DocBook document with different namespaces, the original version does not help.

In Example 2.3, “Namespace-aware Output of an XPath”, the revised template recognizes namespaces and counts elements on the sibling axis.

Example 2.3. Namespace-aware Output of an XPath
<xsl:stylesheet version="1.0"
  xmlns:n="urn:x-toms:ns:namespaces"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  exclude-result-prefixes="n">
  <namespaces xmlns="urn:x-toms:ns:namespaces">
    <ns prefix="d">http://docbook.org/ns/docbook</ns>
    <ns prefix="xi">http://www.w3.org/2001/XInclude</ns>
    <ns prefix="rdf">http://www.w3.org/1999/02/22-rdf-syntax-ns#</ns>
    <ns prefix="cc">http://creativecommons.org/ns#</ns>
    <ns prefix="svg">http://www.w3.org/2000/svg</ns>
  </namespaces>
  
  <xsl:variable name="nsnodes" select="document('')//n:namespaces/n:ns"/>
  
  <xsl:template name="xpath.location">
    <xsl:param name="node" select="."/>
    <xsl:param name="path" select="''"/>
    <xsl:param name="method">prefix</xsl:param>
  
    <xsl:variable name="next.path">
      <xsl:choose>
        <xsl:when test="$method = 'prefix' and $nsnodes[namespace-uri($node)]">
          <xsl:value-of select="concat($nsnodes[namespace-uri($node) = .]/@prefix, ':')"/>
        </xsl:when>
        <xsl:when test="$method = 'clark' and namespace-uri($node) != ''">
          <xsl:value-of select="concat('{', namespace-uri($node), '}')"/>
        </xsl:when>
      </xsl:choose>  
      <xsl:value-of select="local-name($node)"/>      
      <xsl:if test="generate-id($node) != generate-id(/*) and
                    count(../*[local-name()=local-name(current()) and
                             namespace-uri()=namespace-uri(current())]) > 1">
        <xsl:text>[</xsl:text>
        <xsl:value-of select="count(preceding-sibling::*
                          [local-name()=local-name(current()) and
                           namespace-uri()=namespace-uri(current())]) + 1"/>
      <xsl:text>]</xsl:text>
    </xsl:if>
      <xsl:if test="$path != ''">
        <xsl:text>/</xsl:text>
      </xsl:if>
      <xsl:value-of select="$path"/>
    </xsl:variable>
    
    <xsl:choose>
      <xsl:when test="$node/parent::*">
        <xsl:call-template name="xpath.location">
          <xsl:with-param name="node" select="$node/parent::*"/>
          <xsl:with-param name="path" select="$next.path"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat('/', $next.path)"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  
</xsl:stylesheet>

1

Contains the list of namespaces and their prefixes

2

Create a node set of all namespace to prefix mappings

3

Defines the method how prefixes are displayed. You can choose between prefix and the Clark notation. The latter adds the namespace in curly braces before the element name, but makes the whole XPath very verbose.

4

Choose between the prefixed or Clark notation

5

Checks, if we are not at the root node and there is more than one element with the same name and the same namespace. If this is true, we output a predicate where the current number is enclosed in square brackets.

The template can output the XPath in two different notations:

Prefix (method="prefix", default)

Namespace nodes are abbreviated with a prefix. The prefix is definied in the namespaces element. A XPath to the first chapter inside a book looks like this:

/d:book/d:chapter[1]
Clark (method="clark")

Namespace nodes are printed with the full namespace URL. The namespace URL is definied in the namespaces element. An XPath to the first chapter inside a book looks like this:

/{http://docbook.org/ns/docbook}book/{http://docbook.org/ns/docbook}chapter[1]

Use Clark Notation For Debugging Purposes Only

The above notation is called the Clark notation, after JamesClark, the editor of the XSLT 1.0 and XPath 1.0 specifications. However, this notation cannot be used in XSLT as it is not officially supported. Only use the prefixed version in this case. The Clark notation is just useful for debugging purposes when you want or need the full namespace URLs.

Be aware, the previous template is slower than the original version (it has to count a little bit more). For debugging purpose it is probably fine.

See Also


Project@GitHubIssue#7