Replacing templates

At some point in your customization efforts you may find that none of the above methods will achieve the result you want. The most powerful customization method is to replace specific XSL templates in the DocBook collection with your own customized templates. The general technique is:

  1. Find the DocBook XSL template that handles the text you want to change.

  2. Copy that template to your customization layer.

  3. Modify the copy to do what you want. Your version will override the behavior of the standard template because yours has a higher import precedence.

This method is very powerful, but it is not easy. It requires extensive knowledge of XSLT programming, and familiarity with the internals of the DocBook XSL stylesheet collection. This customization method is possible because the DocBook stylesheets are written in a highly modular fashion.

Here is a simple example of a template replacement. The default formatting for the command element is boldface. Perhaps you prefer your commands to appear in a monospaced font. In the collection of files for the DocBook XSL distribution, you will find html/inline.xsl that contains this xsl:template:

<xsl:template match="command">
  <xsl:call-template name="inline.boldseq"/>
</xsl:template>

Also in that file you will notice a template named inline.monoseq that does what you want. So you can add the following template to your customization layer to change how command is formatted:

<xsl:template match="command">
  <xsl:call-template name="inline.monoseq"/>
</xsl:template>

You can see a longer example of rewriting a template in the section “Customizing TOC presentation”.

Finding the right template

Most DocBook templates perform a very specific and limited function. The trick is to find the template that is handling the behavior you want to change. Typically many templates are called during the processing of a given element. Depending on what you are trying to change, you may have to trace through several templates to find the one you need to replace.

There are two basic kinds of XSLT template:

  • Templates that have a match attribute, which are called during xsl:apply-templates when the processor encounters an element satisfying the match pattern.

  • Templates that have a name attribute and are called by name like a subroutine using xsl:call-template.

If you are trying to change how a particular element is handled, you can usually start by scanning for match attributes that include the name of your element. Sometimes there will be more than one template whose match attribute could apply. Then the processor generally selects the one with the best match, that is, the most specific match. For example, a match pattern of chapter/para is more specific than para. The more specific the match, the higher the priority in selecting that template.

Once you have found the starting point, you can trace through the template body to see what other templates it uses. Named templates are explicitly called by name using xsl:call-template. Those templates are easy to find because there is only one for each name.

If a template uses xsl:apply-templates, then you need to consider several factors that affect which template might be applied:

  • If xsl:apply-templates includes a select attribute, then those elements matched by the select expression are going to be processed. So you need to find templates with match attributes that apply to those elements.

  • If xsl:apply-templates does not include a select attribute, then the children of the current element will be processed. So you need to look for templates with match attributes that apply to the children.

  • If xsl:apply-templates includes a mode attribute, then you can narrow your search to those matching templates that have the same mode value.

You may need to look in several directories to find the right template. Most of the templates for HTML processing are in the html subdirectory of the stylesheet distribution, and most of the templates for generating FO output are in the fo subdirectory. But they share templates in the common and lib directories, so you may need to look there as well. Here is a handy Linux command for searching three directories at once, showing how to find all references to href.target:

find html lib common -name '*.xsl' | xargs grep 'href.target'

This command is run in the directory above html. It uses find to search the html, lib, and common directories for files named *.xsl. It uses the xargs command to feed that list to the grep command that scans each file for the given regular expression. If you are a Windows user, this command is available if you install Cygwin.

Hopefully at some point you will find the specific template that does the processing you want to change. That's the template you want to duplicate and modify in your customization layer.

Import precedence

When you add overriding templates to your customization layer, you have to pay attention to XSLT import precedence. This says that the importing stylesheet's templates take precedence over the imported stylesheet's templates. In general, that is what you want, since that is how you override the behavior of the stock templates. Also, when your customization has several import statements, one that occurs later in the file has precedence over those that precede it in the file. That is useful for managing modular stylesheet files.

But what is not obvious is that import precedence is stronger than priority selection. Within a stylesheet, a match pattern that is more specific has a higher priority than a less specific pattern. So a template with match="formalpara/para" has a higher priority than a template with match="para". That's how you get templates to apply in certain contexts but not others. But import precedence can override this priority selection.

A detailed example will make this easier to understand. In the DocBook stylesheet file html/block.xsl, there is a <xsl:template match="para"> template for general para elements that outputs HTML <p></p> tags around its content. There is another <xsl:template match="formalpara/para"> template for a para within a formalpara element. It does not output <p></p> tags, because the wrapper element's template match="formalpara" does that, so that the title and the para are in one set of <p></p> tags. The more specific template has a higher priority in the DocBook stylesheet.

Now copy the <xsl:template match="para"> template to your customization layer. Now your formalpara elements will start generating invalid HTML, because there will be nested <p></p> tags, which is not permitted in HTML. The reason you get this error is because your match="para" template now has higher import precedence than the match="formalpara/para" template in block.xsl, even though your template is less specific. The import precedence is stronger than the match priority.

