Re: [xsl] Trouble with grouping

Subject: Re: [xsl] Trouble with grouping
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 6 Feb 2002 10:22:40 +0000
Hi David,

> I have found salvation within this list in regards to grouping and
> sorting before. I'm at my wits ends with this one. For some reason,
> I am displaying records outside of the current context, and I cannot
> figure out why. Oh, and the additional records coming through are
> not being displayed where their context is correct. The XSL and XML
> files are a little to long to attach, so they are here:

This is one of the classic problems with Muenchian grouping...

Keys always index all the elements that match the pattern in their
match attribute *throughout the document*. So when you do:

<xsl:key name="distinct" match="Transaction" use="IsCredit" />

You index *all* the Transaction elements in the document, whatever
Account they are in, based on their IsCredit value.

When you try to get the unique ones within a particular Account, with
(in the context of an Account element):

  Transactions/Transaction[count(. | key('distinct', IsCredit)[1]) = 1]

then what you're actually doing is searching through the Transaction
elements within the Account, and locating those that are first *in the
entire document, across Accounts* with a particular IsCredit value.
That's why you're sometimes *not* getting Transactions that you know
that you should - unless the Transactions within this Account happen
to be the first in the document with that particular IsCredit value
(which would generally only be for the first Account, I imagine), you
won't see them.

Also, when you do:

  key('distinct', IsCredit)

to get all the Transactions with the same IsCredit value, you get
*all* the Transactions, across the entire document, across Accounts,
with that particular IsCredit value. Which is why you're getting
Transactions in Accounts when you didn't want them.

So that's the problem. One solution is to make the key include some
information about the identity of the Account in which the
Transaction's occurred, as well as the IsCredit value. You need some
unique identifier for each Account - I'm guessing that the
Account's Statement/Number acts as a unique identifier; if all else
fails you can use generate-id() to create one.

Change the key to include the Account's identifier within the key
value, like so:

<xsl:key name="distinct" match="Transaction"
  use="concat(ancestor::Account/Statement/Number, '+', IsCredit)" />

Then to get the unique ones within the current Account element, you
can do:

  <xsl:variable name="account" select="Statement/Number" />
  <xsl:apply-templates
    select="Transactions/Transaction
              [count(. | key('distinct',
                             concat($account, '+', IsCredit))[1]) = 1]">
    <xsl:sort select="IsCredit" order="descending" />
  </xsl:apply-templates>

And to get all the rest of the Transactions with that particular
IsCredit within the Account of the current Transaction, you can use:

  key('distinct',
      concat(ancestor::Account/Statement/Number, '+', IsCredit))

---

In XSLT 2.0, because you don't use keys, these kinds of scoping
problems don't occur. You can simply do:

  <xsl:for-each-group select="Transactions/Transaction"
                      group-by="IsCredit">
    <xsl:sort select="IsCredit" order="descending" />
    ...
    <xsl:for-each select="current-group()">
      ...
    </xsl:for-each>
    ...
  </xsl:for-each-group>

Does anyone else feel that an apply-templates-group equivalent would
be useful? I have a feeling that a lot of people use
a template, rather than a xsl:for-each, when generating group-level
output at the moment (certainly that's what I've standardly done),
which means changing things around quite a lot when you start using
XSLT 2.0 grouping methods instead.

Of course you can split things up by doing:

  <xsl:for-each-group select="Transactions/Transaction"
                      group-by="IsCredit">
    <xsl:sort select="IsCredit" order="descending" />
    <xsl:apply-templates select="." />
  </xsl:for-each-group>

...

<xsl:template match="Transaction">
    ...
    <xsl:for-each select="current-group()">
      ...
    </xsl:for-each>
    ...
</xsl:template>

But the position() within the Transaction template is then always 1,
whereas with something like:

  <xsl:apply-templates-group select="Transactions/Transaction"
                             group-by="IsCredit">
    <xsl:sort select="IsCredit" order="descending" />
  </xsl:apply-templates-group>

then the position() changes as you would expect.

Cheers,

Jeni

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


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


Current Thread