0

To generate the xpath for each node in a xml file and add this path as attribute to each node, I found some help here. The xslt file should look like:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output indent="yes" encoding="UTF-8"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="*">
    <xsl:copy>
      <xsl:attribute name="xpath">
        <xsl:for-each select="ancestor-or-self::*">
          <xsl:value-of select="concat('/',local-name())"/>
          <!--Predicate is only output when needed.-->
          <xsl:if
            test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]">
            <xsl:value-of
              select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"
            />
          </xsl:if>
        </xsl:for-each>
      </xsl:attribute>
      <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="text()"/>

</xsl:stylesheet>

Now I'm interested in a more compact way using xslt 2.0. For example in the following xslt file I have two function createXPath and getXpath. The first one returns a path with node names and the second returns the corresponding number. Is it possible to combine those in smart way?

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:func="http://www.functx.com">
  <xsl:output method="xml" encoding="utf-8"/>

<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:attribute name="xpath">
            <xsl:value-of select="func:getXpath(.)"/>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

   <xsl:function name="func:createXPath" >
    <xsl:param name="pNode" as="node()"/>
    <xsl:value-of select="$pNode/ancestor-or-self::*/local-name()" separator="/"/>
  </xsl:function>

  <xsl:function name="func:getXpath">
  <xsl:param name="pNode" as="node()"/>
   <xsl:value-of select="$pNode/ancestor-or-self::*/(count(preceding-sibling::*) + 1)" separator="/" />
</xsl:function> 

</xsl:stylesheet>
0

2 Answers 2

1

Combining the two functions is rather trivial - for example, you could do:

<xsl:function name="func:path" >
    <xsl:param name="target" as="element()"/>
    <xsl:value-of select="for $step in $target/ancestor-or-self::* return concat(name($step), '[', count($step/preceding-sibling::*[name() = name($step)]) + 1, ']')" separator="/"/>
</xsl:function>

However, this method is quite inefficient, as it has to traverse the tree repeatedly. Consider instead:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<xsl:template match="*">
    <xsl:param name="path"/>
    <xsl:variable name="my-path">
        <xsl:value-of select="$path"/>
        <xsl:text>/</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>[</xsl:text>
        <xsl:value-of select="count(preceding-sibling::*[name() = name(current())]) + 1"/>
        <xsl:text>]</xsl:text>
    </xsl:variable>
    <xsl:copy>
        <xsl:attribute name="xpath">
            <xsl:value-of select="$my-path" />    
        </xsl:attribute>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates>
            <xsl:with-param name="path" select="$my-path"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

which takes advantage of XSLT's recursive processing model.

Sign up to request clarification or add additional context in comments.

Comments

1

A couple of points.

(a) There is no such thing as "the XPath of a node". Some people, when they use such a term, will mean a path like a/b/c, others will mean a[1]/b[5]/c[6], and others will mean a path that is fully namespace-qualified by using predicates at each level that test namespace-uri().

(b) XPath 3.0 provides a function path() which returns an XPath expression of this kind; it uses the EQName syntax Q{uri}local to ensure that element names are context-free.

(c) My approach to getting the kind of path you are generating would be

<xsl:function name="f:path" as="xs:string">
  <xsl:param name="e" as="element(*)"/>
  <xsl:value-of select="ancestor-or-self::*/concat(
    '/',
    name($e),
    concat('[',f:index($e),']')[. ne '[1]']
  )" separator=""/>
</xsl:function>

<xsl:function name="f:index" as="xs:integer">
  <xsl:param name="e" as="element(*)"/>
  <xsl:sequence select="count(preceding-sibling::*[name()=name($e)])+1"/>
</xsl:function>

and then

<xsl:copy>
  <xsl:attribute name="path" select="f:path(.)"/>
  ....
</xsl:copy>

2 Comments

I don't think you have tested these.
I very rarely test code samples. I think it's important that the reader should understand the code rather than just using it verbatim, and leaving the odd error helps to ensure that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.