Creating a table of contents using Ibex

John Farrow, Visual Programming Limited

This technical note describes how to create a table of contents at the beginning of a document.

Approach

A table of contents is usually created in its own page-sequence near the beginning of the document. Assuming a document which contains chapters, a block is created in the table of contents for each chapter; this block contains the name of the chapter and a page-number-citation element which will be replaced by the page number on which the chapter occurs.

The stylesheet used to create a document with a table of contents processes the chapter elements twice; first to create the list of chapters in the table of contents and secondly to place the actual chapters in the document.

Example data

For this example we use a small document which contains some <chapter> elements which in turn multiple <heading> and <paragraph> elements, like this:

<?xml version="1.0" encoding="utf-8"?>
<content>

<chapter name="First Chapter">
	<heading>First Heading</heading>
	<paragraph>
	Images are added to the document using either the external-graphic or 
  instream-foreign-object elements. Use the external-graphic element to include a 
  file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to 
  include an image defined in Scalable Vector Graphics (SVG) format.
</paragraph>

	<heading>Second Heading</heading>
	<paragraph>
    Images are added to the document using either the external-graphic or
    instream-foreign-object elements. Use the external-graphic element to include a
    file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to
    include an image defined in Scalable Vector Graphics (SVG) format.
  </paragraph>


	<heading>Third Heading</heading>
	<paragraph>
    Images are added to the document using either the external-graphic or
    instream-foreign-object elements. Use the external-graphic element to include a
    file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to
    include an image defined in Scalable Vector Graphics (SVG) format.
  </paragraph>
</chapter>

<chapter name="Second Chapter">
	<heading>Second Chapter First Heading </heading>
	<paragraph>
    Images are added to the document using either the external-graphic or
    instream-foreign-object elements. Use the external-graphic element to include a
    file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to
    include an image defined in Scalable Vector Graphics (SVG) format.
  </paragraph>

	<heading>Second Chapter Second Heading</heading>
	<paragraph>
    Images are added to the document using either the external-graphic or
    instream-foreign-object elements. Use the external-graphic element to include a
    file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to
    include an image defined in Scalable Vector Graphics (SVG) format.
  </paragraph>


	<heading>Second Chapter Third Heading</heading>
	<paragraph>
    Images are added to the document using either the external-graphic or
    instream-foreign-object elements. Use the external-graphic element to include a
    file in JPEG, GIF, TIFF, BMP, or PNG formats.  Use the instream-foreign-object element to
    include an image defined in Scalable Vector Graphics (SVG) format.
  </paragraph>
</chapter>

</content>

XSL stylesheet

The stylesheet we use looks like this:

