Grouping Data by Examining the Keyed Node-set

MSXML 5.0 SDK

Microsoft XML Core Services (MSXML) 5.0 for Microsoft Office - XSLT Developer's Guide

Grouping Data by Examining the Keyed Node-set

By definition, a node-set cannot contain a given node more than once. Look at the following portion of the <catalog> element's template rule, the grp_categ mode, for grouping the books by genre. See if you can tell what the references to the categ_key key are doing.

<xsl:template match="catalog" mode="grp_categ">
    <h2>Titles Grouped by Genre</h2>
    <xsl:for-each select="book[count(. | key('categ_key', genre)[1])=1]">
        <h3><xsl:value-of select="genre" /></h3>
        <xsl:for-each select="key('categ_key', genre)">
            <xsl:sort select="title"/>
...
        </xsl:for-each>
    </xsl:for-each>
</xsl:template>

The predicate portion of the outer <xsl:for-each> element's select attribute looks rather complex, but it essentially consists of two separate conditions joined by the XPath union operator, a pipe or vertical-bar symbol (|). So select="book[count(. | key('categ_key', genre)[1])=1]" translates as selecting the current <book> element if it is also the first item in the "genre" category as returned by the key('categ_key', genre)[1] function

The union operator | returns a node-set joined by the selected <book> element and the first item of the "genre" category. If the size of this node-set is one (1), the selected <book> is the first item in the "genre" category and the template rule instantiates a level-3 heading in the result tree, displaying the name of the book's category. Otherwise, the selection is bypassed.

The second reference to the key() function is where the actual grouping occurs, within the category established by the outer <xsl:for-each>. It says, within each of the node-sets established by the categ_key key, to list the titles and so on of all books in the group.

Example

XML File (books.xml)

Use the Sample XML File (books.xml). Below the line <?xml version='1.0'?>, add the following line:

<?xml-stylesheet type="text/xsl" href="lib_cat2.xsl"?>

XSLT File (lib_cat2.xsl)

This file uses the <genre> element as the category for sorting.

<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name="categ_key" match="book" use="genre"/>
<xsl:key name="author_key" match="book" use="author"/>

<xsl:template match="/">
    <HTML>
        <HEAD>
            <TITLE>Scootney Publishing: Special Exhibit</TITLE>
            <STYLE>
                h1 {font-family: Arial,Helvetica,Univers,sans-serif;
                    font-size: 18pt; font-weight: bold;
                    background-color: teal;
                    width: 75%}
                h2 {font-family: Arial,Helvetica,Univers,sans-serif;
                    font-size: 14pt; font-weight: bold;
                    background-color: white;
                    width: 75%}
                h3 {font-family: Arial,Helvetica,Univers,sans-serif;
                    font-size: 12pt; font-weight: bold;
                    background-color: gold;
                    width: 75%;
                    border-width: 1;
                    border-style: solid}
            </STYLE>
        </HEAD>
        <BODY>
            <h1>Scootney Publishing: Special Exhibit Catalog</h1>
            <xsl:apply-templates select="catalog" mode="grp_categ"/>
            <xsl:apply-templates select="catalog" mode="grp_author"/>
        </BODY>
    </HTML>
</xsl:template>

<xsl:template match="catalog" mode="grp_categ">
    <h2>Titles Grouped by Genre</h2>
    <xsl:for-each select="book[count(. | key('categ_key', genre)[1])=1]">
        <h3><xsl:value-of select="genre" /></h3>
        <xsl:for-each select="key('categ_key', genre)">
            <xsl:sort select="title"/>
            <TABLE border="0" width="75%">
                <tr>
                    <th width="10%" align="right">Title</th>
                    <td width="90%" align="left"><xsl:value-of select="title" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Author</th>
                    <td width="90%" align="left"><xsl:value-of select="author" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Copyright</th>
                    <td width="90%" align="left"><xsl:value-of select="publish_date" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Price</th>
                    <td width="90%" align="left"><xsl:value-of select="price" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right" valign="top">Summary</th>
                    <td width="90%" align="left" valign="top"><xsl:value-of select="description" /></td>
                </tr>
            </TABLE>
            <hr width="75%" align="left"/>
        </xsl:for-each>
    </xsl:for-each>
</xsl:template>

<xsl:template match="catalog" mode="grp_author">
    <h2>Titles Grouped by Author</h2>
    <xsl:for-each select="book[generate-id() = generate-id(key('author_key', author)[1])]">
        <xsl:sort select="author"/>
        <h3><xsl:value-of select="author" /></h3>
        <xsl:for-each select="key('author_key', author)">
            <xsl:sort select="title"/>
            <TABLE border="0" width="75%">
                <tr>
                    <th width="10%" align="right">Title</th>
                    <td width="90%" align="left"><xsl:value-of select="title" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Genre</th>
                    <td width="90%" align="left"><xsl:value-of select="genre" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Copyright</th>
                    <td width="90%" align="left"><xsl:value-of select="publish_date" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right">Price</th>
                    <td width="90%" align="left"><xsl:value-of select="price" /></td>
                </tr>
                <tr>
                    <th width="10%" align="right" valign="top">Summary</th>
                    <td width="90%" align="left" valign="top"><xsl:value-of select="description" /></td>
                </tr>
            </TABLE>
            <hr width="75%" align="left"/>
        </xsl:for-each>
    </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Formatted Output

A portion of the output is shown here.