Adding new templates

One of the beauties of a template-driven language like XSLT is that you can add to the collection of templates. Your new templates follow the same rules of selection as the stock templates, so your templates participate on an equal footing with the originals.

A new template is an xsl:template element with a match attribute that you add to your customization layer. The match attribute specifies an XSL pattern that selects which elements it should be applied to. See the section “Template selection rules” if your template is not getting used as expected.

Here are some reasons why you might want to add new templates:

The following sections provide guidelines and examples.

Formatting determined by attribute value

The DocBook role attribute is often used to distinguish one use of an element from another. If you want the difference expressed in formatting, you can add a template that responds to an attribute value. The following is an example of a template that draws a border around a para that has role="intro".

<xsl:template match="para[@role = 'intro']">
  <fo:block border="0.5pt solid blue"
            padding="3pt"
            xsl:use-attribute-sets="normal.para.spacing">
    <xsl:call-template name="anchor"/>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

In this example, the original template matching on para was copied from fo/block.xsl to the customization layer and then changed. The match attribute with @role='intro' means that this template is applied only to para elements with that role attribute value. The template adds border and padding properties for format such paragraphs.

This process is similar to replacing a template, but in this case the original template matching on plain para was not changed. This template is new, and applies only to the special paragraphs.

This second example applies different formatting for different values of role:

<xsl:template match="para[@role]">
  <fo:block padding="3pt"
            xsl:use-attribute-sets="normal.para.spacing">
    <xsl:choose>
      <xsl:when test="@role = 'intro'">
        <xsl:attribute name="border">0.5pt solid blue</xsl:attribute>
      </xsl:when>
      <xsl:when test="@role = 'concept'">
        <xsl:attribute name="border">1pt solid black</xsl:attribute>
        <xsl:attribute name="margin-left">14pt</xsl:attribute>
        <xsl:attribute name="background-color">#EFEFEF</xsl:attribute>
      </xsl:when>
      ...
    </xsl:choose>
    <xsl:call-template name="anchor"/>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

Now the match is on any para that has any role attribute. The xsl:choose statement tests the role value and applies attributes for each value. You can use xsl:attribute to add an attribute to the parent block, as long as no output has been generated for the block yet. Unfortunately, XSL does not permit applying an attribute-set within an xsl:choose statement.

Adding processing steps

If you want to add some steps to an existing template, you may be able to avoid copying and editing the whole template. Instead, you can use xsl:apply-imports to call the original template, and put that in a new template that adds new steps.

For example, if you wanted to add an icon in the margin for each figure element, you can create a new template that matches on figure, generates the icon, and then applies the original figure template.

<xsl:template match="figure">
  <xsl:call-template name="floater">
    <xsl:with-param name="content">
      <fo:external-graphic src="url(figure-icon.png)"/>
    </xsl:with-param>
    <xsl:with-param name="position">left</xsl:with-param>
    <xsl:with-param name="width">4pc</xsl:with-param>
  </xsl:call-template>
  <xsl:apply-imports/>
</xsl:template>

The template match attribute matches on all figure elements. Because it is in a customization layer that imports the DocBook XSL stylesheet, it has higher import precedence than the original template that matches on figure. So this template will be used instead of the original. It calls a utility template in the DocBook stylesheet named floater that generates an fo:float in the output. After that, xsl:apply-imports is used to apply the original figure template to output the figure title and graphic.

Handling new elements

If you customize the DocBook schema to add new elements, then you must customize the stylesheet to handle the new element names. If that is not done, then you will see error messages like the following when you process a document with such elements:

element encountered in parent but no template matches

If a new element is to be formatted the same as an existing element, you can probably copy and change the original template for the existing element so it handles the new element too. For example, if you add an element named concept whose content model is just like para, then you can copy the template with match="para" to your customization layer and change the match attribute:

<xsl:template match="para | concept">
  <fo:block xsl:use-attribute-sets="normal.para.spacing">
    <xsl:call-template name="anchor"/>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

The only change here is to match on either para or concept. Both are handled like paragraphs.

