Automatically generating links in Tables of ContentsWe'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 AnchorsIn 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 SchemaWe only need to modify the Section schema to allow ID
strings. 1.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 rulesWe need to generate anchors for the sections. 1.2.1 The title templateThe 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 templateThis 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 templateThe 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 LinksWe 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 templateA 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 templateThis 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 TOCsWe also need to adjust the generated Tables of Contents to
support generated links. 3.1 The toc templateThe 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 templateA 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 FinallyDownload: article files. Read on: Generating Tables of Contents Revisited. First Posted: January 18th, 2006 - Wednesday. Last Updated: February 1st, 2006 - Wednesday. |