Numbering Things
XSLT provides the <xsl:number>
element to number the parts
of a document. (It can also be used to format a numeric value; more on
that later.) In general, <xsl:number>
counts something. We’ll
look at a variety of examples here.
To fully illustrate how <xsl:number>
works, we’ll need an XML
document with some things to count. We’ll reuse our list of cars from
the previous section:
<?xml version="1.0" encoding="utf-8"?>
<!-- cars.xml -->
<cars>
<manufacturer name="Chevrolet">
<car>Cavalier</car>
<car>Corvette</car>
<car>Impala</car>
<car>Malibu</car>
</manufacturer>
<manufacturer name="Ford">
<car>Pinto</car>
<car>Mustang</car>
<car>Taurus</car>
</manufacturer>
<manufacturer name="Volkswagen">
<car>Beetle</car>
<car>Jetta</car>
<car>Passat</car>
<car>Touraeg</car>
</manufacturer>
</cars>
We’ll use <xsl:number>
in
several different ways to illustrate the various options we have in
numbering things. We’ll start with something simple:
<?xml version="1.0" encoding="utf-8"?>
<!-- number1.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title>Automobile manufacturers and their cars</title>
</head>
<body>
<xsl:for-each select="cars/manufacturer">
<p>
<xsl:number format="1. "/>
<xsl:value-of select="@name"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
We get this HTML document:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Automobile manufacturers and their cars</title> </head> <body> <p>1. Chevrolet</p> <p>2. Ford</p> <p>3. Volkswagen</p> </body> </html>
This is about the simplest example of <xsl:number>
that you can write. (You
could leave off the format
attribute,
but you’d get paragraphs such as <p>1Chevrolet</p>
—probably not
what you want.) Changing the stylesheet to use format="a. "
generates these paragraphs:
<p>a. Chevrolet</p> <p>b. Ford</p> <p>c. Volkswagen</p>
Here’s what we get with format="i.
"
:
<p>i. Chevrolet</p> <p>ii. Ford</p> <p>iii. Volkswagen</p>
The <xsl:number>
element
has lots of other attributes and capabilities; we’ll look at the most
common ones here. (See the complete description of the <xsl:number>
element in Appendix A.)
Here’s an example that uses the value
attribute:
<?xml version="1.0" encoding="utf-8"?>
<!-- number2.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<head>
<title>Automobile manufacturers and their cars</title>
</head>
<body>
<xsl:for-each select="cars/manufacturer">
<p>
<xsl:text>Cars produced by </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>: </xsl:text>
<xsl:number value="count(car)" format="01"/>
</p>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
This stylesheet generates this HTML document:
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Automobile manufacturers and their cars</title> </head> <body> <p>Cars produced by Chevrolet: 04</p> <p>Cars produced by Ford: 03</p> <p>Cars produced by Volkswagen: 04</p> </body> </html>
In this case, we could have used <xsl:value-of select="count(car)"/>
to
get similar results, but <xsl:number>
lets us format the number
as we want.
Using <xsl:number>
with
the format
attribute is a good way to format any value, whether it comes from
the XML source or not. For example, this markup:
<xsl:number value="1965" format="I"/>
produces the text MCMLXV
.
As you’d expect, there are more powerful things we can do. Here’s
an example that uses the level
and
count
attributes:
<?xml version="1.0" encoding="utf-8"?>
<!-- number3.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Automobile manufacturers and their cars
</xsl:text>
<xsl:for-each select="cars/manufacturer">
<xsl:number count="manufacturer" format="1. "/>
<xsl:value-of select="@name"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="car">
<xsl:number count="manufacturer|car" level="multiple"
format="1.1. "/>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This stylesheet gives us the following text document:
Automobile manufacturers and their cars 1. Chevrolet 1.1. Cavalier 1.2. Corvette 1.3. Impala 1.4. Malibu 2. Ford 2.1. Pinto 2.2. Mustang 2.3. Taurus 3. Volkswagen 3.1. Beetle 3.2. Jetta 3.3. Passat 3.4. Touraeg
The count
attribute tells the XSLT processor what elements to count, and
level=
"multiple"
counts the manufacturers at
one level and the cars per manufacturer at another. Notice that in the
second <xsl:for-each<
element
we used the attribute count="manufacturer|car"
, even though
we’re looking only at <car>
elements. That’s because the number 3.2
means the second <car>
from the third <manufacturer>
. If we don’t include the
manufacturers in our count, we won’t get the results we want.
The values for level
are
single
, multiple
, and any
. The value single
, the default, counts only an item’s
siblings, while multiple
counts an
item along with any of its ancestors (that’s what we did in the previous
example). level="any"
counts an item
along with everything that occurred before it in the document, whether
it’s an ancestor of the current item or not. Here’s an example that uses
level="any"
:
<?xml version="1.0" encoding="utf-8"?>
<!-- number4.xsl -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Automobile manufacturers and their cars
</xsl:text>
<xsl:for-each select="cars/manufacturer">
<xsl:number count="manufacturer|car" level="any" format="1. "/>
<xsl:value-of select="@name"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="car">
<xsl:number count="manufacturer|car" level="any" format="1. "/>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here we use an identical <xsl:number>
element in both places.
We’re counting all of the <manufacturer>
and <car>
elements, so we need to use
level="any"
and count=
"manufacturer|car"
in both places. Here
are the results:
Automobile manufacturers and their cars 1. Chevrolet 2. Cavalier 3. Corvette 4. Impala 5. Malibu 6. Ford 7. Pinto 8. Mustang 9. Taurus 10. Volkswagen 11. Beetle 12. Jetta 13. Passat 14. Touraeg
Also keep in mind that you can use <xsl:number>
at isolated times. Here’s a
contrived example that counts only even-numbered cars from each
manufacturer:
<?xml version="1.0" encoding="utf-8"?>
<!-- number5.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:text>Automobile manufacturers and their cars
</xsl:text>
<xsl:for-each select="cars/manufacturer">
<xsl:value-of select="@name"/>
<xsl:text>
</xsl:text>
<xsl:for-each select="car">
<xsl:text> </xsl:text>
<xsl:if test="(position() mod 2) = 0">
<xsl:number count="manufacturer|car" level="multiple"
format="1.1. "/>
</xsl:if>
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
To select only even-numbered items, we use the XPath mod
operator. If we divide the position of the current element by 2
and the result is zero, we know we have an even-numbered item. This
stylesheet gives us the following list:
Automobile manufacturers and their cars Chevrolet Cavalier 1.2. Corvette Impala 1.4. Malibu Ford Pinto 2.2. Mustang Taurus Volkswagen Beetle 3.2. Jetta Passat 3.4. Touraeg
In this example, we used <xsl:number>
only for even-numbered cars
from each manufacturer, and we never used <xsl:number>
for the <manufacturer>
element at all. Despite
that, the <xsl:number>
element
calculates the correct value based on the position of the current item
in the source document.
That can lead to some complications, however. If we sort the
source document as we process it, our numbers look a little strange.
We’ll add a <xsl:sort>
element
to our stylesheet:
<?xml version="1.0" encoding="utf-8"?> <!-- number6.xsl --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:text>Automobile manufacturers and their cars
</xsl:text> <xsl:for-each select="cars/manufacturer"> <xsl:value-of select="@name"/> <xsl:text>
</xsl:text> <xsl:for-each select="car"> <xsl:sort select="."/> <xsl:text> </xsl:text> <xsl:if test="(position() mod 2) = 0"> <xsl:number count="manufacturer|car" level="multiple" format="1.1. "/> </xsl:if> <xsl:value-of select="."/> <xsl:text>
</xsl:text> </xsl:for-each> </xsl:for-each> </xsl:template> </xsl:stylesheet>
And our results look like this:
Automobile manufacturers and their cars Chevrolet Cavalier 1.2. Corvette Impala 1.4. Malibu Ford Mustang 2.1. Pinto Taurus Volkswagen Beetle 3.2. Jetta Passat 3.4. Touraeg
The number for Pinto
isn’t what
we expected. That’s because the position()
function is based on the
current (sorted) context, while the numbering we’re doing is based on
the original document order. In the sorted order Pinto
is the second item, so the test
of our <xsl:if>
element is true. When we use
<xsl:number>
, however, Pinto
is the first <car>
in the source document. The result
is the number 2.1
instead of the
2.2
we expected.
It’s not pretty, but here’s a stylesheet that fixes the problem.
We use <xsl:number>
to count
<manufacturer>
elements; that
generates the first part of the number. Next we use position()
to output the position of the
sorted element. The stylesheet looks like
this:
<?xml version="1.0" encoding="utf-8"?> <!-- number7.xsl --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text"/> <xsl:template match="/"> <xsl:text>Automobile manufacturers and their cars
</xsl:text> <xsl:for-each select="cars/manufacturer"> <xsl:value-of select="@name"/> <xsl:text>
</xsl:text> <xsl:for-each select="car"> <xsl:sort select="."/> <xsl:text> </xsl:text> <xsl:if test="(position() mod 2) = 0"> <xsl:number count="manufacturer" level="multiple" format="1."/> <xsl:value-of select="position()"/> <xsl:text>. </xsl:text> </xsl:if> <xsl:value-of select="."/> <xsl:text>
</xsl:text> </xsl:for-each> </xsl:for-each> </xsl:template> </xsl:stylesheet>
This numbers the Pinto
using
the sorted order:
Automobile manufacturers and their cars Chevrolet Cavalier 1.2. Corvette Impala 1.4. Malibu Ford Mustang 2.2. Pinto Taurus Volkswagen Beetle 3.2. Jetta Passat 3.4. Touraeg
We used <xsl:number>
to count the
position of the current car within the current manufacturer.
Get XSLT, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.