
Of Grouping, Counting, and Context
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 |
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 |
