Automatic Numbering, Part 1
November 6, 2002
XSLT's xsl:number instruction makes it easy to insert a number into your result document. Its value attribute lets you name the number to insert, but if you really want to add a specific number to your result, it's much simpler to add that number as literal text. When you omit the value attribute from an xsl:value-of instruction, the XSLT processor calculates the number based on the context node's position in the source tree or among the nodes being counted through by an xsl:for-each instruction, which makes it great for automatic numbering.
Eight other attributes are available to tell the XSLT processor how you want your numbers to look. Before we look at them, we'll start by numbering the color names in this simple document (all sample documents and stylesheets are available in this zip file):
<colors> <color>red</color> <color>green</color> <color>blue</color> <color>yellow</color> </colors>
The following template adds a number before each color element and puts a period and a space between the number and the element's content.
<!-- xq395.xsl: converts xq394.xml into xq396.txt --> <xsl:template match="color"> <xsl:number/>. <xsl:apply-templates/> </xsl:template>
The result adds a simple number before each period:
1. red 2. green 3. blue 4. yellow
The format attribute gives you greater control over the numbers' appearance. The following stylesheet adds the color list to the result tree four times, using upper- and lower-case Roman numerals in the format attribute the first two times and upper- and lower-case letters the third and fourth times. In this stylesheet, the period and space are in the format attribute value instead of being literal text after the xsl:number instruction as they were in the example above. This will be useful as we see how to do fancier numbering such as "2.1.3" for a subsection.
<!-- xq397.xsl: converts xq394.xml into xq398.txt --> <xsl:template match="colors"> <xsl:for-each select="color"> <xsl:number format="I. "/><xsl:value-of select="."/><xsl:text> </xsl:text> </xsl:for-each> <xsl:text> ~~~~~~~~~~~~~~~~~~~~~~~ </xsl:text> <xsl:for-each select="color"> <xsl:number format="i. "/><xsl:value-of select="."/><xsl:text> </xsl:text> </xsl:for-each> <xsl:text> ~~~~~~~~~~~~~~~~~~~~~~~ </xsl:text> <xsl:for-each select="color"> <xsl:number format="A. "/><xsl:value-of select="."/><xsl:text> </xsl:text> </xsl:for-each> <xsl:text> ~~~~~~~~~~~~~~~~~~~~~~~ </xsl:text> <xsl:for-each select="color"> <xsl:number format="a. "/><xsl:value-of select="."/><xsl:text> </xsl:text> </xsl:for-each> </xsl:template>
To output the same list from the XML document above four times, this stylesheet has a single template rule for the list's parent element, colors. Its template has four xsl:for-each elements, one for each pass through the list. (XSLT's xsl:for-each elements are a popular place to use the xsl:number instruction, because a template that needs to iterate through a list of nodes and add nodes to the result tree may well want to add them with numbers or letters in front of them.)
The stylesheet also has xsl:text nodes to add carriage returns after each color name and a line of tildes between each set of color names. Using the same color names input document, this stylesheet creates the following result document:
I. red II. green III. blue IV. yellow ~~~~~~~~~~~~~~~~~~~~~~~ i. red ii. green iii. blue iv. yellow ~~~~~~~~~~~~~~~~~~~~~~~ A. red B. green C. blue D. yellow ~~~~~~~~~~~~~~~~~~~~~~~ a. red b. green c. blue d. yellow
The next example shows how leading zeros before a "1" in a format attribute tell the XSLT processor to pad the number with zeros to make it the width shown. The "001. " in the template above will (in addition to adding a period and a space after each number) add as many zeros as necessary before each number to make it three digits wide.
<!-- xq399.xsl: converts xq400.xml into xq401.txt --> <xsl:template match="color"> <xsl:number format="001. "/><xsl:apply-templates/> </xsl:template>
For example, it converts this source document
<colors> <color>red</color> <color>green</color> <color>blue</color> <color>yellow</color> <color>purple</color> <color>brown</color> <color>orange</color> <color>pink</color> <color>black</color> <color>white</color> <color>gray</color> </colors>
to this:
001. red 002. green 003. blue 004. yellow 005. purple 006. brown 007. orange 008. pink 009. black 010. white 011. gray
The xsl:number element's grouping-separator and grouping-size attributes let you add punctuation to larger numbers to make them easier to read. For example, a grouping-separator value of "," and a grouping-size value of "3" put commas before each group of three digits in numbers over 999, so that 10000000 gets formatted as 10,000,000. (These two attributes work as a pair -- if you use either without the other, the XSLT processor ignores it.)
If a value is specified for the xsl:number element's lang attribute, an XSLT processor may check it and adjust the formatting of the numbers or letters to reflect the conventions of the specified language.
The xsl:number element's letter-value attribute also makes it easier to follow the numbering conventions of other languages -- it can have a value of either "alphabetic" or "traditional" to distinguish between the use of letters as letters and their use in some other numbering system. For example, the English language uses the letters I, V, X, C, M, and others for Roman numerals, in which case they're certainly not listed in alphabetical order. XSLT processors have built-in recognition of the difference between using these characters as alphabetic characters and as Roman numerals. (See the use of the format attribute above.) But for similar cases with other spoken languages, the letter-value attribute can make the stylesheet developer's intent clearer.
The level attribute specifies which source tree levels will be counted for the xsl:number element's value. Its default value is "single". A level value of "multiple" lets you count nested elements such as the color elements in this document:
<colors> <color>red</color> <color>green</color> <color>blue <color>robin's egg</color> <color>navy</color> <color>cerulean</color> </color> <color>yellow</color> </colors>
Note how the color element with "blue" as a value has three more color elements inside of it. To number this nested list along with the main color list, the following template rule has a value of "multiple" specified for the level attribute:
<!-- xq403.xsl: converts xq402.xml into xq404.txt --> <xsl:template match="color"> <xsl:number level="multiple" format="1. "/> <xsl:apply-templates/> </xsl:template>
When processing the XML document above, this stylesheet numbers the color "blue" as "3." and the list of colors inside of it as "3.1.", "3.2.", and "3.3.":
1. red 2. green 3. blue 3.1. robin's egg 3.2. navy 3.3. cerulean 4. yellow
The level attribute can also let you do this with elements that are nested inside of other kinds of elements. When you do this, the count and from attributes give you greater control over what gets counted for each level of numbering. To show what these attributes can do when working together, we'll use this DocBook document.
<book><title>Title of Book</title> <chapter><title>First Chapter</title> <sect1><title>First Section, First Chapter</title> <figure><title>First picture in book</title> <graphic fileref="pic1.jpg"/></figure> </sect1> </chapter> <chapter><title>Second Chapter</title> <sect1><title>First Section, Second Chapter</title> <sect2> <title>First Subsection, First Section, Second Chapter</title> <figure><title>Second picture in book</title> <graphic fileref="pic2.jpg"/></figure> </sect2> <sect2> <title>Second Subsection, First Section, Second Chapter</title> <figure><title>Third picture in book</title> <graphic fileref="pic1.jpg"/></figure> </sect2> <sect2> <title>Third Subsection, First Section, Second Chapter</title> <figure><title>Fourth picture in book</title> <graphic fileref="pic1.jpg"/></figure> </sect2> </sect1> <sect1><title>Second Section, Second Chapter</title> <para>The End.</para> </sect1> </chapter> </book>
This next template rule resembles the one that numbered the nested list of colors. It numbers the sect1 elements and has a value of "multiple" for the xsl:number instruction's level attribute.
<!-- xq406.xsl: converts xq405.xml into xq407.txt --> <xsl:template match="sect1"> <xsl:number format="1. " level="multiple"/> <xsl:apply-templates/> </xsl:template>
The result numbers the sect1 elements, but only the sect1 elements:
Title of Book First Chapter 1. First Section, First Chapter First picture in book Second Chapter 1. First Section, Second Chapter First Subsection, First Section, Second Chapter Second picture in book Second Subsection, First Section, Second Chapter Third picture in book Third Subsection, First Section, Second Chapter Fourth picture in book 2. Second Section, Second Chapter The End.
Next month, we'll see how to number the different chapter, sect1, and sect2 elements as 1., 1.1, 1.1.1, and so forth, with numbering levels restarting at appropriate places. We'll also see how to number the pictures in the book automatically, both as one sequence that never restarts at "1" and also as a sequence that restarts with each new chapter. Finally, we'll learn some advantages and disadvantages of using the position() function in an xsl:value-of element as an alternative to the xsl:number instruction. (If you really can't wait, see my book XSLT Quickly.)