Re: [xsl] concatenated key

Subject: Re: [xsl] concatenated key
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 23 Apr 2003 12:41:21 +0100
DaveP wrote:
>> Given a node, I'm trying to identify/pull out those nodes in the
>> document having the same set of identically named child nodes. For
>> example, given the node Alpha in the following, I'd like to pull
>> out the "Alpha2" node.
>> 
>> <node name="Alpha">
>>   <node name="a"/>
>>   <node name="b"/>
>> </node>
>> <node name="roman">
>>   <node name="i"/>
>>   <node name="ii"/>
>> </node>
>> <node name="Alpha2">
>>   <node name="a"/>
>>   <node name="b"/>
>> </node>
>
> Beating Jeni to it for once :-)
> In xslt 2 this is
>
> <xsl:template match="node">
>   <xsl:variable name="theseChildren" select="node/@name"/>
>   <xsl:if test="some $x in ../node/node/@name satisfies ($x =
> $theseChildren)">
>       <xsl:variable name="this" select="generate-id()"/>
>       (I am <xsl:value-of select="@name"/>)
>     <xsl:for-each select="../node">
>       <xsl:if test="(node/@name = $theseChildren ) and not(generate-id() =
> $this)">
>         Match at         <xsl:value-of select="position()"/>  <br />
>       </xsl:if>
>   </xsl:for-each>
> </xsl:if>
> </xsl:template>

I don't think that works. The test "node/@name = $theseChildren" tests
whether *any* of the name attributes within the current node is the
same as *any* of the name attributes in $theseChildren (just as it
does in XSLT 1.0). So if you had:

<node name="Alpha">
  <node name="a" />
  ...
  <node name="i" />
</node>
<node name="roman">
  <node name="i" />
  <node name="ii" />
</node>

then the above code would say that "roman" was the same as "Alpha"
because they both contain <node name="i" />.

Also note that you don't need to test generate-id() with XPath 2.0 --
you can use the 'is' and 'isnot' operators. Plus you can use "except"
to create a sequence of nodes that doesn't include a node you know.

I think that the best way to approach this problem in XSLT 2.0 would
be to use the string-join() function to concatenate the values of the
name attributes together and then use a key:

<xsl:key name="nodes" match="node[node]"
         use="string-join(node/@name, ' ')" />

<xsl:template match="node">
  <xsl:variable name="others"
    select="key('nodes', string-join(node/@name, ' ')) except ." />
  <xsl:if test="$others">
    <xsl:value-of select="@name" /> matches...
    <xsl:for-each select="$others">
      ...<xsl:value-of select="@name" />
    </xsl:for-each>
  </xsl:if>
</xsl:template>

I would be tempted to turn the "string-join(node/@name, ' ')"
expression into a function to prevent myself from making a mistake by
using a different separator or something in the two places where it's
used.

Cheers,

Jeni

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread