XML.com: XML From the Inside Out
oreilly.comSafari Bookshelf.Conferences.

advertisement

Of Grouping, Counting, and Context

Of Grouping, Counting, and Context

July 31, 2002

John Simpson is the author of XPath and XPointer.

Q: How do I count the number of elements with a given attribute value?

My XML file looks like this:

   <Match>
      <Date>21/3/2005</Date>
      <Stadium>Wembley</Stadium>
      <Team Name="Liverpool">
         <Goal Scorer="O'Reilly"/>
         <Goal Scorer="Smith"/>
         <Goal Scorer="O'Reilly"/>
      </Team> 
      <Team Name="Real Madrid">
         <Goal Scorer="Charles"/>
         <Goal Scorer="Humble"/>
         <Goal Scorer="Humble"/>
         <Goal Scorer="Santana"/>
         <Goal Scorer="Humble"/>
      </Team>
   </Match>

I want to get output like this (for the Liverpool team):

   Player   Goals
   O'Reilly 2
   Smith    1

Any ideas?

A: Great question. The answer requires knowledge of a couple of XSLT techniques: grouping (with XSLT keys) and using the count() function.

Here's the relevant portion of an XSLT stylesheet to solve the problem; notes on the code follow, particularly on the portions which are in boldface:

   <xsl:key name="player" match="@Scorer"
use="."/>

   <xsl:template match="Team">
      <table border="1">
         <tr><th colspan="3">Team: <xsl:value-of
select="@Name"/></th></tr>
        
<tr><th>Player</th><th>Goals</th><th>Gen'd
ID</th></tr>
         <xsl:for-each
select="Goal/@Scorer[generate-id()=generate-id(key('player',
.))]">
            <tr>
               <td><xsl:value-of
select="."/></td>
               <td><xsl:value-of
select="count(../../Goal[@Scorer=current()])"/></td>

               <td><xsl:value-of
select="generate-id(.)"/></td>
            </tr>
         </xsl:for-each>
      </table>
      <br />
   </xsl:template>

Related Reading

XPath and XPointer

XPath and XPointer
Locating Content in XML Documents
By John E. Simpson

This code fragment first sets up an XSLT key -- something like an index in DBMS terms. The name of the key is "player"; it matches on a pattern identified by the XPath expression @Scorer. The value of the key, given by the use attribute, is the string-value of that match pattern. Note that the xsl:key element is a top-level element, a child of the root xsl:stylesheet element. Thus the match pattern isn't "relative" to anything at all in the source tree -- there's nothing like a context node to which it can be relative -- until the key is actually invoked, using the key() function, by some lower-level stylesheet template or instruction. Importantly, unlike a DBMS unique index or an XML ID-type attribute, an XSLT key may point to more than one "thing" at a time. Also unlike ID-type attributes, keys aren't restricted to identifying elements only. In this case, we're going to be grouping on the basis of attributes which share the same value: the Scorer attributes in the source tree.

There's one template rule in this stylesheet fragment. For every occurrence of a Team element in the source tree, as located by the xsl:template's match attribute, the template constructs a three-column table. Two columns display the names of the scoring players on each team and the number of goals scored; I've added the third column just to demonstrate how the keying works.

The first thing of interest in the template rule is the xsl:for-each element within it. Its select attribute starts clearly enough --"for each Scorer attribute of a Goal child of the context Team element" -- but then seems to trail off into gibberish when it moves into the predicate.

Pages: 1, 2

Next Pagearrow







close