Re[2]: question with using Muenchian/xsl:key (Re: sort/group/count p robl em)

Subject: Re[2]: question with using Muenchian/xsl:key (Re: sort/group/count p robl em)
From: Jeni Tennison <mail@xxxxxxxxxxxxxxxx>
Date: Sun, 12 Nov 2000 20:05:04 +0000

> I also tried the normal (inefficient) way of using two XPath:
> <xsl:for-each select="item[not(@itemid = preceding-sibling::item/@itemid)]">
>   <td class="text" width="48%"><xsl:value-of select="@itemid"/></td>
>   <td class="text-right" width="6%"><xsl:value-of select="sum(item[@itemid =
> current()/@itemid]/@units)"/></td>
> </xsl:for-each>
> That also returned me a sum of 0, is there something wrong with my
> calculation of sum?

Yes. The calculation here is for the sum of the units attributes for
the item elements whose itemid attribute is equal to the current
node's itemid attribute *and that are children of the current node*.
However, within the xsl:for-each, the current node is an 'item'
element, a *sibling* of the other items that you're interested in.
The correct XPath for the sum is:

  sum(../item[@itemid = current()/@itemid]/@units)

As far as using keys are concerned, I'm not convinced that they are
substantially better than the above method with the problem as you
describe it now. Whether or not the Muenchian Method is worthwhile
depends on what the payoff between memory (keys take up more memory)
and speed (keys take less time) are for you, and on the number of
items within one itemlist compared to the number of items throughout.
If there are only a few items in each itemlist, and you're processing
each itemlist in turn, then it's probably not worth using keys for
this problem.

However, if speed is more important and the itemlists are long, then
the vital thing that you need to do is narrow down the list of items
that the key gives you for a particular itemid in the context of a
particular itemlist.

There are two general ways of doing this.  The first is, as David
Carlisle suggested, to include information about the itemlist that
you're currently looking at in the key value that you use to retrieve
the items you're interested in.  At present, the key that you have
looks like:

<xsl:key name="items" match="item" use="@itemid" />

The key value is the value of the itemid attribute on the particular
item.  You could include in that key value something about the
itemlist that the item belongs to: its id, if it has one, or a name,
or anything at all that distinguishes it from the other itemlists in
the document.  The most general thing you can use is its unique id as
generated through the generate-id() function.  You can include that in
your key value by concatenating it with the @itemid value:

<xsl:key name="itemlist-items" match="item"
         use="concat(generate-id(parent::itemlist), '::', @itemid)" />

Then, in order to get the uniquely itemid'd items within a particular
itemlist, you can use the XPath:

  item[generate-id() =
                       concat(generate-id(parent::itemlist), '::', @itemid)))]

In other words, get the items whose unique id is the same as the
unique id of the (first) item returned from the 'items' key with a key
value equal to a concatenation of the unique id of the item's parent
itemlist, '::', and the item's itemid attribute.

You can also use:

  item[1 = count(. |
                            '::', @itemid))[1])]

In other words, get the items such that there is only one node in the
node set resulting from the union of that item and the first item
returned from the 'items' key with a key value equal to a
concatenation of the unique id of the item's parent itemlist, '::',
and the item's itemid attribute.

The second general solution is to have the key() return *all* the
items (no matter what itemlist they belong to), but filter that list
to only those items that are in the itemlist for the item you're
looking at.  To use this method you have to have a context in which it
is possible to get at the common node set that you have in mind.  For
example, in your case, you need to be in a context where the current
node is the itemlist that you're interested in, such as within a
template matching that itemlist.

Mike Brown suggested one way of doing this using the Kaysian Method
for finding node set intersections, which is a general approach that
is essential when all you know is the node set filter that you're
interested in, not what all those nodes have in common.  For example,
if you had a global variable that held the node set that you were
using as a filter, then Mike's approach would be perfect.

In your case, though, you know that the node set filter involves the
identity of the itemlist for the items.  If you have the unique
identity for the current itemlist within a variable:

  <xsl:variable name="itemlist-id" select="generate-id()" />

then you can filter the node set returned by the key by testing which
of them have a parent itemlist that has the same unique id as the
current itemlist:

  key('items', @itemid)[generate-id(parent::itemlist) =

You can insert this filtered key result into either of the Muenchian

  item[generate-id() =
       generate-id(key('items', @itemid)[generate-id(parent::itemlist) =


  item[1 = count(. |
                 key('items', @itemid)[generate-id(parent::itemlist) =

I hope that helps,


Jeni Tennison

 XSL-List info and archive:

Current Thread