views:

38

answers:

2

Hello there.

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>
+4  A: 

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.

Dimitre Novatchev
Thanks! I thought the expression "/" would always let me access the root node of the xml document, didn't know this behaviour could be changed by working with a temporary tree.
xor_eq
@Dimitre: +1 for rigth explanation of the problem.
Alejandro
@xor_eq: `/` *is* selecting the root node -- of your current document. The current document is made by `<xsl:for-each>` to be the temporary tree -- so `/` is selecting the root node of the temporary tree.
Dimitre Novatchev
A: 

This stylesheet does the same you want without extensions:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:key name="NumByValue" match="num" use="."/>
    <xsl:template match="num[count(.|key('NumByValue',.)[1])=1]">
        <xsl:copy-of select="../somenode[@key=current()]"/>
    </xsl:template>
</xsl:stylesheet>

Output:

<somenode key="123" />
Alejandro
Looks a bit like the muenchian method of filtering unique elements. But my problem here is (and that's why I'm using the extension methods) that I have to compute the nodes that I'm later on extracting the nodes from. And because of that I can't use xsl:key since that's a top-level element.
xor_eq
@xor_eq: I'm reversing your logic. Instead of building a RTF with all `num` elements transformed to `key` elements, then converting this RTF to a node-set for iteration over those having unique string value, and then finaly output all `somenode` elements with such `@key`. Because unique values are in source, there is no need for two step process. For performance (avoiding node set comparison) you could have another key matching `somenode` elements and ussing `@key`.
Alejandro
@Alejandro: you're completely right, however, in this question I wrote a very simple example. And the example is also bad, as I can see now, because I didn't make it clear that those `num` values do not have to be unique. In my original code I don't have the unique values in my source, it's more like I've got every `num` element a few hundred times, so I have to extract the unique values first.
xor_eq
@xor_eq: You wrote: *I've got every num element a few hundred times, so I have to extract the unique values first*. Yes! And that's the reason I'm grouping `num` by value.
Alejandro
@Alejandro: You are, but with the help of xsl:key. I can't use this element, because those `num` elements are not in the source (that's only the case in my example), they are computed. I might as well try to create another xml-doc first, which computes all the `num` elements before I need them and then use xsl:key to group then and so on... but then I wouldn't have asked. I prefer to not having to create another xml-doc and compute the values I need on the fly. Which leaves me with the need for ext. functions because a recursive version, which I tried too, dies at a certain level of complexity
xor_eq
@xor_eq: No highway has one way. If you are computing this `num` elements, there could be a way to make this computed value to serve as the key. Ask another question with your input specifing the compution.
Alejandro
If I get the time, I'll try to do that.
xor_eq