The XML SiteXSL templates for creating and updating Tables of Contents

Generating Tables of Contents Revisited

After finishing the TOC XSL templates, I was rather unhappy with the result. The code itself was messy, complicated and using the templates was not very flexible. Specific behaviors were controlled by sheet-wide parameters making impossible having two different TOC styles in the same XML.

Contents

1 Rationale

So I decided to fix it. The first idea was to add style parameters on the toc elements and to allow TOCs to directly target section elements. But that complicated the code even more and still left unsolved situation like the case when you want to have a bunch of sections in the content but you don't want to put them under an enclosing section because that has certain implications like a decreased heading level, etc.

Finally I realised that I was trying to represent two semantically different things using a single XML element. The obvious solution is to keep the section element doing what it was already doing and to create a new element to fulfill the TOC roles: content.

Implementing this was surprisingly easy, probably because it was mostly refactoring and refining previous code.

2 XML Schema

We will modify the XML schema to support the new elements and to remove certain declarations that are not needed anymore.

The main addition is the content element. It can appear anywhere a section can appear, except inside another content element. Everything inside the content element can then be processed for TOC purposes.

Article Type2.1 The article type

The article type must be changed to accept a content or section element everywhere it used to accept a section before. The exception is the content element itself, which is not recursive.

We accomplish this by accepting the content-or-section group.

  <xs:complexType name="articleType">
    <xs:complexContent>
      <xs:extension base="pageType">
        <xs:group ref="content-or-section" maxOccurs="unbounded"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

Content-or-Section Group2.2 The content-or-section group

This groups content and section elements. The section elements accept embedded content elements.

  <xs:group name="content-or-section">
    <xs:annotation>
    <xs:choice>
      <xs:element name="section" type="content-sectionType"/>
      <xs:element name="content" type="contentType"/>
    </xs:choice>
  </xs:group>

Content Type2.3 The content type

The content element is just a sequence of sections. The sections are of section type, and thus they don't allow embedded content elements.

An optional id attribute can be used to identify it and an optional numbering attribute turns on the title numbering for the embedded sections.

  <xs:complexType name="contentType">
    <xs:sequence>
      <xs:element name="section" type="sectionType" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="id" type="xs:string"/>
    <xs:attribute name="numbering" type="xs:boolean"/>
  </xs:complexType>

Content-Section Type2.4 The content-section type

The content-section type is an extension type of the section-base type that accepts embedded section or content elements. That means that it isn't itself embedded in a content element.

  <xs:complexType name="content-sectionType">
    <xs:complexContent>
      <xs:extension base="section-baseType">
        <xs:group ref="content-or-section" minOccurs="0" maxOccurs="unbounded"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

Section-Base Type2.5 The section-base type

This type is intended to be used as a base for more complete section types. It contains the common element definitions. It is extended by:

  • content-section type
  • section type
  <xs:complexType name="section-baseType">
    <xs:sequence>
      <xs:element name="title" type="xs:string" minOccurs="0"/>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="toc" type="tocType"/>
        <xs:element name="paragraph" type="xs:string"/>
      </xs:choice>
    </xs:sequence>
    <xs:attribute name="id" type="xs:string"/>
  </xs:complexType>

Section Type2.6 The section type

The section type is an extension type of the section-base type that accepts embedded section elements. It does not accept embedded content elements, because it itself is embedded in a content element.

  <xs:complexType name="sectionType">
    <xs:complexContent>
      <xs:extension base="section-baseType">
        <xs:sequence>
          <xs:element name="section" type="sectionType" minOccurs="0"
            maxOccurs="unbounded"/>
        </xs:sequence>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

2.7 The toc type

There is only one modification to the old toc type: the global attribute was replaced by the content attribute.

The content attribute is optional and it is the ID of the content element in the current article that this TOC is for. When missing, the ID-less content elements are used.

3 XSLT processing rules

The processing rules were refactored and simplified according to the new schema.

3.1 The title-number template

No convoluted logic is necessary here anymore. Just number all titled sections included in a content.

  <xsl:template name="title-number">
    <xsl:param name="numbering" tunnel="yes"/>
    <xsl:if test="$numbering">
      <xsl:number level="multiple" count="content//section[title]" format="1.1 "
      />
    </xsl:if>
  </xsl:template>

3.2 The content template

This rule's role is to turn on the anchors and numbering (if so specified by the numbering attribute) for the included sections.

  <xsl:template match="content">
    <xsl:apply-templates>
      <xsl:with-param name="numbering" tunnel="yes" select="@numbering = 'true'"/>
      <xsl:with-param name="anchors" tunnel="yes" select="'true'"/>
    </xsl:apply-templates>
  </xsl:template>

3.3 The toc template

The first change in the toc template is how the highest level TOc entries are computed:

  • First the corresponding content element is searched
    • by id, if there is one
    • without id otherwise
  • Then all the titled sections in that content element are selected
    <xsl:variable name="contentID" select="@content"/>
    <xsl:variable name="content"
      select="if (@content) then ancestor::article//content[@id = $contentID] 
        else ancestor::article//content[not(@id)]"/>
    <xsl:call-template name="do-toc">
      <xsl:with-param name="entires" select="$content/section[title]"/>
      ...

Secondly, the numbering and anchor parameters are computed and tunnelled from here.

      <xsl:with-param name="numbering"
        select="if (@style) then (@style = 'numbered') 
          else ($content/@numbering = 'true')"
        tunnel="yes"/>
      <xsl:with-param name="anchors" tunnel="yes" select="'true'"/>

3.4 Sheet parameters

The following sheet parameters are no longer necessary and were removed:

  • numbered-sections was replaced by the numbering attribute of the content element
  • numbered-first-section is no longer necessary because the content element is now used to determine which sections are numbered
  • section-auto-ids are now on by default inside a content element

4 Finally

Download: article files.

First Posted: February 3rd, 2006 - Friday.