This is the mail archive of the
xsl-list@mulberrytech.com
mailing list .
Re: 2 level Grouping through attributes
- From: Jeni Tennison <jeni at jenitennison dot com>
- To: xsl-list at lists dot mulberrytech dot com
- Date: Mon, 11 Feb 2002 19:09:42 +0000
- Subject: Re: [xsl] 2 level Grouping through attributes
- Organization: Jeni Tennison Consulting Ltd
- References: <5.0.2.1.0.20020211134828.05247130@pop5.inter.nl.net>
- Reply-to: xsl-list at lists dot mulberrytech dot com
Ronald Heller wrote:
> Try the following:
> <?xml version='1.0'?>
> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
> <xsl:output method="xml" indent="yes"/>
> <xsl:key name="level1" match="XML_OUTPUT/@UserID" use="." />
>
> <xsl:key name="level2" match="XML_OUTPUT/@CategoryID" use="." />
>
> <xsl:template match="/">
> <ROOT>
> <xsl:for-each select="//XML_OUTPUT[generate-id(@UserID) =
> generate-id(key('level1', @UserID)[1])]">
> <xsl:variable name="user">
> <xsl:value-of select="@UserID" />
> </xsl:variable>
>
> <xsl:comment>
> <xsl:value-of select="generate-id(@UserID)" />
> </xsl:comment>
>
> <USER ID="{$user}">
> <xsl:for-each select="//XML_OUTPUT[generate-id(@CategoryID)
> = generate-id(key('level2', @CategoryID)[1]) and @UserID=$user]">
> <xsl:variable name="category">
> <xsl:value-of select="@CategoryID" />
> </xsl:variable>
>
> <CATEGORY ID="{$category}" TITLE="{@CategoryTitle}">
> <xsl:for-each
> select="//XML_OUTPUT[@CategoryID=$category and @UserID=$user]">
> <CONTENT_ITEM ID="{@ContentItemID}"
> TITLE="{@ItemTitle}">
> </CONTENT_ITEM>
> </xsl:for-each>
> </CATEGORY>
> </xsl:for-each>
> </USER>
> </xsl:for-each>
> </ROOT>
> </xsl:template>
> </xsl:stylesheet>
Unfortunately this runs into the classic trap with multi-level
grouping using the Muenchian Method when identifying the unique values
at the second level. See:
> <xsl:for-each select="//XML_OUTPUT[generate-id(@CategoryID)
> = generate-id(key('level2', @CategoryID)[1]) and @UserID=$user]">
Here, you go through all the XML_OUTPUT elements in the document, and
then identify those that:
(a) are the first in the document with that particular CategoryID;
and
(b) have the UserID equal to $user
This will miss out XML_OUTPUT elements that have the same user ID, but
*aren't* the first in the document with the particular category ID. So
you won't pick up everything in the document.
To do two-level grouping using the Muenchian method, the key for the
second level has to incorporate information from the first level. I'd
use the keys:
<xsl:key name="level1" match="XML_OUTPUT" use="@UserID" />
<xsl:key name="level2" match="XML_OUTPUT"
use="concat(@UserID, '+', @CategoryID)" />
This has two advantages: it gives you the right answer, and it means
you can use the 'level2' key to find all the XML_OUTPUT elements with
a particular user and category, which is a lot more efficient than
searching through the entire document again.
Then:
<xsl:template match="/">
<ROOT>
<xsl:for-each
select="//XML_OUTPUT[generate-id() =
generate-id(key('level1', @UserID)[1])]">
<xsl:variable name="user" select="@UserID" />
<xsl:comment>
<xsl:value-of select="generate-id(@UserID)" />
</xsl:comment>
<USER ID="{$user}">
<xsl:for-each
select="key('level1', $user)
[generate-id() =
generate-id(key('level2',
concat($user, '+', @CategoryID)[1])]">
<xsl:variable name="category" select="@CategoryID" />
<CATEGORY ID="{$category}" TITLE="{@CategoryTitle}">
<xsl:for-each select="key('level2',
concat($user, '+', $category)">
<CONTENT_ITEM ID="{@ContentItemID}" TITLE="{@ItemTitle}">
</CONTENT_ITEM>
</xsl:for-each>
</CATEGORY>
</xsl:for-each>
</USER>
</xsl:for-each>
</ROOT>
</xsl:template>
---
The XSLT 2.0 solution is very similar:
<xsl:template match="/">
<ROOT>
<xsl:for-each-group select="//XML_OUTPUT" group-by="@UserID">
<xsl:variable name="user" select="@UserID" />
<xsl:comment>
<xsl:value-of select="generate-id(@UserID)" />
</xsl:comment>
<USER ID="{$user}">
<xsl:for-each-group select="current-group()"
group-by="@CategoryID">
<xsl:variable name="category" select="@CategoryID" />
<CATEGORY ID="{$category}" TITLE="{@CategoryTitle}">
<xsl:for-each select="current-group()">
<CONTENT_ITEM ID="{@ContentItemID}" TITLE="{@ItemTitle}">
</CONTENT_ITEM>
</xsl:for-each>
</CATEGORY>
</xsl:for-each>
</USER>
</xsl:for-each>
</ROOT>
</xsl:template>
Cheers,
Jeni
---
Jeni Tennison
http://www.jenitennison.com/
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list