Rate this page

Create a table of contents using XSLT

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:

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