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 template.

But what isn't 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 don't 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 don't pass a value.