Temple, Hurley (2005-07-29)
| Hole | Yards | Par | SI | Score | Hole | Yards | Par | SI | Score | |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 338 | 4 | 9 | 5 | 10 | 236 | 3 | 10 | 6 | |
| 2 | 410 | 4 | 1 | 8 | 11 | 398 | 4 | 2 | 5 | |
| 3 | 350 | 4 | 13 | 5 | 12 | 477 | 5 | 8 | 8 | |
| 4 | 484 | 5 | 15 | 7 | 13 | 127 | 3 | 18 | 5 | |
| 5 | 130 | 3 | 17 | 8 | 14 | 349 | 4 | 12 | 7 | |
| 6 | 317 | 4 | 5 | 6 | 15 | 324 | 4 | 4 | 5 | |
| 7 | 310 | 4 | 7 | 5 | 16 | 229 | 3 | 14 | 4 | |
| 8 | 210 | 3 | 11 | 7 | 17 | 414 | 4 | 6 | 8 | |
| 9 | 437 | 4 | 3 | 5 | 18 | 261 | 4 | 16 | 4 | |
| Out: | 2986 | 35 | 56 | In: | 2815 | 34 | 52 | |||
| Total: | 5801 | 69 | 108 | |||||||
An XSL template to display a golf scorecard
I’ve created a small XSL template to display golf scorecards, an example of which can be seen above. I originally made it for our UK Shield 2006 website (it’s a yearly golf tournament, and my team is organising next year’s), but it serves as a simple overview of some XSL techniques.
The two files I’ve used for this example can be downloaded at the end of this article.
Firstly, the XML file that we’re going to use:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="CourseToTable.xsl"?>
<GolfCourses>
<GolfCourse>
<Details>
<Name>Temple</Name>
<Location>Hurley</Location>
<Date>2005-07-29</Date>
</Details>
<Holes tee="Yellow">
<Hole id="1" yards="363" par="4" si="9" score="5" />
<Hole id="2" yards="410" par="4" si="1" score="8" />
<Hole id="3" yards="350" par="4" si="13" score="5" />
<Hole id="4" yards="484" par="5" si="15" score="7" />
<Hole id="5" yards="130" par="3" si="17" score="8" />
<Hole id="6" yards="317" par="4" si="5" score="6" />
<Hole id="7" yards="310" par="4" si="7" score="5" />
<Hole id="8" yards="210" par="3" si="11" score="7" />
<Hole id="9" yards="437" par="4" si="3" score="5" />
<Hole id="10" yards="236" par="3" si="10" score="6" />
<Hole id="11" yards="398" par="4" si="2" score="5" />
<Hole id="12" yards="477" par="5" si="8" score="8" />
<Hole id="13" yards="127" par="3" si="18" score="5" />
<Hole id="14" yards="349" par="4" si="12" score="7" />
<Hole id="15" yards="324" par="4" si="4" score="5" />
<Hole id="16" yards="229" par="3" si="14" score="4" />
<Hole id="17" yards="414" par="4" si="6" score="8" />
<Hole id="18" yards="261" par="4" si="16" score="4" />
</Holes>
</GolfCourse>
</GolfCourses>
It’s a simple layout (and please ignore the scores – I wasn’t having a good day, to put it mildly!), and can take include a number of rounds – just duplicate the node.
Next, the XSL. First we have the usual XSL headers, and a key. This provides a kind of name-value pair, where the “name” is the @id attribute of the hole, and the “value” is the matching hole node. This provides a fast way of getting individual hole nodes – a lot faster than getting them from an xpath query. For further details, see here.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" >
<xsl:output method="html" encoding="utf-8" indent="yes"/>
<xsl:key name="HoleKey" match="Hole" use="@id"/>
Next we have the GolfCourses template. This sets up the HTML framwork – body tags, and the like. It also creates a table to contain two columns of scorecards, then selects a set of cards to go in each column. To do this, it uses the query position() mod 2 = 1 (to get scorecards with indexes 1, 3, 5, …), and position() mod 2 = 1 (to get scorecards with indexes 2, 4, 6, …). The position() function returns the 1-based index of the node, and mod is the modulus operator (it returns the remainder of a division): for example, 3 / 2 = 1.5, so 3 mod 2 = 1. Similarly, 4 mod 2 = 0. This neatly sorts the course nodes into two lists.
<!-- ******************************************************** -->
<xsl:template match="GolfCourses">
<html>
<body>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="100%">
<tr>
<td align="center" valign="top"><xsl:apply-templates select="GolfCourse[position() mod 2 = 1]"/></td>
<td align="center" valign="top"><xsl:apply-templates select="GolfCourse[position() mod 2 = 0]"/></td>
</tr>
</table>
</body>
</html>
</xsl:template>
Next we have the GolfCourse template. First, we create a variable to choose whether we’re showing scores (if scores are present, use them, otherwise don’t). The output the course details – name and location, and, if we’re showing scores, the date on which the round was played. Next it creates the table, and enters the header row (<th> tags), then it selects the first nine holes (select=”Holes/Hole[@id < 10]“), and applies the appropriate template. Finally, the template for the totals line is called, then the table is closed.
<!-- ******************************************************** -->
<xsl:template match="GolfCourse">
<xsl:variable name="bShowScores">
<xsl:choose>
<xsl:when test="count(Holes/Hole/@score) > 0">
<xsl:text>Y</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>N</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<h2>
<xsl:value-of select="Details/Name"/>
<xsl:text>, </xsl:text>
<xsl:value-of select="Details/Location"/>
<xsl:if test="$bShowScores='Y'">
<xsl:text> (</xsl:text>
<xsl:value-of select="Details/Date"/>
<xsl:text>)</xsl:text>
</xsl:if>
</h2>
<table align="center" border="1" cellspacing="0" cellpadding="0">
<tr>
<th>Hole</th>
<th bgcolor="Yellow">Yards</th>
<th bgcolor="Yellow">Par</th>
<th>SI</th>
<xsl:if test="$bShowScores='Y'">
<th>Score</th>
</xsl:if>
<th width="10"></th>
<th>Hole</th>
<th bgcolor="Yellow">Yards</th>
<th bgcolor="Yellow">Par</th>
<th>SI</th>
<xsl:if test="$bShowScores='Y'">
<th>Score</th>
</xsl:if>
</tr><xsl:apply-templates select="Holes/Hole[@id < 10]">
<xsl:with-param name="bShowScores" select="$bShowScores"/>
<xsl:sort select="id" data-type="number" order="ascending"/>
</xsl:apply-templates><xsl:call-template name="TotalsLine">
<xsl:with-param name="bShowScores" select="$bShowScores"/>
</xsl:call-template></table>
</xsl:template>
Next we have the Hole template. This gets called once for each hole on the way out (the first nine). For each of these, it outputs the hole it has been called for and the corresponding hole on the back nine (@id+9), by calling the HoleDetails template.
<!-- ******************************************************** -->
<xsl:template match="Hole">
<xsl:param name="bShowScores"/>
<xsl:variable name="nsHoleOut" select="."/>
<xsl:variable name="nsHoleIn" select="key('HoleKey', @id + 9)"/>
<tr>
<xsl:call-template name="HoleDetails">
<xsl:with-param name="nsHole" select="$nsHoleOut"/>
<xsl:with-param name="bShowScores" select="$bShowScores"/>
</xsl:call-template>
<td></td>
<xsl:call-template name="HoleDetails">
<xsl:with-param name="nsHole" select="$nsHoleIn"/>
<xsl:with-param name="bShowScores" select="$bShowScores"/>
</xsl:call-template>
</tr>
</xsl:template>
Next we have the HoleDetails template. This outputs the hole details (as the name may have suggested), entering each detail into a separate table cell (<td>). The details are hole id, yardage, par, stroke index, and score (if we’re displaying it).
<!-- ******************************************************** -->
<xsl:template name="HoleDetails">
<xsl:param name="nsHole"/>
<xsl:param name="bShowScores"/>
<td align="center"><xsl:value-of select="$nsHole/@id"/></td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nsHole/@yards"/></td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nsHole/@par"/></td>
<td align="center"><xsl:value-of select="$nsHole/@si"/></td>
<xsl:if test="$bShowScores='Y'">
<td align="center"><xsl:value-of select="$nsHole/@score"/></td>
</xsl:if>
</xsl:template>
Finally we have the TotalsLine template. This calculates the totals for the round – it creates two variables to hold the first nine and last nine holes (nsHolesIn and nsHolesOut), then creates a number of other variables to hold the calculated sums, and a variable called nSpanCols, which is used to hold the number of cells that should be spanned to get everything to line up (if we’re displaying the scores we have one extra column in each half of the course). Finally, it outputs the calculated totals in a set of table cells.
<!-- ******************************************************** -->
<xsl:template name="TotalsLine">
<xsl:param name="bShowScores"/>
<xsl:variable name="nsHolesOut" select="Holes/Hole[@id < 10]"/>
<xsl:variable name="nsHolesIn" select="Holes/Hole[@id > 9]"/>
<xsl:variable name="nOutYards" select="sum($nsHolesOut/@yards)"/>
<xsl:variable name="nInYards" select="sum($nsHolesIn/@yards)"/>
<xsl:variable name="nOutPar" select="sum($nsHolesOut/@par)"/>
<xsl:variable name="nInPar" select="sum($nsHolesIn/@par)"/>
<xsl:variable name="nYards" select="sum(Holes/Hole/@yards)"/>
<xsl:variable name="nPar" select="sum(Holes/Hole/@par)"/>
<xsl:variable name="nOutScore" select="sum($nsHolesOut/@score)"/>
<xsl:variable name="nInScore" select="sum($nsHolesIn/@score)"/>
<xsl:variable name="nScore" select="sum(Holes/Hole/@score)"/>
<xsl:variable name="nSpanCols">
<xsl:choose>
<xsl:when test="$bShowScores='Y'">
<xsl:text>7</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>6</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<tr>
<td align="right">Out:</td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nOutYards"/></td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nOutPar"/></td>
<td></td>
<xsl:if test="$bShowScores='Y'">
<td align="center"><xsl:value-of select="$nOutScore"/></td>
</xsl:if>
<td></td>
<td align="right">In:</td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nInYards"/></td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nInPar"/></td>
<td></td>
<xsl:if test="$bShowScores='Y'">
<td align="center"><xsl:value-of select="$nInScore"/></td>
</xsl:if>
</tr>
<tr>
<td colspan="{$nSpanCols}" align="right">Total:</td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nYards"/></td>
<td align="center" bgcolor="Yellow"><xsl:value-of select="$nPar"/></td>
<td></td>
<xsl:if test="$bShowScores='Y'">
<td align="center"><xsl:value-of select="$nScore"/></td>
</xsl:if>
</tr></xsl:template>
</xsl:stylesheet>
Download the XML sample file and the XSL file here:
[attachments docid=146,145]
