Difficulty: ★★☆ (medium)
Keywords: HTML, simple navigation, next, previous, sections, generate.simple.navigation

Problem

You need a simple navigation for your single HTML file. The navigation should contain for each chapter a link to the next and previous chapters including an up link pointing to the enclosing part or book. This is depicted in the following graphic:

Solution

The following file defines the named template generate.simple.navigation:

Example 5.2. simple-navigation.xsl
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:d="http://docbook.org/ns/docbook"
  xmlns="http://www.w3.org/1999/xhtml"
  exclude-result-prefixes="d">
    
  <xsl:template name="generate.simple.navigation">
    <xsl:param name="node" select="."/>
    <xsl:variable name="prev" select="$node/preceding-sibling::d:chapter"/>
    <xsl:variable name="next" select="$node/following-sibling::d:chapter"/>
    <xsl:variable name="up"   select="$node/parent::d:*"/>
   
    <div class="chapter-navigation">
      <ul>
        <xsl:if test="count($next) >0">
          <li class="next">
            <xsl:call-template name="gentext.nav.next"/>
            <xsl:text>: </xsl:text>
            <a>
              <xsl:attribute name="href">
                <xsl:call-template name="href.target">
                  <xsl:with-param name="object" select="$next"/>
                </xsl:call-template>
              </xsl:attribute>
              <xsl:value-of select="$next/d:title"/>
            </a>
          </li>
        </xsl:if>
        <xsl:if test="count($prev) >0">
          <li class="prev">
            <xsl:call-template name="gentext.nav.prev"/>
            <xsl:text>: </xsl:text>
            <a>
              <xsl:attribute name="href">
                <xsl:call-template name="href.target">
                  <xsl:with-param name="object" select="$prev"/>
                </xsl:call-template>
              </xsl:attribute>
              <xsl:value-of select="$prev/d:title"/>
            </a>
          </li>
        </xsl:if>
        <xsl:if test="count($up) >0">
          <li class="up">
            <xsl:call-template name="gentext.nav.up"/>
            <xsl:text>: </xsl:text>
            <a>
              <xsl:attribute name="href">
                <xsl:call-template name="href.target">
                  <xsl:with-param name="object" select="$up"/>
                </xsl:call-template>
              </xsl:attribute>
              <xsl:value-of select="$up/d:title"/>
            </a>
          </li>
        </xsl:if>
      </ul>
    </div>
  </xsl:template>
</xsl:stylesheet>

This is not enough, of course. Use the following steps to include it into your customization layers:

  1. Create a customization layer as shown in Section 2.3, “Writing Customization Layers”.

  2. Include the stylesheet from Example 5.2, “simple-navigation.xsl into your customization layer:

    Example 5.3. db-simple-navigation.xsl
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE xsl:stylesheet [
      <!ENTITY db "https://cdn.docbook.org/release/xsl/current">
    ]>
    <xsl:stylesheet version="1.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:d="http://docbook.org/ns/docbook"
      xmlns="http://www.w3.org/1999/xhtml" 
      exclude-result-prefixes="d">
      
      <xsl:import href="&db;/xhtml/docbook.xsl"/>
      <xsl:import href="simple-navigation.xsl"/>
      
      <xsl:param name="generate.simple.navigation" select="1"/>
      <xsl:param name="html.stylesheet">book.css</xsl:param>
    
      <xsl:template name="chapter.titlepage.before.recto">
        <xsl:if test="$generate.simple.navigation != 0">
          <xsl:call-template name="generate.simple.navigation"/>
        </xsl:if>
      </xsl:template>
    </xsl:stylesheet>
  3. Use the file db-simple-navigation.xsl to transform your documents.

If you want to switch on or off the above behaviour, use the parameter generate.simple.navigation[9] and set it to 0.

Discussion

The named template generate.simple.navigation works as follows:

  1. Three variables are definied for the previous, next, and up links. All depends on the node parameter which is by default the context node. This is useful as you can change the behaviour very easily by just changing the context node when calling the template with xsl:call-template.

  2. The prev and next variables are filled with the previous or next chapter elements along their sibling axis. In comparison, the up variable needs to find its enclosing element; it uses the parent axis for this purpose.

    If there are no previous or next chapters, the node set will be empty. For the up variable, this depends on where a chapter belongs; usually it is enclosed in a book or part element. As we need only the parent element As such this variable can not be empty (only if the chapter would be the root element.)

  3. The simple navigation is implemented as an unordered list inside a div element. The enclosing div element is used to make styling with CSS easier.

  4. With xsl:if we check the amount of nodes in our prev and next variables. Only if the variable contains more than zero nodes it will a listitem created.

  5. If we create a listitem, we want to insert a text to help our readers and to distinguish the different directions. The text has to be independent from the used language; so hard-coding the text would not be sufficient. For this reason, the DocBook stylesheets contains named templates to generate localized text. The next, previous, and up link texts are created through the named templates generate.nav.next, generate.nav.prev, and generate.nav.up.

  6. As the generated localized text contains only the text without interpunctations, we append “: ”.

  7. We create the link with the a link. The needed href attribute is created by calling href.target (origin html.xsl). This template is responsible for the correct linking value.

  8. The content of the a element is inserted from the title element of the corresponding node. As we are only interested in the string value, we can use xsl:value-of.

The generate.simple.navigation template matches only chapters along the sibling axis. As such it can not find a glossary or appendix after a chapter. If you want to create links for any component elements, not only chapters, you need to change the variables prev and next. A first attempt would lead to this:

<xsl:variable name="prev" select="$node/preceding-sibling::d:*[1]"/>
<xsl:variable name="next" select="$node/following-sibling::d:*[1]"/>

This works for the next variables. However, our expression for the prev variable contains a bug. Consider the following structure:

book
  title
  info
  chapter
  ...

The preceding-sibling axis returns the info and title elements. This is not what you want as these are no component elements for our prev link. In that case we need an expression to filter out the unwanted elements. This is done with an predicate:

<xsl:variable name="prev" select="$node/preceding-sibling::d:*[not(self::d:title|self::d:info)][1]"/>

That expression first creates a node set with all preceding elements. In a second step they are check against the term not(self::d:title|self::d:info); only those elements remain in the node set which are not title or info elements. In our above example, this leads to a node set with zero nodes and is exactly what we wanted to achieve.

With the previous change, we allow any structural element to be included in a next or previous link. However, we show our navigation links only in chapter titlepages at the moment (the named template chapter.titlepage.before.recto.) We need to extend the stylesheet db-simple-navigation.xsl to allow elements like appendix, glossary, etc. Refer to the content modell of DocBook´s book element for details. For an appendix this looks like this:

<xsl:template name="appendix.titlepage.before.recto">
  <xsl:if test="$generate.simple.navigation != 0">
    <xsl:call-template name="generate.simple.navigation"/>
  </xsl:if>
</xsl:template>

For the other elements this is exactly the same except the name. Just use the element name and append .titlepage.before.recto.

See Also


[9] It is not an error to have a parameter with the same name as a named template.


Project@GitHubIssue#10