Re: [xsl] multi-level grouping trouble

Subject: Re: [xsl] multi-level grouping trouble
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Mon, 2 Apr 2001 08:34:32 +0100
Hi Dave,

> Yes, I've looked at Jeni's example message on multi-level grouping.
> I'm still having trouble with this, though, and would appreciate if
> someone could point out what I'm still doing incorrectly. I think
> that the first-level grouping is okay, but that the second-level is
> hosed.

Take a look at how you're calling the second level grouping. You've
set up the key as:

>   <xsl:key
>     name="i_by_su"
>     match="i"
>     use="concat(s,' ',u)"/>

Then you're using it here:

>   <xsl:template match="i">
>     <tr><td><h3><xsl:value-of select="s"/></h3></td></tr>
>     <xsl:for-each select="s[generate-id(u) =
> generate-id(key('i_by_su',u)[1])]">
>       <xsl:sort select="b"/>
>       <xsl:sort select="e"/>
>       <xsl:apply-templates select="."/>
>     </xsl:for-each>
>   </xsl:template>

So what you're saying in that second line is:

  for each s element where
    the unique ID of its child u element is equal to
    the unique ID of
      the first (i element) returned by calling the 'i_by_su' key
         with a key value being the value of the (s element's) u child

That's a little bit astray.  You're actually after *i elements*
(again).  And you want the *i elements* whose own unique ID is the
same as the first i element returned from the key when you access it
with a key value being the composite s concatenated with u (with a
space separator).

  i[generate-id(.) =
    generate-id(key('i_by_su', concat(s, ' ', u))[1])]

Note that this is exactly the same as the one for the one-level key,
except that the key name and key value have changed to match the key
name and key value that you've set up.

Now, one issue is how to get those i elements.  You're in an
'i'-matching template so an 'i' element is the current node.  You could
do simply:

  ../i[generate-id(.) =
       generate-id(key('i_by_su', concat(s, ' ', u))[1])]

But that means that the processor will work through *all* the i
elements to find those whose unique ID matches the first from the key.
You don't want that because you only want those who have the same 's'
as the current i element. So you can use the 'i_by_s' key to get
those, before filtering them to find those that are unique in the
second group:

  key('i_by_s', s)[generate-id() =
                   generate-id(key('i_by_su', concat(s, ' ', u))[1])]

You actually get very close to this in the s-matching template.

Note that if you just change the select expression of the xsl:for-each
in the i-matching template to the above, then you'll be in trouble
because you're applying templates to the current node within the
xsl:for-each, which will apply templates to an 'i' element, which
matches the same template and does the same thing again.  If you want
to iterate over the s elements next, then just select the s element
children of the i elements:

  key('i_by_s', s)[generate-id() =
                                   concat(s, ' ', u))[1])]/s

Or you could apply templates to the i elements in a different mode.
That's what I would usually do.

Whichever you do, you can then use the key in its plain form to get
*all* the i elements with a particular s and u.  For example, in an
i-matching template, you can get all the i elements with the same s
and u as the current i element with:

  key('i_by_su', concat(s, ' ', u))

One more thing - is there any reason that you're wrapping an
xsl:for-each around an xsl:apply-templates like this:

   <xsl:for-each select="i[generate-id(.) =
      <xsl:sort select="s"/>
      <xsl:apply-templates select="."/>

That's almost exactly the same as just:

  <xsl:apply-templates select="i[generate-id() =
                                 generate-id(key('i_by_s', s)[1])]">
     <xsl:sort select="s" />

The latter is not only shorter, but (if it's important) the position()
of the i elements within the i-matching template will reflect the
order in which they're processsed, rather than always being 1.

> I am willing to use saxon:intersection, since I'm already using
> saxon:preview just to be able to create the input data in the first
> place.

Well you may want to use saxon:hasSameNodes() rather than using
generate-id() to test node equality. For example:

  i[saxon:hasSameNodes(., key('i_by_s', s)[1])]

I hope that helps,


Jeni Tennison

 XSL-List info and archive:

Current Thread