The XML SiteXSLT rules for generating links in Tables of Contents

Automatically generating links in Tables of Contents

We've created the article Table of Contents last time, but we finished without one very important element: links from the TOC items to their respective sections.

To create these links we need two pieces of information: the link's destination and its source. The link source is the place where the link will be placed.

1 Named Anchors

In order to be able to create links from the Table of Contents to the actual content sections, we need to have named anchors in the content itself. The anchor names can be specified as Section IDs.

1.1 XML Schema

We only need to modify the Section schema to allow ID strings.

Section Type1.1.1 The section type

We introduce in the Section schema an optional ID string that we can use as a name for the named anchor.

  <xs:complexType name="sectionType">
    ...
    <xs:attribute name="id" type="xs:string"/>
  </xs:complexType>

1.2 XSLT processing rules

We need to generate anchors for the sections.

1.2.1 The title template

The title template must be modified to call the rule that generates the anchor.

  <xsl:template match="title">
    ...
    <xsl:element name="h{$heading}">
      <xsl:call-template name="anchor"/>
      ...
    </xsl:element>
  </xsl:template>

1.2.2 The anchor template

This template will output a named anchor element. For the name we use the section ID, if one is present. Otherwise a name can be automatically generated by the section-auto-id template.

The section ID auto-generation is turned on by the sheet parameter $section-auto-ids. Its main drawback currently is that is creates anchors for all sections in the website, even if they are never linked to. The manual ID on the other hand, will only generate anchors for the named sections but is more work.

  <xsl:template name="anchor">
    <xsl:choose>
      <xsl:when test="../@id">
        <a name="#{../@id}"/>
      </xsl:when>
      <xsl:when test="$section-auto-ids">
        <a>
          <xsl:attribute name="name">
            <xsl:call-template name="section-auto-id"/>
          </xsl:attribute>
        </a>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

1.2.3 The section-auto-ID template

The auto ID algorithm is very simple - a straight count of the section elements in the document is used.

  <xsl:template name="section-auto-id">
    <xsl:text>section</xsl:text>
    <xsl:number level="any" count="section"/>
  </xsl:template>

2 Internal Links

We need to add the XSLT rules that will create internal links from the Table of Contents to the respective sections.

2.1 The do-toc template

A number of modifications are needed on this existing rule:

First we will output a class attribute on the TOC's list elements, so that we can style them.

  <xsl:template name="do-toc">
    ...
      <xsl:element name="{$element}">
        <xsl:attribute name="class">toc</xsl:attribute>
        ...
  </xsl:template>

Then instead of outputting the title for each section, we will call the link-in template to output a link to the section

Finally, when selecting the subsections to be processed, we will only take into consideration the ones that have IDs specified or auto-generated.

            ...
            <xsl:call-template name="link-in"/>
            <xsl:call-template name="do-toc">
              <xsl:with-param name="entires"
                select="if ($section-auto-ids) then section[title] 
                else section[@id]"/>
            ...

2.2 The link-in template

This named template expects to have a section node selected in the context when called. It will then output an internal link to that section's anchor.

The anchor's name is obtained using the same algorithm used in the anchor generation:

  • the section ID, if available
  • the automatically generated ID if the sheet parameter $section-auto-ids is 'true'
  <xsl:template name="link-in">
    <xsl:variable name="href">
      <xsl:choose>
        <xsl:when test="@id">
          <xsl:value-of select="@id"/>
        </xsl:when>
        <xsl:when test="$section-auto-ids">
          <xsl:call-template name="section-auto-id"/>
        </xsl:when>
      </xsl:choose>
    </xsl:variable>
    <a href="#{$href}">
      <xsl:value-of select="title/text()"/>
    </a>
  </xsl:template>

3 Generated TOCs

We also need to adjust the generated Tables of Contents to support generated links.

3.1 The toc template

The algorithm to determine the initial set of sections on which to generate the TOC is modified as follows:

  • if this is a global TOC
    • if the section IDs are auto generated
      • then select all the titled sections children of the ancestor article
    • else select all sections with IDs that are children of the ancestor article
  • else (this is a local TOC)
    • if the section IDs are auto generated
      • then select all the titled children sections of the parent section
    • else select all sections with IDs that are children of the parent section
      <xsl:with-param name="entires"
        select="if (@global='true') then 
          (if ($section-auto-ids) then 
            ancestor::article/section[title] 
          else ancestor::article/section[@id]) 
        else 
          (if ($section-auto-ids) then 
            ../section[title] 
          else ../section[@id])"/>

The reason is that we use titles as the inclusion criteria only when there are no IDs (explicit or auto generated)

3.2 The title-number template

A fix is need for title numbering: previously we were counting all sections when numbering titles. Now we change that to count only titled sections, and ignore untitled sections.

4 Finally

Download: article files.

Read on: Generating Tables of Contents Revisited.

First Posted: January 18th, 2006 - Wednesday.
Last Updated: February 1st, 2006 - Wednesday.