Get an XPath location path from a DOM node

Get an XPath location path from a DOM node

Given a node in a TclDOM DOM tree, how do I turn that into the equivalent XPath location path, like /root/child?

Answer: there are two parts to this question. Firstly, you need to walk the tree hierarchy and find the names of the nodes for the ancestors. Secondly, to identify an individual node you need to count the preceding siblings of that node.

Here's a simple Tcl procedure that just does the first half. The trick is using the "path" method to get the node ancestry. The first node will be the root node, so we can skip that.

proc getPath {node} {
    set path {}

    foreach child [lrange [$node path] 1 end] {
        append path / [$child cget -nodeName]
    }

    return $path
}

Let's give that a try:

package require dom

set d [dom::parse {<test><a><b><c/></b></a></test>}]
set mynode [dom::selectNode $d //c]
puts [getPath $mynode]
 Output ==> /test/a/b/c

The problem with this solution is that the location path is not unique. Consider the XML document:

<test><a><b><c/><c/></b></a></test>

The location path would match both 'c' elements. Here's a better solution:

proc getPath2 {node} {
    set path {}

    foreach child [lrange [$node path] 1 end] {
        append path / [$child cget -nodeName] \[

        set sibs [dom::selectNode $child preceding-sibling::[$child cget -nodeName]]

        append path [expr [llength $sibs] + 1]
        append path \]
    }

    return $path
}

Now let's try this:

package require dom

set d [dom::parse {<test><a><b><c/><c/></b></a></test>}]
set mynode [dom::selectNode $d {//c[last()]}]
puts [getPath $mynode]
 Output ==> /test[1]/a[1]/b[1]/c[2]