Search code examples
javaxsltxpathxercesxalan

Why can't I use values of nodes I retrieve by using exsl:node-set/set:distinct in an XPath-Expression?


In a xslt-stylesheet I'm using the methods exsl:node-set and set:distinct to access and filter unique nodes from a variable that contains a result tree fragment. I can write the values of these nodes into my output file, example:

<xsl:variable name="myNodes">
  <xsl:call-template name="getNodes"/>
</xsl:variable>

<xsl:for-each select="set:distinct(exsl:node-set($myNodes)/key)">
  <xsl:value-of select="."/>
</xsl:for-each>

The values of the keys are written in the output, just as expected. However, if I try to use the values in an XPath expression, it fails:

<xsl:for-each select="set:distinct(exsl:node-set($myNodes)/key)">
  <xsl:variable name="result" select="/tree//somenode[@key = current()]"/>
  <xsl:value-of select="$result"/>
</xsl:for-each>

Now, the output is empty, whereas I know that there is a "somenode" in my input-xml that should be selected by the XPath expression and it's value is not empty.

Now my question is: Why does this happen?

I'm using Java 1.6, Xerces 2.7 and Xalan 2.7.

update: as requested, some data for the example: xml doc contains:

<tree>
  <somenode key="123"/>
  <num>123</num>
  <num>0815</num>
</tree>

the getNodes template:

<xsl:template name="getNodes">
  <xsl:for-each select="/tree/num">
    <xsl:element name="key">
      <xsl:value-of select="."/>
    </xsl:element>
  </xsl:for-each>
</xsl:template>

Solution

  • Here is a transformation that does something close to what you want:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:set="http://exslt.org/sets"
     xmlns:exsl="http://exslt.org/common"
     >
     <xsl:output omit-xml-declaration="yes"/>
    
     <xsl:template match="/">
      <xsl:variable name="myNodes">
        <xsl:call-template name="getNodes"/>
      </xsl:variable>
    
    
      <xsl:variable name="vDoc" select="/"/>
    
      <xsl:for-each select="set:distinct(exsl:node-set($myNodes)/key)">
        <xsl:variable name="result" select="$vDoc/tree//somenode[@key = current()]"/>
        <xsl:copy-of select="$result"/>
      </xsl:for-each>
     </xsl:template>
    
     <xsl:template name="getNodes">
      <xsl:for-each select="/tree/num">
        <xsl:element name="key">
          <xsl:value-of select="."/>
        </xsl:element>
      </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <tree>
      <somenode key="123"/>
      <num>123</num>
      <num>0815</num>
    </tree>
    

    the wanted result is produced:

    <somenode key="123"/>
    

    Do note:

    1. The source XML document cannot direstly be accessed inside the <xsl:for-each>, because this instruction sets the current node to a node in another document -- the temporary tree created by exsl:node-set().

    2. For this reason we capture the source XML document in a variable $vDoc. We access the source XML document inside the <xsl:for-each> via this variable.

    3. The element <somenode key="123"/> has no text-node descendents and hence no string value. Using <xsl:value-of> on it will not produce any output. This is why we use <xsl:copy-of> here -- it copies the complete element and we see the result.