Table of Contents
When you find that you need to change something that doesn't seem to have a parameter, then you will have to customize the DocBook XSL stylesheets. Fortunately, the stylesheets were designed to make them easy to customize (thanks to Norm Walsh). The basic idea is to create a customization layer in which you put all your changes, and then rely on the standard DocBook stylesheets for everything else. The following sections describe how to create a customization layer and what you can put in it.
You don't need a customization layer to customize how your HTML output looks. You can control your HTML presentation by simply adding your own CSS stylesheet to the standard DocBook HTML output. See the section called “Using CSS to style HTML” for more information.
A customization layer is a new XSL stylesheet that layers your changes on top of the existing DocBook XSL templates. It permits you to customize your processing without actually editing the original DocBook stylesheet files. Your changes are a thin layer on top of the extensive collection of DocBook code.
Keeping your changes separate has a lot of advantages:
You can generally upgrade to a new release of the DocBook stylesheets without having to reintegrate your changes into a lot of separate DocBook files. You may have to tweak your customization layer a bit, depending on the changes introduced in the new version. But you won't have to repeat typing a lot of code into the new version's files.
It makes it easy to distribute a customization to other users, because you only have to distribute one file and not a large collection of modified DocBook files.
Getting help with problems is easier because your changes are completely isolated. You can attach just your customization layer to an email, and others can apply it to their own standard DocBook files for testing.
Writing a customization layer requires knowledge of XSLT syntax. You can read Appendix B and the examples here to get started, but you will probably need to obtain a good XSLT reference book, such as Michael Kay's XSLT Programmer's Reference.
The basic features of a customization layer are:
It is an XSLT stylesheet file, using standard XSLT syntax.
It imports one of the DocBook XSL stylesheet files, which acts as the starting point.
It adds whatever modifications are needed.
It is used in place of the standard DocBook stylesheet when you process your DocBook XML files.
The file can be named whatever you like, and you can create more than one customization layer with different customizations. Here is a simple example of a customization layer with each line explained. It just sets a few parameters for HTML processing:
To process your XML files with the customization layer, you simply use your customization layer file in place of the standard DocBook file that it imports. For example, if the above example was saved to a file named mystyles.xsl, then you could use it as follows:
xsltproc --output myfile.html mystyles.xsl myfile.xml
Here you have replaced the standard docbook.xsl stylesheet file with mystyles.xsl. Because your file imports the standard docbook.xsl file, the effect is the same as processing with the standard file but with your customizations added.
If you do customizations for both HTML and FO output, you need to start with separate customization layers. You might be tempted to write one customization layer that conditionally imports either the HTML or FO DocBook stylesheet. Unfortunately, it doesn't work, because the xsl:import statement cannot be inside an XSL conditional statement like xsl:if or xsl:choose. If you also do chunk and nonchunk HTML processing, then you will need a third customization layer.
Maintaining multiple customization layers can lead to inconsistencies as they change over time. Many of the parameters apply to both chunk and nonchunk HTML output, or to both HTML and FO output. If you set the shade.verbatim parameter in one, you will probably want to set it in all. You can do that easily if you separate the common parts of your customization layer into a separate file. Then you can use xsl:include in all of your customization layers to include the same file with the same parameter settings. Here are two customizations that share a common-customizations.xsl file that contains shared parameters:
HTML customization: <?xml version='1.0'> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="html/docbook.xsl"/> <xsl:include href="common-customizations.xsl" /> <xsl:param name="html.stylesheet" select="'corpstyle.css'"/> </xsl:stylesheet> FO customization: <?xml version='1.0'> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:import href="fo/docbook.xsl"/> <xsl:include href="common-customizations.xsl" /> <xsl:param name="paper.type" select="'A4'"/> </xsl:stylesheet>
Any parameters that are specific to one output type are defined in that customization layer only.
If you use a relative path in the href of the xsl:include statement, then that is taken are relative to the location of your customization layer file. That's probably ok if you keep them all in one place.
A customization layer imports the standard DocBook stylesheet file as its starting point. This means you must supply some URI in the href attribute of xsl:import.
That URI can be:
A relative pathname, which is taken to be relative to the location of the customization layer file.
An absolute pathname.
A web address.
Relative and absolute pathnames are not very portable. That means if you give someone else a copy of your customization layer, it may not work because the processor cannot find the original DocBook stylesheet file at the specified location.
Using a web address is generally more portable because most systems have web access today. But having to fetch a large number of stylesheet files over the web will greatly slow down your processing.
A good compromise is to use a web address for the URI but apply an XML catalog to map it to a local file location. See the section called “Map a web address to a local file”.
The most common use of a customization layer is to consistently set parameter values without having to enter them on the command line. Notice in the previous example how simple the command line is, even though two parameters are being set. If you have more than a couple of parameters you regularly use, you might be better off putting them into a customization layer. It simplifies the typing, and ensures you won't forget one.
The XSL syntax for setting parameter values within the stylesheet requires careful attention to detail. The basic syntax is:
<xsl:param name="parametername" select="'parametervalue'"/>
You enter the parameter value as the attribute value of the select attribute. As with all XML attribute values, it is enclosed in quotes (double quotes here). But it also has a second set of inner single quotes. That's to indicate that the parameter value is a string. This is necessary because an XSL parameter can also contain XML elements (called nodes in this context), and an unquoted string is treated as an element name that is expected to have some element content.
The most common mistake when specifying parameter values in a customization layer is to omit the inner quotes. You won't necessarily see any error message, and you'll be wondering why your parameter didn't work.
XSL supports the definition of named sets of attributes. An attribute-set is like a complex parameter that lets you define a collection of attribute names and values that can be referenced by a single attribute-set name. That permits you to "define once, use globally" any groups of attributes that apply in more than one place. The DocBook FO stylesheet defines several attribute-sets in the fo/param.xsl stylesheet file. These can be customized like parameters in your customization layer. Here is an example of the definition of an attribute-set named section.title.properties:
<xsl:attribute-set name="section.title.properties"> <xsl:attribute name="font-family"> <xsl:value-of select="$title.font.family"/> </xsl:attribute> <xsl:attribute name="font-weight">bold</xsl:attribute> <!-- font size is calculated dynamically by section.heading template --> <xsl:attribute name="keep-with-next.within-column">always</xsl:attribute> <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute> <xsl:attribute name="space-before.optimum">1.0em</xsl:attribute> <xsl:attribute name="space-before.maximum">1.2em</xsl:attribute> </xsl:attribute-set>
It consists of a set of xsl:attribute elements enclosed in a xsl:attribute-set wrapper. The name of the set is assigned to the wrapper. This attribute-set is used for all section titles, on the assumption that they share many formatting properties. These attributes are to be inserted into the fo:block start tag for the section title. That is accomplished in a DocBook XSL template with a xsl:use-attribute-sets attribute. Here is an example from the fo/sections.xsl stylesheet file:
<xsl:template name="section.heading"> <xsl:param name="level" select="1"/> <xsl:param name="title"/> <fo:block xsl:use-attribute-sets="section.title.properties"> ...
Note that the font size attribute needs to be added to each to complete the specification for the title text.
To customize an attribute-set, you don't have to copy the whole thing to your customization layer. It is a feature of attribute-sets that if more than one set has the same name, then they are effectively merged. That means you can add or modify just a few attributes and leave the rest as they are in the DocBook stylesheet. For example, if you want to left-align all your section headings, you could add this to your customization layer:
<xsl:attribute-set name="section.title.properties"> <xsl:attribute name="text-align">left</xsl:attribute> </xsl:attribute-set>
This is merged with the other attributes at processing time.
One of the customization features of the DocBook XSL stylesheets is a set of empty XSL templates. These are placeholder templates that can be filled in by you. They are automatically called at particular points during processing, but by default they are empty and have no effect. If you fill them in, then they will have the effect you define.
For example, there is an empty template named user.header.content in the HTML stylesheet. It is called at the top of each HTML page generated during chunking. If you put some content in such a named template in your customization layer, then that content will appear at the top of every HTML output file. A trivial example is:
<xsl:template name="user.header.content"> <p><b>Hi Mom!<b></p> </xsl:template>
The template body can be any valid XSL coding that can generate whatever content you want. A more complete example of HTML headers and footers is in the section called “HTML headers and footers”.
The DocBook XSL distribution includes some tools for generating new versions of certain templates. Title pages are one feature that are hard to standardize, because everyone seems to want to do them differently. So the distribution includes a special stylesheet template/titlepage.xsl that generates a titlepage stylesheet module from a titlepage specification file. The titlepage specification file is written in XML (not DocBook). See the section called “HTML title page customization” for a complete example.
Generated text is any text that appears in the output that was not in the original XML input file. Examples include "Chapter 5", "Example 7.2", "Table of Contents", as well as the text generated for xref cross references. The customization features for generated text are quite powerful. You can customize each type of generated text separately, and you can do it for any of the languages supported by DocBook.
If you want to see where the default generated text comes from, start by looking in the file common/en.xml included with the DocBook distribution. That file has all the text strings generated for English text. You'll notice that the common directory also has similar files for many other languages.
In the common/en.xml file, you'll see several groups of elements. The first group looks like this:
<l:gentext key="Abstract" text="Abstract"/> <l:gentext key="abstract" text="Abstract"/> <l:gentext key="Answer" text="A:"/> <l:gentext key="answer" text="A:"/> <l:gentext key="Appendix" text="Appendix"/> <l:gentext key="appendix" text="appendix"/>
These l:gentext elements associate a printable string (the text attribute) with each element name (the key attribute). These gentext elements are in the l: namespace (localization) to keep them separate. The equivalent lines in the German file common/de.xml look like this:
<l:gentext key="Abstract" text="Zusammenfassung"/> <l:gentext key="abstract" text="Zusammenfassung"/> <l:gentext key="Answer" text="A:"/> <l:gentext key="answer" text="A:"/> <l:gentext key="Appendix" text="Anhang"/> <l:gentext key="appendix" text="Anhang"/>
Other groups of elements are contained within a l:context element which indicates in what context those strings will be used. Here is a sample from the English title context:
<l:context name="title"> <l:template name="abstract" text="%t"/> <l:template name="answer" text="%t"/> <l:template name="appendix" text="Appendix %n. %t"/>
Here the text attribute value is taken to be a text template that is filled in at run time. Any %t mark in the attribute value is a placeholder for the element's title, and %n is a placeholder for its number (if it is numbered).
To customize one or more generated text strings, you create local versions of the generated text elements in your customization layer. Your collection of new generated text elements is put into a DocBook parameter named local.l10n.xml. During processing that requires generated text, that parameter is always checked before using the default values.
Below is a short generated text customization for changing the generated text for a chapter cross reference from the default Chapter 3 to something like Chapter 3: “Using a mouse”.
<xsl:param name="local.l10n.xml" select="document('')"/> <l:i18n xmlns:l="http://docbook.sourceforge.net/xmlns/l10n/1.0"> <l:l10n language="en"> <l:context name="xref"> <l:template name="chapter" text="Chapter %n: “%t”"/> </l:context> </l:l10n> </l:i18n>
Defines an XSL parameter named local.l10n.xml. The select attribute that provides the content of the parameter performs a neat trick. The XSL document() function normally opens and reads another file. But the blank function argument is a special case, which means to read the current document, that is, the current XSL file. This loads your entire customization layer file into the parameter. Once loaded, specific instances of generated text can be extracted as needed.
The root element for a set of generated text elements. The i18n name is short for internationalization.
Wrapper element for a set of generated text elements for a given language, in this case English.
Identifies in which context the contained generated text strings will be applied. The xref context is used when generating text for xref cross reference elements.
Defines a new generated text string for a given element, in this case chapter. The numerical character entities are left and right double quotes.
See the section called “Customizing cross references” for a longer example.
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:
Find the DocBook XSL template that handles the text you want to change.
Copy that template to your customization layer.
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. It's 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 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 called “Customizing toc presentation”.
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 apply to 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 to see what other templates it calls or applies. Hopefully at some point you will find the template that does the part you want to change. That's the template you want to duplicate and modify in your customization layer.
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. In general, 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 formalapara. Its effect is to just apply the original such template in block.xsl, which avoids the nested <p></p> tags.
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.