5

I have found a case where xsl:key seems not to be working.
I am using XSLT 1 with Xalan (compiled) and this is what is happening:

1.- This works: key named test1 works fine:

<xsl:variable name="promosRTF">
  <xsl:copy-of select="promo[@valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />

<!-- now the key: when defined and used here it works fine: -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="key('test1','anytext')/*">
  loop through elements in key... ok, works fine
</xsl:for-each>

<xsl:for-each select="$promos/*">
  ..loop through elements in variable $promos ...it is not empty so the loop iterates several times
</xsl:for-each>

2.- This doesn't work: key test1 is now defined and used (which is the important point, I guess) inside a loop that iterates through elements obtained with xalan:nodeset

<xsl:variable name="promosRTF">
  <xsl:copy-of select="promo[@valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />

<xsl:for-each select="$promos/*">
  <!-- now the key: when defined and used (or just used) inside this for-each loop it does nothing: -->
  <xsl:key name="test1" match="//promo" use="'anytext'" />
  <xsl:for-each select="key('test1','anytext')/*">
    loop through elements in key... NOTHING HERE, it does not work :(
  </xsl:for-each>

  ..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>

Does anybody know what is happening? Notice that variable $promos is not empty, so the loop really iterates, it is the key used inside it which does nothing.

Thank you very much in advance.

PS: after Martin's answer I post this alternative code, which doesn't work either:

<xsl:variable name="promosRTF">
  <xsl:copy-of select="promo[@valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />

<!-- now the key: defined as a first level element: -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="$promos/*">
  <!-- but used inside this for-each loop it does nothing: -->
  <xsl:for-each select="key('test1','anytext')/*">
    loop through elements in key... NOTHING HERE, it does not work :(
  </xsl:for-each>

  ..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>

Solution: In Martin's comments was the key to the problem: nodes in a result tree fragment are treated as nodes in a different document.
Martin pointed the workaround too: In XSLT 1.0 you need to change the context node using a for-each and then call key inside the for-each. This code will work then:

<xsl:variable name="promosRTF">
  <xsl:copy-of select="promo[@valid='true']"/>
</xsl:variable>
<xsl:variable name="promos" select="xalan:nodeset($promosRTF)" />

<!-- now the key -->
<xsl:key name="test1" match="//promo" use="'anytext'" />
<xsl:for-each select="$promos/*">
  <!-- inside this for-each we are in a different *document*, so we must go back: -->
  <xsl:for-each select="/">
    <xsl:for-each select="key('test1','anytext')/*">
      loop through elements in key... and now IT WORKS, thanks Martin :)
    </xsl:for-each>
  </xsl:for-each>

  ..loop through elements in variable $promos ...it is not empty so iterates several times
</xsl:for-each>
Fernando
  • 1,013
  • 2
  • 10
  • 23

1 Answers1

2

I am surprised that you don't get an error by putting the xsl:key into the for-each. In http://www.w3.org/TR/xslt#key you can see that xsl:key is defined as a <!-- Category: top-level-element --> top level element so you need to put it as a child of xsl:stylesheet or xsl:transform.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Thank you for pointing that, Martin. I begun my code defining the key as a child of xsl:stylesheet, and trying several options ended up with de code above, which doesn't throws any error but does nothing. If I define the key as a first level element but I use it inside the loop through the xalan:nodeset generated elements it does nothing either. – Fernando Aug 06 '13 at 09:49
  • 3
    The only other potential problem in your code is that keys are built per document/tree so nodes in a result tree fragment (converted to a node-set) are treated as nodes in a different document. In XSLT 2.0 the `key` function takes a third argument to allow you to pass in the document node (or more generally root of the subtree) you want to search. In XSLT 1.0 you need to change the context node using a `for-each` and then call `key` inside the `for-each`. And that means usually outside the `for-each` that changes the context node you first need to store the other document in a variable. – Martin Honnen Aug 06 '13 at 10:00
  • ... I was suspecting something like that. I will make some test before accepting your answer, Martin, thank you very much. – Fernando Aug 06 '13 at 10:07
  • Hi Martin, that was it. If I call key inside it works as expected. Thanks again. (I accepted the answer although the solution was in the comment) – Fernando Aug 06 '13 at 10:41