Working with Numbered Lists

Contact Us or call 1-877-932-8228
Working with Numbered Lists

Working with Numbered Lists

The position() function

The position() function returns the position of the context node in a selected set of nodes. The following XML and XSLT documents illustrate how position() works.

Code Sample:

AdvancedXsltTechniques/Demos/BeatlesPosition.xml
<?xml version="1.0"?>
<?xml-stylesheet href="BeatlesPosition.xsl" type="text/xsl"?>
<beatles>
	<beatle link="http://www.paulmccartney.com">
		<name>
			<firstname>Paul</firstname>
			<lastname>McCartney</lastname>
		</name>
	</beatle>
	<beatle link="http://www.johnlennon.com">
		<name>
			<firstname>John</firstname>
			<lastname>Lennon</lastname>
		</name>
	</beatle>
	<beatle link="http://www.georgeharrison.com">
		<name>
			<firstname>George</firstname>
			<lastname>Harrison</lastname>
		</name>
	</beatle>
	<beatle link="http://www.ringostarr.com">
		<name>
			<firstname>Ringo</firstname>
			<lastname>Starr</lastname>
		</name>
	</beatle>
	<beatle link="http://www.webucator.com" real="no">
		<name>
			<firstname>Nat</firstname>
			<lastname>Dunn</lastname>
		</name>
	</beatle>
</beatles>

Code Sample:

AdvancedXsltTechniques/Demos/BeatlesPosition.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/beatles">
	<xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="beatle">
	<xsl:value-of select="position()"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="name/lastname"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template> 

</xsl:stylesheet>

The result of transforming AdvancedXsltTechniques/Demos/BeatlesPosition.xml against AdvancedXsltTechniques/Demos/BeatlesPosition.xsl is shown below.

1. McCartney 2. Lennon 3. Harrison 4. Starr 5. Dunn

Notice that the resulting list is numbered correctly. So far, so good. But look what happens when we add non-beatle sibling to the beatle elements.

Code Sample:

AdvancedXsltTechniques/Demos/BeatlesPosition-Oops.xml
<?xml version="1.0"?>
<?xml-stylesheet href="BeatlesPosition.xsl" type="text/xsl"?>
<beatles>
	<beatle link="http://www.paulmccartney.com">
		<name>
			<firstname>Paul</firstname>
			<lastname>McCartney</lastname>
		</name>
	</beatle>
	<beatle link="http://www.johnlennon.com">
		<name>
			<firstname>John</firstname>
			<lastname>Lennon</lastname>
		</name>
	</beatle>
	<oops />
	<beatle link="http://www.georgeharrison.com">
		<name>
			<firstname>George</firstname>
			<lastname>Harrison</lastname>
		</name>
	</beatle>
	<beatle link="http://www.ringostarr.com">
		<name>
			<firstname>Ringo</firstname>
			<lastname>Starr</lastname>
		</name>
	</beatle>
	<beatle link="http://www.webucator.com" real="no">
		<name>
			<firstname>Nat</firstname>
			<lastname>Dunn</lastname>
		</name>
	</beatle>
</beatles>

As the result below shows, when we transform the file above against the same XSLT, the numbering is incorrect.

1. McCartney 2. Lennon 4. Harrison 5. Starr 6. Dunn

This is because position() function returns the position of the current node within the selected node set and the <Oops/> element is in the selected node set, right between John Lennon and George Harrison.

This can be corrected by selecting a specific node-set with the xsl:apply-templates tag, but that can cause other problems.

xsl:number

The <xsl:number/> tag provides another mechanism for numbering elements. It counts nodes that match a specified XPath. It works as follows.

First, xsl:number takes a count attribute, which takes an XPath indicating which elements to count. When not specified, the default of count is the name of the current element. When specified, the processor first looks at the current node and then looks up the tree at the current node's ancestors until it finds an element that matches the XPath specified in count. It stops as soon as it finds a match.

Second, xsl:number takes a level attribute, which determines how many levels of the XML document should be examined for nodes that match the pattern specified in the count attribute. Possible values are single, multiple and any. We'll look at examples of each of these.

level = "single"

When level="single" (the default), a count is done of the node matched in the count attribute plus all preceding siblings that also match the XPath specified in count.

Code Sample:

