[xsl] key() in match pattern of xsl:key

Subject: [xsl] key() in match pattern of xsl:key
From: "Joel E. Denny" <jdenny@xxxxxxxxxxxxxxx>
Date: Fri, 9 Dec 2005 16:24:08 -0500 (EST)
Should it be possible to reference one key in the match pattern of 
another?  I did not find this usage discussed in the XSLT 1.0 
recommendation or elsewhere on the web.  Did I miss it?  I haven't quite 
deciphered the XSLT 2.0 recommendation well enough yet.  Does it address 
this usage?

Even if this usage is non-standard, is it widely supported?  Moreover, is 
there typically any restriction on the declaration order of keys?  That 
is, among the keys, is it `declaration before use' or `use before 
declaration'?  Or either way?

If this is a well understood issue, then the rest of this email is 
probably irrelevant.  In case it is not, I provide more explanation (with 
examples) for this usage and explain what I know so far.  Maybe there's a 
more standard way to accomplish what I want?  Or maybe XSLT 2.0 offers 
another approach without sacrificing efficiency?

I find that this technique makes computing set differences (and surely 
there are other applications) readable, efficient, and scalable. For 
example, given:

  <xsl:key name="keyB" match="B" use="." />

The variable AnotB stores the final set I'm after:

  <xsl:variable name="AnotB" select="//A[ not( key( 'keyB', . ) ) ]" />

However, there are times when I may want a key defined for that set for 
the sake of efficiency:

  <xsl:key name="keyAnotB" match="A[ not( key( 'keyB', . ) ) ]" use = "." />

Moreover, as my project grows, I may find that I need to use keyAnotB in 
the computation of yet another set.

I have tested this computation with libxslt 1.1.15.  Although Daniel 
Veillard has informed me that this usage is currently outside the realm of 
specified functionality, the above does work for my simple test cases.  I 
mention this only because it may offer some clue as to whether it's 
possible to implement this and implement it efficiently.

Note that libxslt builds the keys in the reverse order of the 
declarations, so it's `use before declaration'.  That is, keyAnotB must be 
declared before keyB.

If you would like to see a working test case, find the XML and XSLT at the 
end of this email.  The desired output is:

  <?xml version="1.0"?>
  $A:
    1.0: A=1, B=1, AnotB=0
    2.0: A=1, B=0, AnotB=1
    3.0: A=1, B=0, AnotB=1
  $B:
    1.0: A=1, B=1, AnotB=0
    4.0: A=0, B=1, AnotB=0
    5.0: A=0, B=1, AnotB=0
  $AnotB:
    2.0: A=1, B=0, AnotB=1
    3.0: A=1, B=0, AnotB=1

I have also been told by Kasimier Buchcik that this test case works fine 
in Stylus Studio regardless of the declaration order.

With regard to efficiency, it is interesting to note that I also tried an 
alternative approach with EXSLT's user defined functions.  For example, I 
I converted keyB to a function and called it from keyAnotB.  This also 
works under libxslt 1.1.15.  However, for a much more complex test case 
than the one below, I noticed a jump from 1sec to 25sec processing time.  
This seems reasonable since the function must iterate the document on 
every call but the key need not.

Thanks.

Joel

---------------------------------------------------------
<?xml version="1.0" encoding="iso-8859-1"?>

<test>
  <A>1.0</A>
  <A>2.0</A>
  <A>3.0</A>
  <B>1.0</B>
  <B>4.0</B>
  <B>5.0</B>
</test>
---------------------------------------------------------
<?xml version="1.0" encoding="iso-8859-1"?>

<xsl:transform
  version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
> 

<xsl:key name="keyA" match="A" use="." />
<xsl:key name="keyAnotB" match="A[ not( key( 'keyB', . ) ) ]" use = "." />
<xsl:key name="keyB" match="B" use="." />
<xsl:variable name="AnotB" select="//A[ not( key( 'keyB', . ) ) ]" />

<xsl:template match="/">

  <xsl:text>$A:&#10;</xsl:text>
  <xsl:for-each select="//A">
    <xsl:text>  </xsl:text><xsl:value-of select="." />
    <xsl:text>: A=</xsl:text>
    <xsl:value-of select="count( key( 'keyA', . ) )" />
    <xsl:text>, B=</xsl:text>
    <xsl:value-of select="count( key( 'keyB', . ) )" />
    <xsl:text>, AnotB=</xsl:text>
    <xsl:value-of select="count( key( 'keyAnotB', . ) )" />
    <xsl:text>&#10;</xsl:text>
  </xsl:for-each>

  <xsl:text>$B:&#10;</xsl:text>
  <xsl:for-each select="//B">
    <xsl:text>  </xsl:text><xsl:value-of select="." />
    <xsl:text>: A=</xsl:text>
    <xsl:value-of select="count( key( 'keyA', . ) )" />
    <xsl:text>, B=</xsl:text>
    <xsl:value-of select="count( key( 'keyB', . ) )" />
    <xsl:text>, AnotB=</xsl:text>
    <xsl:value-of select="count( key( 'keyAnotB', . ) )" />
    <xsl:text>&#10;</xsl:text>
  </xsl:for-each>

  <xsl:text>$AnotB:&#10;</xsl:text>
  <xsl:for-each select="$AnotB">
    <xsl:text>  </xsl:text><xsl:value-of select="." />
    <xsl:text>: A=</xsl:text>
    <xsl:value-of select="count( key( 'keyA', . ) )" />
    <xsl:text>, B=</xsl:text>
    <xsl:value-of select="count( key( 'keyB', . ) )" />
    <xsl:text>, AnotB=</xsl:text>
    <xsl:value-of select="count( key( 'keyAnotB', . ) )" />
    <xsl:text>&#10;</xsl:text>
  </xsl:for-each>

</xsl:template>

</xsl:transform>
---------------------------------------------------------

Current Thread