Generating Tables of Contents RevisitedAfter 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. Contents1 RationaleSo 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 SchemaWe 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. 2.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>
2.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> 2.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>
2.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>
2.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> 2.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 typeThere 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 rulesThe processing rules were refactored and simplified
according to the new schema. 3.1 The title-number templateNo 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 templateThis 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 templateThe 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 parametersThe 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 FinallyDownload: article
files. First Posted: February 3rd, 2006 - Friday.
|