by Bob DuCharmeNovember 05, 2003Relational databases have always offered a feature known as grouping, that is, sorting a collection of records on a field or combination of fields and then treating each subcollection that has the same value in that sort key as a unit. For example, if the following XML document was stored in a relational database table, grouping the records by project value would let us print the records with a subhead for each project name at the beginning of that project's group of records, and it would let us find Statistics Such as The average or total size of the files in each project.
While XSLT 1.0 lets you sort elements (see the July 2002 column for an introduction), it still forces you to jump through several hoops to do anything extra with the groups that result from the sort. Oracle's lead XML Technical Evangelist Steve Muench developed an approach using the xsl: key element, and this became so popular that it's known as the "Muenchian Method." Jeni Tennison has a fine explanation of it on her site.XSLT 2.0 makes grouping even easier than Steve did The XSLT 2.0 xsl:. for -each-group instruction iterates across a series of groups, with the criteria for grouping specified by its attributes. The required select attribute identifies the elements to sort and group, and either the group-by, group-adjacent, group-starting-with Or Group-ending-with attribute describes how to sort and group.
Let's look at a simple example. The single template rule in the following XSLT 2.0 stylesheet tells the XSLT processor that when it finds a files element it should select all the file children of that element and sort them into groups based on the value of each file Element's Project Attribute Value. (All Examples in this Column Are Available In this zip file. to run them, use saxon 7, the only xslt processor current offer support for 2.0.)
Version = "2.0"> xsl: text> xsl: for-each-group> xsl: template> xsl: stylesheet> Just as the xslt 1.0 xsl: for-Each instruction Iterates Across a node set, with child elements of the xsl: for-each element specifying what you want done to each node in the set, the xsl: for -each-group instructions iterates across the groups, with children of the xsl: for-each-group element specifying what you want done to each group The example above does two simple things as it finds each group.: IT outputs the value of the current-grouping-key () Function, Which Returns the grouping key value shared by the me. of the group. It outputs a carriage return. Using the XML Document Shown Earlier Asia Source Document, The Stylesheet Creates this Result: Mars Neptune Jupiter IT Lists The Grouping Values. This Ability To List All The Different Project Values with no refeats in The List May SEEM SIMPLE, But It Would Have Taken a Lot More Code In Xslt 1.0. Let's Replace The Template Rule With One That Does A bit More: xsl: text> xsl: for-energy> xsl: text> xsl: for-each-group> xsl: template> The Contents of this xsl: for-each element begin with an xslt 1.0 xsl: for-each element which, as i mentioned, Iterates Across a set of nodes. By selecting the current-group () Node Set, The xsl: for- Each Element Iterates Over the Nodes of The "Mars" Group in The First Xsl: for-Each-Group Pass, The Nodes of The "Neptune" Group in The Second Pass, and Those of the "jupiter" Group in the final pass. each iteration of the xsl: for-each instruction outputs the value of the name attribute of the context node (the node being processed by the loop), a comma, and the value of the context node's size attribute, finishing with a carriage return added with an xsl: text element.After the xsl: for-each element iterates across the group being processed by the xsl:. for-each-group element, the template outputs a message about the average size value within each group To do this, IT Uses The current-grouping-key () Function That We SAW IN OUR First Stylesheet To Name The Group and the Avg () Function to Compute T Heave........................ Applied to the Same Source Document, this Second Stylesheet Products this Result: Swablr.eps, 4313 Ummagumma.zip, 2441 Schtroumpf.txt, 389 Average Size for Mars Group: 2381 Batboy.wks, 424 Paisley.Doc, 988 Mondegreen.doc, 1993 Average Size for Neptune Group: 1135 Potrzebie.dbf, 1102 Kwatz.xom, 43 Gadabout.pas, 685 Average Size for Jupiter Group: 610 If the xsl: for-each-group element uses a group-adjacent attribute instead of a group-by attribute, it does not sort the selected elements, leaving with the same them in their original order and grouping adjacent elements key value together. For Example, IF We Revise The Previous Stylesheet's Template To Look Like This (Note Also The Removal of The Instructions That Compute Average File Size), xsl: text> xsl: for-energy> xsl: text> xsl: for-each-group> xsl: template> it only groups together the potrzebie.dbf / kwatz.xom pair and the ummagumma.zip/schtroumpf.txt pair, since those were the only contiguous file elements in our source documents that had the same project attribute value- "jupiter" for potrzebie. DBF and kwatz.xom and "MARS" for ummagumma.zip and schtroumpf.txt. Swablr.eps, 4313 Batboy.wks, 424 Potrzebie.dbf, 1102 Kwatz.xom, 43 Paisley.Doc, 988 Ummagumma.zip, 2441 Schtroumpf.txt, 389 Mondegreen.doc, 1993 Gadabout.pas, 685 The group-starting-with attribute names a node that the xsl: for-each-group element will treat as the beginning of a new group This can add depth to a flat list of elements by enclosing groups of those elements in container elements.. HTML documents, in which h1, h2, h3, and p elements after any of these headers are usually siblings, can benefit a lot from this; its flat structure makes it difficult for a stream-based parser to know which section of a document is ending when, and containing elements make this much easier to add some depth to the following HTML document, the group-starting-with attribute can let us specify that each h1 element starts a new chapter:.
par 1 p>
paral 2 p>
par 3 p>
paral 4 p>
par 5 p>
paral 6 p>
body>
html>
The following template rule does this to elements within a body element by specifying "h1" as the node starting each group that the XSLT processor should enclose in a chapter element. Note how the select attribute does not specify one kind of element to group, But all (*) children of the body element:
XSL: COPY>
xsl: for-energy>
chapter>
xsl: for-each-group>
body>
xsl: template>
Applying It to the HTML Document Shown Above Gives US This Result:
par 1 p>
paral 2 p>
par 3 p>
chapter>
paral 4 p>
par 5 p>
paral 6 p>
chapter>
body>
html>
The fourth and last way to specify a grouping is the group-ending-with attribute, which names a pattern that identifies nodes that should end each group. The following template rule specifies that a group ends when it finds an element with any name (* WHOSE POSION, MODULO 3, Equals 0 - in Other Words, Any Element Whose Position Wtem, ITS Parent Is A Multiple of 3. The Template Rule Also Encloses The WHOLELT IN A Book Element.
Group-ending-with = "* [position () MOD 3 = 0]> XSL: COPY> xsl: for-energy> chapter> xsl: for-each-group> book> xsl: template> A Stylesheet with this Template Rule Creates This Result WHEN Using The Files Document We SAW Earlier: chapter> chapter> chapter> book>