The DTD for these documents provides a number of ways of defining lists. For example, an ordered list looks like this:
<p>The design goals for XML are:</p> <olist> <item><p>XML shall be straightforwardly usable over the Internet.</p></item> <item><p>XML shall support a wide variety of applications.</p></item> <item><p>XML shall be compatible with SGML.</p></item> <item><p>It shall be easy to write programs which process XML documents,.</p></item> <item><p>The number of optional features in XML is to be kept to the absolute minimum, ideally zero.</p></item> <item><p>XML documents should be human-legible and reasonably clear.</p></item> <item><p>The XML design should be prepared quickly.</p></item> <item><p>The design of XML shall be formal and concise.</p></item> <item><p>XML documents shall be easy to create.</p></item> <item><p>Terseness in XML markup is of minimal importance.</p></item> </olist>
The rule for ordered lists is interesting, because it uses a recursive named template. The aim here is to decide automatically what kind of numbering to apply to nested levels of list: «1, 2, 3 » for the outermost level, «a, b, c » for the second level, «i, ii, iii » for the third level, and so on.
<!-- olist: an ordered list --> <xsl:template match="olist"> <xsl:variable name="numeration"> <xsl:call-template name="list.numeration"/> </xsl:variable> <xsl:variable name="type"> <xsl:choose> <xsl:when test="$numeration='arabic'">1</xsl:when> <xsl:when test="$numeration='loweralpha'">a</xsl:when> <xsl:when test="$numeration='lowerroman'">i</xsl:when> <xsl:when test="$numeration='upperalpha'">A</xsl:when> <xsl:when test="$numeration='upperroman'">I</xsl:when> <!-- What!? This should never happen --> <xsl:otherwise> <xsl:message> <xsl:text>Unexpected numeration: </xsl:text> <xsl:value-of select="$numeration"/> </xsl:message> <xsl:value-of select="1"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <ol class="enum{$type}"> <xsl:apply-templates/> </ol> </xsl:template> <xsl:template name="list.numeration"> <xsl:param name="node" select="."/> <xsl:choose> <xsl:when test="$node/ancestor::olist"> <xsl:call-template name="next.numeration"> <xsl:with-param name="numeration"> <xsl:call-template name="list.numeration"> <xsl:with-param name="node" select="$node/ancestor::olist[1]"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:call-template name="next.numeration"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="next.numeration"> <xsl:param name="numeration" select="'default'"/> <xsl:choose> <!-- Change this list if you want to change the order of numerations --> <xsl:when test="$numeration = 'arabic'">loweralpha</xsl:when> <xsl:when test="$numeration = 'loweralpha'">lowerroman</xsl:when> <xsl:when test="$numeration = 'lowerroman'">upperalpha</xsl:when> <xsl:when test="$numeration = 'upperalpha'">upperroman</xsl:when> <xsl:when test="$numeration = 'upperroman'">arabic</xsl:when> <xsl:otherwise>arabic</xsl:otherwise> </xsl:choose> </xsl:template>
The recursive call on the «list.numeration » template occurs while calculating a parameter to supply to the «next.numeration » template. The way this works is that if the <olist> element has no <olist> ancestor, it sets the number format to «arabic » . If it does have an <olist> ancestor, it calls the «list.numeration » template supplying the first ancestor as a parameter. The «next.numeration » template supplies the next numbering format in the list: if the input is «arabic » , the output is «loweralpha » , and so on.
I must admit this isn't how I would have written it, though everyone is entitled to their own style. In XSLT 2.0 I would probably define a function:
<xsl:function name="f:olist-format" as="xs:string"> <xsl:param name="node" as="node()"/> <xsl:sequence select="('1', 'a', 'i', 'A', 'I') [count($node/ancestor::olist) mod 5 + 1]"/> </xsl:function>
and then write the template rule for <olist> as:
<xsl:template match="olist"> <ol class="enum{$f:olist-format(.)}"> <xsl:apply-templates/> </ol> </xsl:template>
A reduction from 53 lines to 10 can't be all that bad.