The solution is to provide another template in your customization layer:

<xsl:template match="formalpara/para">
  <xsl:apply-imports/>
</xsl:template>

Now this template has equal import precedence to your match="para" template, because they are both in the importing stylesheet. It also has a higher priority since it is more specific. So it will be used for any para elements inside formalpara. Its effect is to just apply the original such template in block.xsl, which avoids the nested <p></p> tags.

Passing parameters

When rewriting a template, pay careful attention to any parameters the original template is being passed. These are always declared at the top of the template. For example:

<xsl:template name="href.target">
  <xsl:param name="context" select="."/>
  <xsl:param name="object" select="."/>
  ...

Such a template may be called with one or more of the parameters set. For example:

<xsl:call-template name="href.target">
  <xsl:with-param name="context" select="some-context"/>
  <xsl:with-param name="object" select="some-object"/>
  ...
</xsl:call-template>

You want your new template to fit in as a direct replacement for the original, so it should handle the same parameters as the original. Since it is not required that all parameters be passed when a template is called, it should also handle missing parameter values. This requires you to understand when parameters are passed, what might be passed as a parameter value, and how it is expected to be handled. So if you are modifying a named template, check all the templates that call the template you are modifying to see what they do with the parameters.

You may extend a modified template by passing it new parameters. Be sure to declare your new parameters with a default value at the top of your new template. Of course, you'll also have to replace one or more of the calling templates so they can pass the parameter to your new template. You do not have to replace all of the calling templates, since parameters are optional. But you do need to make sure your default value works in case they do not pass a value.

Utility templates and modes

The DocBook XSL stylesheets include many templates that would be considered general purpose subroutines in other programming languages. Such utility templates handle common tasks in a consistent manner and reduce code duplication. You can call these same utility templates in your own templates for the same reasons.

The following table shows some of the more commonly used utility templates. The table includes a link to an example in this book of how the template is used, but there are many more examples in the DocBook stylesheets themselves.

Table 9.1. Commonly used utility templates and modes

Template or mode namePurposeSee usage example in:
mode="object.title.markup"Returns the full numbered title of an element, such as "Chapter 5, Using a Mouse". It finds the appropriate gentext template and fills it in.“Customizing admonitions”
mode="title.markup"Returns the title only of an element, possibly looking in an info element to find it.“Styling print TOC entries”
mode="label.markup"Returns the number only of an element, if it has one. For example, when applied to the fifth chapter element, it returns 5.“Styling print TOC entries”
name="gentext"Returns the gentext string matching a given key passed as a parameter, in the current language.“Customizing TOC presentation”
name="l10n.language"Returns the current language code, normalized to lowercase, with dash replaced by underscore.“Sorting a bibliography”
name="gentext.template"Selects a gentext template from the locale file for the current language, based on a name, context, and optional style passed as parameters.“Controlling page citations by element”
name="substitute.markup"Fills in the placeholders in a gentext template passed as a parameter. It replaces %t with the title, %n with the number, etc.“Controlling page citations by element”
mode="object.xref.markup"Returns the generated cross reference text for the current element as a target of a cross reference. It finds the appropriate gentext template and fills it in.“Customizing cross reference behavior”
name="section.level"Computes the section level of the context node.“Return to top”
name="object.id"Returns the value of the id or xml:id attribute of the context element. May generate a value using the generate-id() function if no id attribute.“Sorting a bibliography”
name="href.target"Returns the link URL for a cross reference, for either HTML or FO output.“Bread crumbs”
name="dbfo-attribute" name="dbhtml-attribute"Returns the value of a DocBook processing instruction that uses the pseudo attribute syntax.“Processing instructions”
mode="xref-to"Returns the text that is generated for an xref to the context node. Usually applies templates in mode object.xref.markup to fetch a gentext template and fill it in.“Customizing cross reference behavior”
name="gentext.startquote" name="gentext.endquote"These two templates can be used to generate the start and end double-quote characters, respectively, in the current locale.“Q and A cross references”
name="string.lower" name="string.upper"Converts a text string to lowercase or uppercase, respectively. Takes a string template parameter, and converts it using the gentext templates named uppercase.alpha and lowercase.alpha in the current locale. 

As the usage examples show, those templates with a name attribute are called using xsl:call-template, with the current node as the context node. Such templates may also have parameters passed to them.

Those templates with a mode attribute are called using xsl:apply-templates with the mode attribute. The apply-templates may also include a select attribute to specify an element other than the current one to apply the template to. There must be a template with a match for each element it is being applied to. Usually there is a match="*" to handle any elements that do not have their own template in that mode.