If your nodes are out of sorts in your source, use grouping to bring them into line.
This hack shows you several techniques for grouping nodes in the output of an XSLT processor. The first uses XSLT 1.0 and the Muenchian method, named after Steve Muench (see http://www.oreillynet.com/pub/au/609). The second uses XSLT 2.0, which is simpler than the XSLT 1.0 method.
The problem that grouping solves is that nodes may not be grouped to your liking in the source document. For example, look at group.xml (Example 3-54).
Example 3-54. group.xml
<?xml version="1.0" encoding="US-ASCII"?> <?xml-stylesheet href="group.xsl" type="text/xsl"?> <uscities> <western> <uscity state="Nevada">Las Vegas</uscity> <uscity state="Arizona">Phoenix</uscity> <uscity state="California">San Francisco</uscity> <uscity state="Nevada">Silver City</uscity> <uscity state="Washington">Seattle</uscity> <uscity state="Montana">Missoula</uscity> <uscity state="Washington">Spokane</uscity> <uscity state="California">Los Angeles</uscity> <uscity state="Utah">Salt Lake City</uscity> <uscity state="California">Sacramento</uscity> <uscity state="Idaho">Boise</uscity> <uscity state="Montana">Butte</uscity> <uscity state="Washington">Tacoma</uscity> <uscity state="Montana">Helena</uscity> <uscity state="Oregon">Portland</uscity> <uscity state="Nevada">Reno</uscity> <uscity state="Oregon">Salem</uscity> <uscity state="Oregon">Eugene</uscity> <uscity state="Utah">Provo</uscity> <uscity state="Idaho">Twin Falls</uscity> <uscity state="Utah">Ogden</uscity> <uscity state="Arizona">Flagstaff</uscity> <uscity state="Idaho">Idaho Falls</uscity> <uscity state="Arizona">Tucson</uscity> </western> </uscities>
The uscity
nodes in group.xml
list western United States cities at random, not in an organized way
as you might prefer. One feature that can help is that each
uscity
node has a state
attribute. The XSLT grouping technique I’ll show you
can organize the output according to state, also listing each
appropriate city with the given state. This grouping technique is
popularly known as the Muenchian method.
The
Muenchian method of grouping employs
keys together with the generate-id()
function.
There are other grouping methods in XSLT, such as one that uses the
preceding-sibling axis, but I’ve chosen to show you
only the Muenchian method here for two reasons. First, it is the most
efficient and fastest method of grouping; second, it is the most
similar to the new grouping method using the XSLT 2.0 element
for-each-group
, which you will see in the next
section.
The stylesheet group.xsl is shown in Example 3-55. It produces HTML output and assembles its output according to the Muenchian method.
Example 3-55. group.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="html"/> <xsl:key name="list" match="uscity" use="@state"/> <xsl:template match="/"> <html> <head> <title>Western State Cities</title></head> <style type="text/css"> h2 {font-family:verdana,helvetica,sans-serif;font-size:13pt} li {font-family:verdana,helvetica,sans-serif;font-size:11pt} </style> <body> <xsl:for-each select="/uscities/western/uscity[generate-id(.)=generate-id(key('list', @state))]/@state"> <xsl:sort/> <h2><xsl:value-of select="."/></h2> <ul> <xsl:for-each select="key('list', .)"> <xsl:sort/> <li><xsl:value-of select="."/></li> </xsl:for-each> </ul> </xsl:for-each> </body> </html> </xsl:template> </xsl:stylesheet>
The secret to understanding the Muenchian method lies in its use of
keys and the generate-id()
function. On line 1 of
group.xsl, the key named list
is defined. This key is used to efficiently find
state
attributes on uscity
elements in group.xml.
Without getting too overwrought, the generate-id()
function is used with the
key()
function in
for-each
to process the first node in a set (line
14). In this example, it finds the first node whose
state
attribute identifies a given state, and
outputs the name of the state found in the attribute.
Following that, another for-each
(line 18)
processes each other node in the document matching the previous
for-each
, also using key()
.
The value-of
(line 20) under this
for-each
outputs the name of the given city. The
sort
elements (lines 15 and 19) under the
for-each
elements sort the nodes in alphabetical
order.
It’s a little complicated, but it works well. Test it with Xalan C++ [Hack #32] :
xalan -m -i 1 -o group.html group.xml group.xsl
You will get nicely grouped HTML output, stored in group.html (Example 3-56).
Example 3-56. group.html
<html> <head> <title>Western State Cities</title> </head> <style type="text/css"> h2 {font-family:verdana,helvetica,sans-serif;font-size:13pt} li {font-family:verdana,helvetica,sans-serif;font-size:11pt} </style> <body> <h2>Arizona</h2> <ul> <li>Flagstaff</li> <li>Phoenix</li> <li>Tucson</li> </ul> <h2>California</h2> <ul> <li>Los Angeles</li> <li>Sacramento</li> <li>San Francisco</li> </ul> <h2>Idaho</h2> <ul> <li>Boise</li> <li>Idaho Falls</li> <li>Twin Falls</li> </ul> <h2>Montana</h2> <ul> <li>Butte</li> <li>Helena</li> <li>Missoula</li> </ul> <h2>Nevada</h2> <ul> <li>Las Vegas</li> <li>Reno</li> <li>Silver City</li> </ul> <h2>Oregon</h2> <ul> <li>Eugene</li> <li>Portland</li> <li>Salem</li> </ul> <h2>Utah</h2> <ul> <li>Ogden</li> <li>Provo</li> <li>Salt Lake City</li> </ul> <h2>Washington</h2> <ul> <li>Seattle</li> <li>Spokane</li> <li>Tacoma</li> </ul> </body> </html>
In the output, under each alphabetically listed state comes an alphabetical list of cities. That’s what grouping can do for you. Figure 3-29 shows group.html in the Opera browser. Because they support client-side XSLT, you can also open group.xml in Mozilla, Firefox, Netscape, or IE, which will render the document according to group.xsl. Opera does not support client-side XSLT.
The design behind grouping in XSLT 2.0
probably grew out of experience with grouping in Version 1.0.
Grouping in XSLT 1.0 usually brings the for-each
instruction element into service. XSLT 2.0 has a new instruction
element called for-each-group
that makes grouping
a relative snap.
Glance at
group2.xml
,
which lumps XPath 2.0’s context-related functions
into two piles by labeling them with a type
attribute (Example 3-57).
Example 3-57. group2.xml
<?xml version="1.0"?> <list> <description>XPath 2.0 Context Functions</description> <date>2003-11-12</date> <function type="new">current-date( )</function> <function type="new">current-dateTime( )</function> <function type="new">current-time( )</function> <function type="new">default-collation( )</function> <function type="new">implicit-timezone( )</function> <function type="legacy">last( )</function> <function type="legacy">position( )</function> </list>
The eight functions in this list are either legacy
functions or new
ones. The
group2.xsl
stylesheet (Example 3-58) groups the functions in
group2.xml according to the content of the
type
attribute.
Example 3-58. group2.xsl
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:template match="list"> <xsl:copy> <xsl:for-each-group select="function" group-by="@type"> <functions type="{@type}"> <xsl:value-of select="current-group( )" separator=", "/> </functions> </xsl:for-each-group> </xsl:copy> </xsl:template> </xsl:stylesheet>
The
for-each-group
function (line 6) selects the sequence
(node-set in XSLT 1.0) to group with the select
attribute; i.e., all function
children of
list
. The group-by
attribute
determines the key for grouping, which in this case is the content of
the type
attribute in the source. The
functions
literal result element uses an attribute
value template to reflect the value of the type
attribute.
On line 8, the value-of
element’s
select
attribute uses the current-group( )
function—also a new kid on the block in XSLT
2.0—to keep track of which group is which. The
separator
attribute is a new addition to XSLT 2.0,
too. It tells the XSLT 2.0 processor to write a comma followed by a
space after each found node is sent to the result tree.
In XSLT 1.0, value-of
outputs only the first node
of a returned node-set in string form; in XSLT 2.0, all nodes in a
sequence can be returned, so you have to plan accordingly.
You might guess that for-each-group
has several
other attributes, which it does: group-adjacent
,
group-starting-with
,
group-ending-with
, and
collation
. I’m not going to cover
them here, but you can read more about
for-each-group
and its attributes in Section 14 of
the XSLT 2.0 specification.
To get this example to work, you need the latest version of Saxon (currently 8.0, which supports XSLT 2.0 [http://saxon.sourceforge.net]). Use this command to transform group2.xml:
java -jar saxon8.jar group2.xml group2.xsl
The result is two lists of functions, grouped and comma-separated, in
functions
elements (Example 3-59).
Example 3-59. Output of group2.xsl
<?xml version="1.0" encoding="UTF-8"?> <list> <functions type="new">current-date( ), current-dateTime( ), current-time( ), default-collation( ), implicit-timezone( )</functions> <functions type="legacy">last( ), position( )</functions> </list>
This example should give you a feel of how to group nodes in XSLT 2.0.
18.188.178.181