
Automatic Numbering, Part 1
by Bob DuCharme
November 06, 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.)