<?xml version='1.0' encoding='utf-8'?>

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                xmlns:ibex="http://www.xmlpdf.com/2003/ibex/Format">

  <xsl:strip-space elements='*'/>


  <xsl:attribute-set name="heading">
    <xsl:attribute name='space-before'>18pt</xsl:attribute>
    <xsl:attribute name='font'>bold 14pt arial</xsl:attribute>
    <xsl:attribute name='text-align'>left</xsl:attribute>
    <xsl:attribute name='keep-with-next.within-page'>always</xsl:attribute>
  </xsl:attribute-set>

  <xsl:attribute-set name='leader-ahead'>
    <xsl:attribute name='rule-thickness'>.2pt</xsl:attribute>
    <xsl:attribute name='leader-length'>100%</xsl:attribute>
    <xsl:attribute name='leader-pattern'>rule</xsl:attribute>
  </xsl:attribute-set>


  <xsl:template match="content">

    <fo:root>

      <!-- define a set of page layouts -->
      <fo:layout-master-set>

        <fo:simple-page-master  master-name="page" margin="1.5cm"
                                page-height="297mm" page-width="210mm">
          <fo:region-body region-name="body" margin-top="1.5cm"
                          margin-left="2cm" margin-right="1.5cm" margin-bottom="1.5cm"/>
        </fo:simple-page-master>

      </fo:layout-master-set>

      <!-- table of contents -->
      <fo:page-sequence master-reference="page" force-page-count="no-force">
        <fo:flow flow-name="body">
          <xsl:apply-templates select="chapter | chapter/heading" mode="toc"/>
        </fo:flow>
      </fo:page-sequence>

      <!-- contents -->
      <xsl:apply-templates select="chapter"/>

    </fo:root>

  </xsl:template>




  <xsl:template match="chapter" mode="toc">

    <fo:block text-align='justify' text-align-last="justify" space-after="3pt" keep-with-next="always">

      <!-- the actual content -->
      <fo:basic-link>

        <xsl:variable name="chapter-number">
          <xsl:number level="multiple" count="chapter" format="1.1.1. "/>
        </xsl:variable>

        <xsl:variable name="reference">
          <xsl:number level="multiple" count="chapter" format="1.1"/>
        </xsl:variable>

        <xsl:attribute name='internal-destination'>chapter-refid-<xsl:value-of select="$reference"/></xsl:attribute>

        <!-- insert the chapter number -->
        <!-- the introduction comes before chapter one and has no chapter number -->

        <xsl:value-of select="$chapter-number"/>

        <xsl:value-of select='@name'/>

        <!-- the default leader width is 0,12pt,100% which works for this -->
        &#160;
        <fo:leader leader-pattern='dots' rule-thickness='.2pt' 
                   leader-alignment='reference-area' font-size="10pt"/>
        &#160;

        <!-- can click on page number -->

        <fo:page-number-citation>
          <xsl:attribute name='ref-id'>chapter-refid-<xsl:value-of select="$reference"/></xsl:attribute>
        </fo:page-number-citation>

      </fo:basic-link>

    </fo:block>
  </xsl:template>

  <xsl:template match="heading" mode='toc'>
    <fo:block text-align='justify' text-align-last="justify" space-after="3pt" margin-left="1cm" 
              margin-right='.1cm'  font-size='0.9em'>

      <xsl:variable name="chapter-number">
        <xsl:number level="multiple" count="chapter | heading" format="1.1. "/>
      </xsl:variable>

      <xsl:variable name="reference">
        <xsl:number level="multiple" count="chapter | heading" format="1.1"/>
      </xsl:variable>

      <fo:basic-link>


        <xsl:attribute name='internal-destination'>ahead-refid-<xsl:value-of select="$reference"/></xsl:attribute>

        <!-- insert the chapter number -->

        <xsl:value-of select='$chapter-number'/>

        <!-- allows </prod> in title -->
        <xsl:apply-templates/>
        <!-- the default leader width is 0,12pt,100% which works for this -->
        &#160;
        <fo:leader leader-pattern='dots' rule-thickness='.2pt' 
                   leader-alignment='reference-area' font-size="10pt"/>

        &#160;

        <!-- can click on page number -->

        <fo:page-number-citation>
          <xsl:attribute name='ref-id'>ahead-refid-<xsl:value-of select="$reference"/></xsl:attribute>
        </fo:page-number-citation>

      </fo:basic-link>

    </fo:block>
  </xsl:template>

  <!-- header across page for one attribute -->
  <xsl:template match="heading">

    <xsl:variable name="chapter-number">
      <xsl:number level="multiple" count="chapter | heading" format="1.1. "/>
    </xsl:variable>

    <xsl:variable name="reference">
      <xsl:number level="multiple" count="chapter | heading" format="1.1"/>
    </xsl:variable>


    <!-- use a list so the number is outside the content -->
    <fo:list-block margin-left='-2cm' provisional-distance-between-starts="2cm" 
                   provisional-label-separation=".25cm">

      <fo:list-item>
        <fo:list-item-label end-indent="label-end()">
          <fo:block text-align="right">
            <!-- destination for toc -->
            <xsl:attribute name='id'>ahead-refid-<xsl:value-of select="$reference"/></xsl:attribute>

            <fo:inline >
              <xsl:value-of select="$chapter-number"/>
            </fo:inline>
          </fo:block>
        </fo:list-item-label>
        <fo:list-item-body start-indent="body-start()">
          <fo:block>
            <xsl:apply-templates/>
          </fo:block>
        </fo:list-item-body>
      </fo:list-item>
    </fo:list-block>

  </xsl:template>


  <xsl:template match="chapter">

    <xsl:variable name="chapter-number">
      <xsl:number level="multiple" count="chapter | heading" format="1.1. "/>
    </xsl:variable>

    <xsl:variable name="reference">
      <xsl:number level="multiple" count="chapter | heading" format="1.1"/>
    </xsl:variable>

    <!-- if first chapter, set page number to 1 -->
    <fo:page-sequence>

      <xsl:attribute name='master-reference'>page</xsl:attribute>
      <xsl:attribute name='force-page-count'>no-force</xsl:attribute>



      <xsl:if test='position() = 1'>
        <xsl:attribute name='initial-page-number'>1</xsl:attribute>
      </xsl:if>

      <fo:flow flow-name="body">

        <!-- the chapter heading -->
        <fo:block font-weight="bold" space-after="1cm">
          <xsl:attribute name='id'>chapter-refid-<xsl:value-of select="$reference"/></xsl:attribute>

          Chapter <xsl:value-of select="$chapter-number"/>

          <xsl:value-of select="@name"/>
        </fo:block>

        <xsl:apply-templates select="heading | paragraph"/>

        <xsl:if test='position() = last()'>
          <fo:block id='last-page'/>
        </xsl:if>
      </fo:flow>

    </fo:page-sequence>

  </xsl:template>

  <xsl:template match="paragraph">
    <xsl:element name="fo:block">
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>




</xsl:stylesheet>

The key thing about this is that we process the chapter elements twice. First we create the table of contents in their own page-sequence like this:

        <fo:page-sequence master-reference="page" force-page-count="no-force">
          <fo:flow flow-name="body">
            <xsl:apply-templates select="chapter | chapter/heading" mode="toc"/>
          </fo:flow>
        </fo:page-sequence>
      

Then we create a separate page-sequence for each chapter like this:

        <xsl:apply-templates select="chapter"/>
      

By specifying the mode attribute on the first apply-templates call, we can have two xsl:template elements which match the 'chapter' element; one template to create the table of content entry and another to actually insert the chapter in the document.

Within the template which creates the table of contents, we use xsl:number to calculate the chapter number based on its position in the XML. By specifying level="multiple" we can create chapter and heading numbers in a hierarchy.

Results

The resulting table of contents in the PDF file looks like this:

Copyright (c) 2002-2016 Visual Programming Limited