AdvancedXsltTechniques/Demos/BeatlesNumbered.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/beatles">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="beatle">
	<xsl:number level="single" count="beatle"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="name/lastname"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template> 

</xsl:stylesheet>

Notice that AdvancedXsltTechniques/Demos/BeatlesNumbered.xsl has the <oops/> element between John Lennon and George Harrison; however, unlike with xsl:position, that element does not affect the output. This is because xsl:number is only counting the beatle elements. The result of transforming AdvancedXsltTechniques/Demos/BeatlesNumbered.xml against AdvancedXsltTechniques/Demos/BeatlesNumbered.xsl is shown below.

1. McCartney 2. Lennon 3. Harrison 4. Starr 5. Dunn

If xsl:number were changed to look like this: <xsl:number level="single" count="*"/>, then <oops/> would also be counted and the numbering would again be wrong.

level="any"

When level="any", a count is done of the node matched in the count attribute plus all preceding elements that also match the XPath specified in count.

To illustrate how any works, we'll first take a look at another example using level="single".

Code Sample:

AdvancedXsltTechniques/Demos/RockStars-Single.xml
<?xml version="1.0"?>
<?xml-stylesheet href="RockStars-Single.xsl" type="text/xsl"?>
<rockstars>
<beatles>
	<beatle link="http://www.paulmccartney.com">
		<name>
			<firstname>Paul</firstname>
			<lastname>McCartney</lastname>
		</name>
	</beatle>
	<beatle link="http://www.johnlennon.com">
		<name>
			<firstname>John</firstname>
			<lastname>Lennon</lastname>
		</name>
	</beatle>
</beatles>
<stones>
	<stone link="http://www.mickjagger.com" >
		<name>
			<firstname>Mick</firstname>
			<lastname>Jagger</lastname>
		</name>
	</stone>
	<stone link="http://www.keithrichards.com" >
		<name>
			<firstname>Keith</firstname>
			<lastname>Richards</lastname>
		</name>
	</stone>
</stones>
</rockstars>

Code Sample:

AdvancedXsltTechniques/Demos/RockStars-Single.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/beatles">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="beatle | stone">
	<xsl:number level="single" count="beatle | stone"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="name/lastname"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template> 

</xsl:stylesheet>

The result of transforming AdvancedXsltTechniques/Demos/RockStars-Single.xml against AdvancedXsltTechniques/Demos/RockStars-Single.xsl is shown below.

1. McCartney 2. Lennon 1. Jagger 2. Richards

Notice that the counting is done at a single level. In other words, only siblings are counted.

Now let's see what happens when level="single" is changed to level="any" as shown in AdvancedXsltTechniques/Demos/RockStars-Any.xsl.

Code Sample:

AdvancedXsltTechniques/Demos/RockStars-Any.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/beatles">
	<xsl:apply-templates/>
</xsl:template>

<xsl:template match="beatle | stone">
	<xsl:number level="any" count="beatle | stone"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="name/lastname"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template> 

</xsl:stylesheet>

The result of transforming AdvancedXsltTechniques/Demos/RockStars-Any.xml against AdvancedXsltTechniques/Demos/RockStars-Any.xsl is shown below.

1. McCartney 2. Lennon 3. Jagger 4. Richards

As you can see, the counting is now done across levels.

level="multiple"

When level="multiple", multiple counts are done at different levels. To determine the results, the processor goes through the following steps.

  1. It creates a list of all the ancestors of the current node and the current node itself.
  2. It throws out any nodes in the list that do not match the XPath specified in the count attribute.
  3. It maps each node in the list to a number, which is calculated by counting the node's preceding siblings that match the XPath specified in the count attribute and adding one (for the node itself).

Code Sample:

AdvancedXsltTechniques/Demos/Outline.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="Outline.xsl"?>
<outline>
	<topic>XML Basics<topic>What is XML?</topic>
		<topic>XML Benefits<topic>XML Holds Data, Nothing More</topic>
			<topic>XML Separates Structure from Formatting</topic>
			<topic>XML Promotes Data Sharing</topic>
			<topic>XML is Human-Readable</topic>
			<topic>XML is Free</topic>
		</topic>
		<topic>XML Documents<topic>The Prolog</topic>
			<topic>Elements</topic>
			<topic>Attributes</topic>
			<topic>CDATA</topic>
			<topic>XML Syntax Rules</topic>
			<topic>Special Characters</topic>
		</topic>
		<topic>Creating a Simple XML File</topic>
	</topic>
