Menu

Automatic Numbering, Part 1

November 6, 2002

Bob DuCharme

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.)