Search code examples
xmlxsltapache-fopdocbook

Generate a TOC of sections under each chapter with DocBook/XSLT-FO


I'm using FOP 2.3 and I have the following:

<book>
  <chapter>
    <title>ChA</title>
    <section>
      <title>Structures</title>
      <xi:include href="struct1.xml"/>
    </section>
    <section>
      <title>Functions</title>
      <xi:include href="function1.xml"/>
    </section>
  </chapter>
</book>

For function1.xml (as an example), I have:

<refentry xml:id="some_func" version="5.0">
  <refmeta>
    <refentrytitle>SomeFunc</refentrytitle>
  </refmeta>
  ...
</refentry>

Is it possible to...

  1. Not list the sections between the refentrys, but to only list them under the chapter title as a mini ToC?
  2. List the refentry/refentrytitle values under each section?

For example the output for the beginning of the chapter ChA should look like:

Page X
       ChA
         Structures
           SomeStruct
         Functions
           SomeFunc

Page X+1
       SomeStruct page

Page X+2
       SomeFunc page

Page X+3
       ChB
         Structures
           SomeStruct2
         Functions
           SomeFunc2
...

Next new page renders every refentry from all sections of chapter ChA sequentially, and are not broken up by sections Structures and Functions.

   

Edit 1

I was able to prevent the sections to be printed between the manual pages. I copied the section.content template:

<xsl:template name="section.content">
  <!-- Don't display section -->
  <!-- <xsl:call-template name="section.titlepage"/> -->
  ...
</xsl:template>

As for listing the sections, I found this:

<xsl:template match="chapter">
  ...
  <fo:page-sequence hyphenate="{$hyphenate}"
                    master-reference="{$master-reference}">
    ...
    <fo:flow flow-name="xsl-region-body">
      <xsl:call-template name="set.flow.properties">
        <xsl:with-param name="element" select="local-name(.)"/>
        <xsl:with-param name="master-reference" select="$master-reference"/>
      </xsl:call-template>

      <fo:block id="{$id}"
                xsl:use-attribute-sets="component.titlepage.properties">
        <xsl:call-template name="chapter.titlepage"/>
        <!-- === HERE IS WHERE I AM INSERTING MY CUSTOM TABLE OF CONTENTS === -->
        <xsl:for-each select="self::*/section">
          <fo:block>
            <xsl:message>DEBUG><xsl:value-of select="title"/></xsl:message>
            <xsl:value-of select="title"/>

            <xsl:for-each select="self::*/refentry/refmeta">
              <fo:block>
                <xsl:value-of select="refentrytitle"/>
              </fo:block>
            </xsl:for-each>
          </fo:block>
        </xsl:for-each>

      </fo:block>

      <xsl:call-template name="make.component.tocs"/>

      <xsl:apply-templates/>
    </fo:flow>
  </fo:page-sequence>
</xsl:template>

Solution

  • Took a while.

    First, the chapter template has to be overwritten in your custom style file:

    <xsl:template match="chapter">
      ...
        <fo:flow flow-name="xsl-region-body">
          <xsl:call-template name="set.flow.properties">
            <xsl:with-param name="element" select="local-name(.)"/>
            <xsl:with-param name="master-reference" select="$master-reference"/>
          </xsl:call-template>
    
          <xsl:call-template name="chapter.titlepage"/>
    
          <!-- Call Table of contents name here -->
          <xsl:call-template name="table.of.contents.titlepage.recto.sony"/>
    
          <fo:block id="{$id}" xsl:use-attribute-sets="component.titlepage.properties">
            <xsl:for-each select="self::*/section">
              <fo:block start-indent="{count(ancestor::*) + 2}pc">
                <xsl:variable name="section.id">
                  <xsl:call-template name="object.id"/>
                </xsl:variable>
    
                <!-- XXX: Error if "title" element doesn't exist -->
                <xsl:value-of select="title"/>
              </fo:block>
    
              <!-- XXX: Error if "refnamediv" doesn't exist -->
              <xsl:for-each select="self::*/refentry/refnamediv">
                <fo:block xsl:use-attribute-sets="toc.line.properties" start-indent="{count(ancestor::*) + 2}pc">
                  <xsl:variable name="refname.id">
                    <xsl:call-template name="object.id"/>
                  </xsl:variable>
    
                  <!-- XXX: Error if "refname" doesn't exist -->
                  <fo:inline font-family="Courier" keep-with-next.within-line="always">
                    <fo:basic-link internal-destination="{$refname.id}">
                      <xsl:value-of select="refname"/>
                    </fo:basic-link>
                  </fo:inline>
    
                  <fo:inline keep-together.within-line="always">
                    <fo:leader leader-pattern="space"
                               keep-with-next.within-line="always"/>
                    <fo:basic-link internal-destination="{$id}">
                      <fo:page-number-citation ref-id="{$refname.id}"/>
                    </fo:basic-link>
                  </fo:inline>
    
                </fo:block>
              </xsl:for-each>
    
            </xsl:for-each>
          </fo:block>
    
          <xsl:call-template name="make.component.tocs"/>
    
          <xsl:apply-templates/>
        </fo:flow>
      </fo:page-sequence>
    </xsl:template>
    

    The above will display a per-chapter table of contents. The section needs to be omitted:

    <xsl:template name="section.content">
      <!-- Don't display section -->
      <!-- <xsl:call-template name="section.titlepage"/> -->
      ...
    </xsl:template>