The DocBook XSL stylesheets are written in a modular fashion. Each of the HTML and FO stylesheets starts with a driver file that assembles a collection of component files into a complete stylesheet. This modular design puts similar things together into smaller files that are easier to write and maintain than one big stylesheet. The modular stylesheet files are distributed among four directories:
contains code common to both stylesheets, including localization data
a stylesheet that produces XSL FO result trees
a stylesheet that produces HTML/XHTML result trees
contains schema-independent functions
The driver files for each of HTML and FO stylesheets are html/docbook.xsl and fo/docbook.xsl, respectively. A driver file consists mostly of a bunch of <xsl:include> instructions to pull in the component templates, and then defines some top-level templates. For example:
<xsl:include href="../VERSION"/> <xsl:include href="../lib/lib.xsl"/> <xsl:include href="../common/l10n.xsl"/> <xsl:include href="../common/common.xsl"/> <xsl:include href="autotoc.xsl"/> <xsl:include href="lists.xsl"/> <xsl:include href="callout.xsl"/> ... <xsl:include href="param.xsl"/> <xsl:include href="pi.xsl"/>
The first four modules are shared with the FO stylesheet and are referenced using relative pathnames to the common directories. Then the long list of component stylesheets starts. Pathnames in include statements are always taken to be relative to the including file. Each included file must be a valid XSL stylesheet, which means its root element must be <xsl:stylesheet>.
XSL actually provides two inclusion mechanisms: <xsl:include> and <xsl:import>. Of the two, <xsl:include> is the simpler. It treats the included content as if it were actually typed into the file at that point, and doesn't give it any more or less precedence relative to the surrounding text. It is best used when assembling dissimilar templates that don't overlap what they match. The DocBook driver files use this instruction to assemble a set of modules into a stylesheet.
In contrast, <xsl:import> lets you manage the precedence of templates and variables. It is the preferred mode of customizing another stylesheet because it lets you override definitions in the distributed stylesheet with your own, without altering the distribution files at all. You simply import the whole stylesheet and add whatever changes you want.
The precedence rules for import are detailed and rigorously defined in the XSL standard. The basic rule is that any templates and variables in the importing stylesheet have precedence over equivalent templates and variables in the imported stylesheet. Think of the imported stylesheet elements as a fallback collection, to be used only if a match is not found in the current stylesheet. You can customize the templates you want to change in your stylesheet file, and let the imported stylesheet handle the rest.
Customizing a DocBook XSL stylesheet is the opposite of customizing a DocBook DTD. When you customize a DocBook DTD, the rules of XML and SGML dictate that the first of any duplicate declarations wins. Any subsequent declarations of the same element or entity are ignored. The architecture of the DTD provides slots for inserting your own custom declarations early enough in the DTD for them to override the standard declarations. In contrast, customizing an XSL stylesheet is simpler because your definitions have precedence over imported ones.
You can carry modularization to deeper levels because module files can also include or import other modules. You'll need to be careful to maintain the precedence that you want as the modules get rolled up into a complete stylesheet.
There is currently one example of customizing with <xsl:import> in the HTML version of the DocBook stylesheets. The xtchunk.xsl stylesheet modifies the HTML processing to output many smaller HTML files rather than a single large file per input document. It uses XSL extensions defined only in the XSL processor XT. In the driver file xtchunk.xsl, the first instruction is <xsl:import href="docbook.xsl"/>. That instruction imports the original driver file, which in turn uses many <xsl:include> instructions to include all the modules. That single import instruction gives the new stylesheet the complete set of DocBook templates to start with.
After the import, xtchunk.xsl redefines some of the templates and adds some new ones. Here is one example of a redefined template:
Original template in autotoc.xsl <xsl:template name="href.target"> <xsl:param name="object" select="."/> <xsl:text>#</xsl:text> <xsl:call-template name="object.id"> <xsl:with-param name="object" select="$object"/> </xsl:call-template> </xsl:template> New template in xtchunk.xsl <xsl:template name="href.target"> <xsl:param name="object" select="."/> <xsl:variable name="ischunk"> <xsl:call-template name="chunk"> <xsl:with-param name="node" select="$object"/> </xsl:call-template> </xsl:variable> <xsl:apply-templates mode="chunk-filename" select="$object"/> <xsl:if test="$ischunk='0'"> <xsl:text>#</xsl:text> <xsl:call-template name="object.id"> <xsl:with-param name="object" select="$object"/> </xsl:call-template> </xsl:if> </xsl:template>
The new template handles the more complex processing of HREFs when the output is split into many HTML files. Where the old template could simply output #object.id, the new one outputs filename#object.id.
You may not have to define any new templates, however. The DocBook stylesheets are parameterized using XSL variables rather than hard-coded values for many of the formatting features. Since the <xsl:import> mechanism also lets you redefine global variables, this gives you an easy way to customize many features of the DocBook stylesheets. Over time, more features will be parameterized to permit customization. If you find hardcoded values in the stylesheets that would be useful to customize, please let the maintainer know.
Near the end of the list of includes in the main DocBook driver file is the instruction <xsl:include href="param.xsl"/>. The param.xsl file is the most important module for customizing a DocBook XSL stylesheet. This module contains no templates, only definitions of stylesheet variables. Since these variables are defined outside of any template, they are global variables and apply to the entire stylesheet. By redefining these variables in an importing stylesheet, you can change the behavior of the stylesheet.
To create a customized DocBook stylesheet, you simply create a new stylesheet file such as mystyle.xsl that imports the standard stylesheet and adds your own new variable definitions. Here is an example of a complete custom stylesheet that changes the depth of sections listed in the table of contents from two to three:
<?xml version='1.0'?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0' xmlns="http://www.w3.org/TR/xhtml1/transitional" exclude-result-prefixes="#default"> <xsl:import href="docbook.xsl"/> <xsl:variable name="toc.section.depth">3</xsl:variable> <!-- Add other variable definitions here --> </xsl:stylesheet>
Following the opening stylesheet element are the import instruction and one variable definition. The variable toc.section.depth was defined in param.xsl with value "2", and here it is defined as "3". Since the importing stylesheet takes precedence, this new value is used. Thus documents processed with mystyle.xsl instead of docbook.xsl will have three levels of sections in the tables of contents, and all other processing will be the same.
Use the list of variables in param.xsl as your guide for creating a custom stylesheet. If the changes you want are controlled by a variable there, then customizing is easy.
If the changes you want are more extensive than what is supported by variables, you can write new templates. You can put your new templates directly in your importing stylesheet, or you can modularize your importing stylesheet as well. You can write your own stylesheet module containing a collection of templates for processing lists, for example, and put them in a file named mylists.xsl. Then your importing stylesheet can pull in your list templates with a <xsl:include href="mylists.xsl"/> instruction. Since your included template definitions appear after the main import instruction, your templates will take precedence.
You'll need to make sure your new templates are compatible with the remaining modules, which means:
Any named templates should use the same name so calling templates in other modules can find them.
Your template set should process the same elements matched by templates in the original module, to ensure complete coverage.
Include the same set of <xsl:param> elements in each template to interface properly with any calling templates, although you can set different values for your parameters.
Any templates that are used like subroutines to return a value should return the same data type.
Another approach to customizing the stylesheets is to write your own driver file. Instead of using <xsl:import href="docbook.xsl"/>, you copy that file to a new name and rewrite any of the <xsl:include/> instructions to assemble a custom collection of stylesheet modules. One reason to do this is to speed up processing by reducing the size of the stylesheet. If you are using a customized DocBook DTD that omits many elements you never use, you might be able to omit those modules of the stylesheet.
The DocBook stylesheets include features for localizing generated text, that is, printing any generated text in a language other than the default English. In general, the stylesheets will switch to the language identified by a lang attribute when processing elements in your documents. If your documents use the lang attribute, then you don't need to customize the stylesheets at all for localization.
As far as the stylesheets go, a lang attribute is inherited by the descendents of a document element. The stylesheet searches for a lang attribute using this XPath expression:
<xsl:variable name="lang-attr" select="($target/ancestor-or-self::*/@lang |$target/ancestor-or-self::*/@xml:lang)[last()]"/>
This locates the attribute on the current element or its most recent ancestor. Thus a lang attribute is in effect for an element and all of its descendents, unless it is reset in one of those descendents. If you define it in only your document root element, then it applies to the whole document:
<?xml version="1.0"?> <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.0//EN" "docbook.dtd"> <book lang="fr"> ... </book>
When text is being generated, the stylesheet checks the most recent lang attribute and looks up the generated text strings for that language in a localization XML file. These are located in the common directory of the stylesheets, one file per language. Here is the top of the file fr.xml:
<localization language="fr"> <gentext key="abstract" text="Résumé"/> <gentext key="answer" text="R:"/> <gentext key="appendix" text="Annexe"/> <gentext key="article" text="Article"/> <gentext key="bibliography" text="Bibliographie"/> ...
The stylesheet templates use the gentext key names, and then the stylesheet looks up the associated text value when the document is processed with that lang setting. The file l10n.xml (note the .xml suffix) lists the filenames of all the supported languages.
You can also create a custom stylesheet that sets the language. That might be useful if your documents don't make appropriate use of the lang attribute. The module l10n.xsl defines two global variables that can be overridden with an importing stylesheet as described above. Here are their default definitions:
<xsl:variable name="l10n.gentext.language"></xsl:variable> <xsl:variable name="l10n.gentext.default.language">en</xsl:variable>
The first one sets the language for all elements, regardless of an element's lang attribute value. The second just sets a default language for any elements that haven't got a lang setting of their own (or their ancestors).