</outline>

Code Sample:

AdvancedXsltTechniques/Demos/Outline.xsl
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="/">
	<xsl:apply-templates select="outline//*"/>
</xsl:template>

<xsl:template match="outline//*">
	<xsl:for-each select="ancestor::*">
		<xsl:text>&#09;</xsl:text>
	</xsl:for-each>
	<xsl:number level="multiple" count="*"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="text()"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template>

</xsl:stylesheet>

The result of transforming AdvancedXsltTechniques/Demos/Outline.xml against AdvancedXsltTechniques/Demos/Outline.xml is shown below.

1.1. XML Basics 1.1.1. What is XML? 1.1.2. XML Benefits 1.1.2.1. XML Holds Data, Nothing More 1.1.2.2. XML Separates Structure from Formatting 1.1.2.3. XML Promotes Data Sharing 1.1.2.4. XML is Human-Readable 1.1.2.5. XML is Free 1.1.3. XML Documents 1.1.3.1. The Prolog 1.1.3.2. Elements 1.1.3.3. Attributes 1.1.3.4. CDATA 1.1.3.5. XML Syntax Rules 1.1.3.6. Special Characters 1.1.4. Creating a Simple XML File

The tabbing is generated with the <xsl:for-each>, which renders one tab for each ancestor of the current element. As the processor looks deeper into the document, the elements have more ancestors and, therefore, are preceded by more tabs.

The first number "1" of each line in the output above represents the Outline element, which is the document element. Because a well-formed XML document can only have one document element, every list generated with xsl:number in this way will start every list item with the number "1". This can be fixed with the from attribute.

The from Attribute

The from attribute takes a regular expression specifying where counting should start.

Code Sample:

AdvancedXsltTechniques/Demos/OutlineFrom.xsl
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
				 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="/">
	<xsl:apply-templates select="outline//*"/>
</xsl:template>

<xsl:template match="outline//*">
	<xsl:for-each select="ancestor::*">
		<xsl:text>&#09;</xsl:text>
	</xsl:for-each>
	<xsl:number level="multiple" count="*" from="outline"/>
	<xsl:text>. </xsl:text>
	<xsl:value-of select="text()"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template>

</xsl:stylesheet>

The result of transforming AdvancedXsltTechniques/Demos/OutlineFrom.xml against AdvancedXsltTechniques/Demos/OutlineFrom.xsl is shown below.

1. XML Basics 1.1. What is XML? 1.2. XML Benefits 1.2.1. XML Holds Data, Nothing More 1.2.2. XML Separates Structure from Formatting 1.2.3. XML Promotes Data Sharing 1.2.4. XML is Human-Readable 1.2.5. XML is Free 1.3. XML Documents 1.3.1. The Prolog 1.3.2. Elements 1.3.3. Attributes 1.3.4. CDATA 1.3.5. XML Syntax Rules 1.3.6. Special Characters 1.4. Creating a Simple XML File

Notice that the initial "1" is now stripped. That's because the processor is now counting from the Outline element rather than from the root node.

Formatting Numbers

In the last example, you may have noticed that the levels are all represented with Arabic numbers. The formatting can be changed with the format attribute, which takes a format token, which is a delimited list of characters. The same delimiter used in the format token is used in the output.

Code Sample:

AdvancedXsltTechniques/Demos/OutlineFormatted.xsl
<?xml version='1.0'?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>

<xsl:template match="/">
	<xsl:apply-templates select="outline//*"/>
</xsl:template>

<xsl:template match="outline//*">
	<xsl:for-each select="ancestor::*">
		<xsl:text>&#09;</xsl:text>
	</xsl:for-each>
	<xsl:number level="multiple" count="*" format="I.A.i.a. "/>
	<xsl:value-of select="text()"/>
	<xsl:text>&#10;</xsl:text>
</xsl:template>

</xsl:stylesheet>

In earlier examples, we placed a period and a space after the number by putting <xsl:text>. </xsl:text> after xsl:number. In this example, we accomplished the same thing by placing a period and a space at the end of the format token.

Next