Using XML Queries and Transformations

Number Calculation Example

As we saw earlier, the simplest way to calculate the number that will be output to the destination document is using the value attribute. Any expression that can be converted to a number can be used here. A more complex, but in some cases very powerful, way to calculate the number is using the level, count and from attributes. It is used whenever the value property is not used.

We will explain the workings of the number element by example. Imagine an XML document containing the full text of a book. The book is divided into chapters (CH elements), sections (SEC elements) and paragraphs (P elements). Within a paragraph, we want to create a paragraph title, including the chapter number, section number, paragraph number, etc. These numbers are not really content; they follow from the structure of the content. We would really like Chapter 1 to be called 'Introduction', not '1. Introduction'. Still, in the final hardcopy (or web page or Acrobat document etc.) we want the number to show up. So we will let the XSLT processor do the counting and insert the numbering on the fly. This is exactly what the number element is good at. Let's have a look at our book document:

level attribute

We are transforming the context node at the bottom of the diagram. There are three modes for counting nodes, 'single', 'multiple' and 'any'. The counting mode is set using the level attribute. The default mode is 'single'.

level='single'

The count attribute specifies which kind of nodes you want to count. If the level attribute is set to 'single', the processor will search along the ancestor axis for a node that matches the count attribute. If the count attribute is empty, it uses the context node itself. Once the processor has found a matching ancestor, it counts the number of preceding siblings that also match the count attribute and adds one. It's quite complex, right? Look at the diagram above. Suppose we want to display the paragraph number of the paragraph our context node is part of. That would be 2 – i.e. it is the second paragraph in the section. To display this the following code would be used:

<xsl:number level='single' count='P'/>

The processor goes up from the context node until it finds a node that matched 'P'. Then it looks at this node's preceding siblings and counts the number of them that match the count attribute (1). It adds one to that, returning 2. The chapter number would similarly be returned by:

<xsl:number level='single' count='CH'/>

The from attribute allows us to look only at a part of the ancestor axis. If the from attribute is specified, the processor will first search for an ancestor that matches the from attribute. After that, it will search for the node that will be counted using the count attribute, but it will not look past the node that was matched by the from attribute. This allows you to narrow down the counting to a subtree of the document.

Using the 'multiple' mode is very much like the 'single' mode, but it can return more values at once:

level='multiple'

This is useful for creating paragraph numbers like §2.2.2. The processor will search along the ancestor axis for all nodes matching the count attribute. Each matching node will be used to calculate a number (just like in single mode, counting preceding siblings). A list of numbers is returned, in document order. Therefore this line will return a list with the current chapter number, section number and paragraph number, in that order:

<xsl:number level='multiple' count='CH|SEC|P'/>

It is up to the number-to-string formatting attributes to output this list as an understandable format.

Note that you may run into trouble if your document structure is not as clean as in this sample. If chapters are not siblings of each other, the numbering will go wrong. Also, try to think about what happens if P elements are not only part of SEC elements, but can also appear directly in a CH element. The P elements would become siblings to the SEC elements and be included in the section numbering.

If the level attribute is set to 'any', the processor counts all nodes matching the pattern in the count attribute that occur in the document before the context node (including the context node itself and its ancestors):

level='any'

This can be used for counting the number of a certain kind of node throughout the document (typically 'notes' and 'diagrams'). If the from attribute is specified, the processor searches backward from the context node for the first node matching that specified by the from attribute. Then it counts all nodes matching the count attribute between the 'from node' and the context node.

Let's look at a few examples using the document structure from the diagram:

XSLT Element

Number Value

<xsl:number level='single' count='CH|SEC' />

2

<xsl:number level='multiple' count='CH|SEC' />

2, 2

<xsl:number level='any' count='CH|SEC' />

7

<xsl:number level='any' count='P' />

11

<xsl:number level='any' count='P' from='CH'/>

5

To output numeric values as a string, the number element specifies a set of attributes. We will not cover all details of formatting numeric values here. Numbering is a lot more complicated than you probably think. Ways of numbering include the obvious ones such as Arabic numbers (1, 2, 3, …), letters (a, b, c, …) and Roman numbers (I, II, III, …). But there are many more. Think of all languages using other character sets. Even many languages that use normal Latin characters use other letter orders when counting. Some languages (Hebrew, Greek) have a special non-alphabetic order of letters especially for numbering. While the specification more or less tries to address these issues, in this book we will assume that you want to use one of the numbering types mentioned above, and will refrain from using traditional Georgian numbering! If you need to use more exotic numbering types, check if the XSLT implementation supports them. Most implementations will not.

format attribute

The most important attribute for formatting numbers is the format attribute. The format attribute specifies the formatting for a list of numeric values. The format string consists of alphanumeric parts, separated by non-alphanumeric parts. When a list of numbers is formatted, the nth alphanumeric part of the format is used for the nth number. If there are more numbers than formats, the last format is used for the remaining numbers. The default format (to be used if nothing is specified or if the specified format is not supported by the XSLT implementation) is '1'. These are the most common formats:

Format String

Name

Example

"1"

Arabic

1, 2, 3, 4, …

"I"

Roman capitals

I, II, III, IV, …

"i"

Roman lower

i, ii, iii, iv, …

"a"

Alphabetic lower

a, b, c, d, …, z, aa, ab, …

"A"

Alphabetic capitals

A, B, C, D, …

"01"

Arabic with trailing zero

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“The difference between theory and practice is smaller in theory than in practice.”