Background: I'm collecting usability statistics for a group of
applications. Each count has the following attributes: date,
application, major heading, minor heading, count. My intent is
to pull this back as XML and render it in to an HTML table.
I have all this working and all the files are below. I would
value any feedback.
A simple example of the data looks like this:
<counts-report>
<counts date="2004-10-07">
<application name="App A">
<major name="Major A">
<minor name="Minor A">1</minor>
<minor name="Minor C">7</minor>
</major>
</application>
</counts>
<counts date="2004-10-08">
<application name="App A">
<major name="Major A">
<minor name="Minor A">1</minor>
<minor name="Minor B">3</minor>
</major>
<major name="Major B">
<minor name="Minor A">15</minor>
</major>
</application>
</counts>
</counts-report>
The output I am looking for is something like:
+------------+---------------------------------------+
| | App A |
| +---------------------------------------+
| | Major A | Major B |
| +---------+---------+---------+---------+
| | Minor A | Minor B | Minor C | Minor A |
+------------+---------+---------+---------+---------+
| 2004-10-07 | 1 | | | |
+------------+---------+---------+---------+---------+
| 2004-10-07 | 1 | 3 | 7 | 15 |
+------------+---------+---------+---------+---------+
| Totals | 2 | 3 | 7 | 15 |
+------------+---------+---------+---------+---------+
Now I looked around for solutions that would let me tabularize
truely sparese data but didn't have any luck and no elegant
solutions came to mind.
The only solution I could come up with was to somehow massage the
data beforehand. The two choices I explored were
1) Turning it in to a non-sparse data set
2) Put a "domain" entry in front listing the union of all
I went with #2 (mainly because for real world data #1 would
inflate the size of the file significantly). So the final file
looks like:
<counts-report>
<domain>
<application name="App A">
<major name="Major A">
<minor name="Minor A"/>
<minor name="Minor B"/>
<minor name="Minor C"/>
</major>
<major name="Major B>
<minor name="Minor A"/>
</major>
</application>
</domain>
<counts date="2004-10-07">
<application name="App A">
<major name="Major A">
<minor name="Minor A">1</minor>
<minor name="Minor C">7</minor>
</major>
</application>
</counts>
<counts date="2004-10-08">
<application name="App A">
<major name="Major A">
<minor name="Minor A">1</minor>
<minor name="Minor B">3</minor>
</major>
<major name="Major B">
<minor name="Minor A">15</minor>
</major>
</application>
</counts>
</counts-report>
The XSLT I am using is at the end of this message. This is my
first non-trivial XSLT experiment so I would appreciate any
comments. A couple of specific questions:
1) I wanted to put in blank cells instead of "-". I
couldn't get the parsers to accept the (verbatim) in
the xslt file (line 60)
2) I used two templates for minor and non-minor table headers
because the colpan value was comming out to zero when (@line
27 & 34... referenced at 11, 12, and 13).
3) I access the actual data for each row by constructing a
path expression using each "minor" entry found in the
domain (line 55).
I would also value any stylistic comments.
Thanks in advance for any help
Charlie
1: <?xml version="1.0" encoding="UTF-8"?>
2: <xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
3: <xsl:output method="html" version="1.0" encoding="UTF-8"
indent="yes"/>
4: <xsl:template match="/counts-report">
5: <html>
6: <head>
7: <title>My Table</title>
8: </head>
9: <body>
10: <table border="1" cellpadding="5">
11: <tr> <th rowspan="3">Date</th> <xsl:apply-templates
mode="header" select="domain/application"/> </tr>
12: <tr> <xsl:apply-templates mode="header"
select="domain/application/major"/> </tr>
13: <tr> <xsl:apply-templates mode="header"
select="domain/application/major/minor"/> </tr>
14:
15: <xsl:apply-templates mode="data" select="counts"/>
16:
17: <tr>
18: <th> Totals </th>
19: <xsl:apply-templates mode="totals"
select="domain/application/major/minor"/>
20: </tr>
21:
22: </table>
23: </body>
24: </html>
25: </xsl:template>
26:
27: <xsl:template mode="header" match="*">
28: <xsl:element name="th">
29: <xsl:attribute name="colspan"><xsl:value-of
select="count(.//minor)"/></xsl:attribute>
30: <xsl:value-of select="@name"/>
31: </xsl:element>
32: </xsl:template>
33:
34: <xsl:template mode="header" match="minor">
35: <xsl:element name="th">
36: <xsl:value-of select="@name"/>
37: </xsl:element>
38: </xsl:template>
39:
40: <xsl:template mode="data" match="counts">
41: <xsl:variable name="rownum" select="position()"/>
42:
43: <xsl:element name="tr">
44: <xsl:if test="$rownum mod 2 = 0">
45: <xsl:attribute name="style">background-color: rgb(0, 204,
204);</xsl:attribute>
46: </xsl:if>
47:
48: <th> <xsl:value-of select="@date"/> </th>
49:
50: <xsl:for-each
select="/counts-report/domain/application/major/minor">
51: <xsl:variable name="app" select="../../@name"/>
52: <xsl:variable name="major" select="../@name"/>
53: <xsl:variable name="minor" select="@name"/>
54:
55: <xsl:variable name="value"
select="/counts-report/counts[$rownum]/application[@name=$app]/major[@name=$major]/minor[@name=$minor]"/>
56:
57: <td>
58: <xsl:choose>
59: <xsl:when test="$value"> <xsl:value-of
select="$value"/> </xsl:when>
60: <xsl:otherwise> - </xsl:otherwise>
61: </xsl:choose>
62: </td>
63:
64: </xsl:for-each>
65:
66: </xsl:element>
67:
68: </xsl:template>
69:
70: <xsl:template mode="totals" match="minor">
71: <xsl:param name="app" select="../../@name"/>
72: <xsl:param name="major" select="../@name"/>
73: <xsl:param name="minor" select="@name"/>
74:
75: <td>
76: <xsl:value-of
select="sum(/counts-report/counts/application[@name=$app]/major[@name=$major]/minor[@name=$minor])"/>
77: </td>
78: </xsl:template>
79:
80: </xsl:stylesheet>