Annotated XSLT - Numbering and Grouping
If you are trying to figure out xsl you should really be reading the very active xsl list. Subscribe or search the archive.Knowledgeable members of the XSL mailing list frequently post elegant solutions for problems other posters can't quite resolve. There's usually a short note that tells you what to look up to figure out how the solution works. This time Wendell Piez walks you through David Carlisle's numbering and grouping solution line by line. He explains what each line or grouping is doing, what it's acting on, and what the result will be. To read the thread in the archive, click here.
From the post:
Good literature deserves commentary --
At 04:17 PM 5/25/2007, David wrote:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="g" match="group_id" use="." />
The 'g' key matches group-id elements by their value, so for example key('g','250') returns all group-id elements with value '250' (however many there are). This is the essence of a Muenchian group.
<xsl:output indent="yes"/>
For cosmetics.
<xsl:variable name="g"
select="/sample/result/details/group_id[generate-id()=generate-id(key('g',.))]"/>
This selects a set of elements, binding them to $g. The set is all the group-id elements whose unique identifier (returned by generate-id()) has the same value as the unique id of the first element returned from the 'g' key using their value (following the XSLT 1.0 rule that the generated id of a set of nodes will be that of
the first node in the set, in document order). That is, it returns the first group-id element in the document that appears with any given value -- so all values are returned exactly once.
You can call these "flag-bearer" elements if you like, since they correspond exactly to the groups of group-id elements, using their values.
<xsl:template match="sample">
<xsl:for-each select="$g">
Iterating over the flag-bearers is equivalent to iterating over the
groups themselves (since each can be used to retrieve its group).
<xsl:sort select="."/>
Dr Carlisle sorts the groups' flag-bearers by their values, so the groups will come out in ascending order by value.
<xsl:variable name="p" select="position()-1"/>
This returns us '0' for the first group, 'n-1' for the nth group, in their sorted order.
<xsl:variable name="c" select="count(key('g',$g[.<current()]))"/>
This returns us the count of the members of all the groups that will appear before this one (a tricky business), by finding all the flag-bearers whose value is less than the current group's, collecting all the members of their groups, and counting them. The key makes this reasonably efficient to do -- but it wouldn't work if we weren't outputting the groups ordered by their values (he'd have to come up with something else).
<xsl:for-each select="key('g',.)">
Now we iterate over the members of the group belonging to this flag-bearer.
<output row="{$c+$p+position()}"><xsl:value-of select="."/></output>
We write an 'output' element. The value of its @row is calculated as:
* The count of all the members of the groups preceding this one (since they'll appear above this one)
* The number of blank lines we'll also include, which happens to be $p
* The position of this member of the group within the group
Its value is its value.
</xsl:for-each>
We're done with the members of this group.
<xsl:if test="position()!=last()">
But unless we're the last group (or rather: the last flag-bearer), we also want...
<output row="{$c+count(key('g',.))+$p+1}"/>
... a blank 'output' element whose row is calculated as the sum of all the members of the groups preceding this one, all the members of this group, the blank lines preceding this one, and 1 (for this blank line)
</xsl:if>
</xsl:for-each>
</xsl:template>
We're done.
Have a good weekend.
Cheers,
Wendell

0 Comments:
Post a Comment
<< Home