xsl-list
[Top] [All Lists]

Re: Getting the XPath of a node

2002-09-04 02:44:03
On Wednesday 04 September 2002 02:03, Dennis wrote:
Say if I have following XML:
<Person id="12345">
  <Name>Dennis</Name>
  <Company>Netscape</Company>
  <Address>Mountain View</Address>
  <Email>dennis(_at_)netscape(_dot_)com</Email>
</Person>

----The XSL to print XPath---
<xsl:template match="Company">
//Print the XPath of Company as /Person/Company
</xsl:template>
More templates corresponding to each element.

If you use Saxon exclusively, first check out 
<http://saxon.sourceforge.net/saxon6.5.2/extensions.html#path>.

If that doesn't help, then I guess the question I need to ask is, do you want 
to output an XPath that identifies a specific <Company> element (as in 
"/Person[(_at_)id='12345']/Company" or "/Person[1]/Company[1]"), or any 
<Company> 
in the whole document ("/Person/Company", as you indicate).

If your answer is the former, then there are several ways you can do it.  One 
would be to figure out a set of parameters that fully specify the location of 
the node in the document, which are specific to that particular type of node.  
For example, to identify a <Company>, you need to know Person/@id.  If you 
want to do this for every type of element, then you can just write a bunch of 
custom templates:

<xsl:template match="Company">
  Person[(_at_)id='<xsl:value-of select="parent::Person/@id"/>']/Company
</xsl:template>

and so on.

If you want a more generic solution, then you can go the route that 
saxon:path() uses, which is to simply use the position of the element and its 
parents within the document, which gives you something like 
"/Person[1]/Company[1]".  To do this, the basic idea would be to select each 
of the ancestors of the node in reverse document order.  You could probably 
mangle xsl:for-each and xsl:sort to do this, but I prefer a recursive 
template (untested):

<template match="*" name="output-xpath">
  <param name="node" select="."/>
  <text>/</text>
  <value-of select="name($node)"/>
  <text>[</text>
  <value-of select="count($node/../*[local-name() = local-name($node)
                          and namespace-uri() = namespace-uri($node)])"/>
  <text>]</text>
  <if test="..">
    <call-template name="output-xpath">
      <with-param name="node" select="$node/.."/>
    </call-template>
  </if>
</template>

[The above only works for elements (assuming it works at all, I haven't 
tested), so if you want to adapt it to text or other types of nodes, then all 
you should need is a big <xsl:choose> to special case outputting of "text()", 
processing-instruction()", etc.]

If you just want the XPath to identify *any* <Company> in the whole document, 
then you should be able to use the same template as above but just without 
the predicate stuff (the '[' + count() + ']').

HTH

-- 
Peter Davis

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list