If a new element is similar to but not the same format as an existing element, then you have a decision to make. You could share a customized version of the existing template as in the previous example, and use xsl:choose to handle any differences within the template. Or you could copy the original template, change it to match on only the new element name, and customize it as needed. Which method you choose depends on how different they need to be, and how hard they will be to maintain. If you want to apply a new attribute-set, it is usually easier to dedicate a new template to the element, like the following:

<xsl:template match="concept">
  <fo:block xsl:use-attribute-sets="concept.properties">
    <xsl:call-template name="anchor"/>
    <xsl:apply-templates/>
  </fo:block>
</xsl:template>

If a new element is not similar to any existing DocBook element, you will need to write an entirely new template to handle it. In that case, it is best to study some of the existing DocBook templates to discover the numerous named utility templates, gentext features, and general methods used for handling DocBook content. A good XSLT reference is a must.

Triggering processing

Once you have created a template matching on your new element, you have to make sure it gets used. Generally that is not a problem, because most (but not all) templates in DocBook XSL use a general <xsl:apply-templates/> to process all their children. If the parent element of your new element includes such, then your new template will get used because it is the only one that matches on the new element.

But some parent elements are selective in what elements they process. For example, the following template for simplelist selects only member elements to process:

<xsl:template match="simplelist[@type='inline']">
  ...
  <fo:inline>
    <xsl:for-each select="member">
      ...
    </xsl:for-each>
  </fo:inline>
</xsl:template>

If you were to create a new element that is a child of simplelist, this its matching template would never be called in this context, because the new element name does not match member. You would need to customize this template to process your new element.

The worst case in the stylesheets of this kind of selective processing is the handling of Next and Previous in chunked HTML. See for example the template named chunk-all-sections in html/chunk-code.xsl. Those templates use long lists of element names in select attributes to figure out the next chunk.

Mode processing

Many elements are processed in more than one XSL mode, so you'll need to also add templates for those modes if appropriate. For example, if you want an element to appear in the table of contents, you may need a mode="toc" template, and check the parent element that would call your new template too. If you want to cross reference to an element, then you need a mode="xref-to" template. An element with title might need a mode="title.markup" template.

New gentext elements

If any of your new elements is to generate text labels or titles, then you will want to add gentext elements to the local.l10n.xml parameter (it is a parameter, not an XML file). You might need to add a general l:gentext element, as well as l:template elements in the contexts of title and xref. See the section “Generated text” for more information.

Template selection rules

In order for your new templates to be used, it helps to understand the rules by which a template is selected to handle a given element. The rules are summarized here, from lowest to highest importance.

  • Assigned priority. Every template has an implicit priority number assigned to it based on how specifically it matches. For example, a match="para[@role]" is more specific than match="para", and so the first has a higher assigned priority. Assigned priorities are always in the range -0.5 to +0.5. If more than one template in a stylesheet file matches on the same element, the template with the highest priority is used. It is possible that two templates with different match attributes resolve to the same assigned priority value, so you may need to add an explicit priority attribute to resolve the conflict.

  • Explicit priority attribute. You can add a priority attribute to any template to override the implicit priority assigned to it. Setting priority="1", for example, will establish a higher priority than any assigned priority, since the latter are limited to the range -0.5 to +0.5.

  • Import precedence. If two templates match on the same element and one is imported, then the template in the importing stylesheet takes precedence, regardless of the relative priority values (assigned or explicit). The local template is said to have a higher import precedence than the imported template. This it the key feature that makes customization layers work. When you import the DocBook stylesheet, you only have to change the templates you need to change, and your templates always have higher import precedence than the originals.

Keep in mind that import precedence always wins over priority values. That can sometimes lead to surprising results. For example, if a template with a priority="10" attribute is imported, and the importing stylesheet has a match on that element but no priority attribute, then the high priority value on the imported template counts for nothing, because the higher import precedence of the local template wins. Likewise with assigned priorities. An imported template with a very specific match will not be used if any template in the importing stylesheet matches on the same element, even if it is a looser match.

See the section “Import precedence” for more information and examples of import precedence.