Create a table of contents using XSLT
Content |
Tested with xsltproc on |
Debian (Etch, Lenny, Squeeze) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Precise, Trusty) |
Tested with Xalan on |
Debian (Etch, Lenny, Squeeze) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Precise, Trusty) |
Tested with Saxon on |
Debian (Etch, Lenny, Squeeze) |
Ubuntu (Hardy, Intrepid, Jaunty, Karmic, Lucid, Maverick, Natty, Precise, Trusty) |
Objective
To create a table of contents using XSLT.
Scenario
Suppose that an XSLT stylesheet is required for processing documents containing the following elements:
name | description |
---|---|
document | the document as a whole |
section | a section of the document |
title | the title of the document or a section |
p | a paragraph |
The document consists of a title followed by one or more sections. A section consists of a title followed by one or more paragraphs. The required output format is HTML, with a table of contents between the document title and the first section. The stylesheet without the table of contents is as follows:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="document"> <html><head> <title><xsl:value-of select="title"/></title> </head><body> <h1><xsl:value-of select="title"/></h1> <xsl:apply-templates/> </body></html> </xsl:template> <xsl:template match="section"> <h2><xsl:value-of select="title"/></h2> <xsl:apply-templates/> </xsl:template> <xsl:template match="title"> </xsl:template> </xsl:stylesheet>
Method
Each section title needs to appear twice in the output document: once in the table of contents,
and once at the start of the section to which it belongs. The simplest way to achieve this in
XSLT is to process the content of the document
element twice by adding a second
apply-templates
element to the document
template:
<xsl:template match="document"> <html><head> <title><xsl:value-of select="title"/></title> </head><body> <h1><xsl:value-of select="title"/></h1> <ol><xsl:apply-templates select="section" mode="toc"/></ol> <xsl:apply-templates/> </body></html> </xsl:template>
The new apply-templates
element one differs from the existing one in that it has
been given two attributes:
-
mode
allows the content of the document to be processed differently during the first pass and the second pass. -
select
ensures that it is onlysection
elements that are processed during the first pass (thereby avoiding the need to provide templates for any other type of element).
All that remains to produce a basic table of contents is to provide an alternative
section
template for use when the mode is equal to toc
:
<xsl:template match="section" mode="toc"> <li><xsl:value-of select="title"/></li> </xsl:template>
Testing
The resulting stylesheet can be tested using the following document:
<?xml version="1.0" encoding="UTF-8"?> <document> <title>Test Document</title> <section> <title>Section One</title> <p>This is section one.</p> </section> <section> <title>Section Two</title> <p>This is section two.</p> </section> </document>
To apply the stylesheet see Process an XML document using an XSLT stylesheet.
Variations
Handling subsections
The stylesheet can be modified as follows to allow for nested subsections. How this is
done depends on how subsections are represented. If section
elements
can include themselves recursively then the corresponding template must be able to expand
itself recursively:
<xsl:template match="section" mode="toc"> <li><xsl:value-of select="title"/></li> <xsl:if test="section"> <ol><xsl:apply-templates select="section" mode="toc"/></ol> </xsl:if> </xsl:template>
Alternatively, if there is a separate subsection
element then the
section
template must be able to call upon the
subsection
template:
<xsl:template match="section" mode="toc"> <li><xsl:value-of select="title"/></li> <xsl:if test="subsection"> <ol><xsl:apply-templates select="subsection" mode="toc"/></ol> </xsl:if> </xsl:template> <xsl:template match="subsection" mode="toc"> <li><xsl:value-of select="title"/></li> </xsl:template>
Making table entries into hyperlinks
If the output format is HTML then the table of contents can be made clickable by placing the
section titles inside a
elements. Titles inside the table are links,
so have href
attributes:
<xsl:template match="section" mode="toc"> <li><a href="{generate-id()}"><xsl:value-of select="title"/></a></li> </xsl:template>
Titles outside the table are anchors, so have name
attributes:
<xsl:template match="section"> <h2><a name="{generate-id()}"><xsl:value-of select="title"/></a></h2> <xsl:apply-templates/> </xsl:template>
Note the use of the generate-id
function. This is used to give each
section a unique identifier so that the relevant table of contents entry can link to it.
generate-id
always returns the same identifier for a given node of the
input document, regardless of where or how many times it is called.
Tags: xslt