Now that you have all the parts needed to create XSL stylesheets, you need to think about how best to assemble them. XSLT provides very few constraints on how to structure stylesheets, but building stylesheets that can be reused and maintained requires some extra consideration and discipline.
Generally, stylesheets tend to be either specialized or broad-based general purpose. The implications of this should be considered early. Examples of the first category are the one-off stylesheets created for a specific task, possibly used only for a single class of document. These will be tailored to the needs of the DTD in question and contain only as much flexibility as the DTD enables. The second category could include a base stylesheet and specialization layers to provide for specialist adaptations or a base stylesheet that can quickly be adapted to a whole range of document classes with minimal effort. These two forms require different approaches.
If you are styling a single DTD or schema for print output, some finer considerations come into play. If you have a firm requirement that is well thought-out and likely to be stable once designed, you've just hit stylesheet heaven. It's far more likely that you will design the stylesheet, it will be in use for a few iterations, then the end users of the output or the information providers will decide they want to tweak this bit, subdivide that part, or adapt the schema. If your stylesheet is well thought-out, it may take the strain of such modifications. If it's monolithic and undocumented, you may find the effort needed to update it is almost as great as the effort that was needed to create it.
A second consideration is the environment in which the stylesheet will be used. Stylesheets that are created and used by the same person can often get away with less formal structures and documentation than stylesheets meant for use in a production environment, where the creators of the stylesheets may be unavailable. Similarly, stylesheets that are written with the expectation that their parts may be reused or changed on a regular basis may need more attention to structural detail than stylesheets meant to be written once and never modified or reused.
If the stylesheet is to live for some time, it should be documented, using either XML comments or namespaced inline documentation. An example of this latter style of documentation may be found in the DocBook Stylesheets now hosted on SourceForge at http://docbook.sourceforge.net. When a particular facet of a stylesheet is designed, the ideas in the designer's mind may be very clear but that clarity is unlikely to remain over time. Make it easier for yourself and others by documenting and updating as you work.
Partitioning is the key to designing a successful, reusable stylesheet that can be quickly adapted. If you can swap modules in and out of your stylesheet quickly and easily, your previous work can be reused. If it's also well documented, you will be able to use that stylesheet extract efficiently. The design requirements of XSLT and XPath support this reuse by being side-effect-free languages. It is still possible to write bad stylesheets that are unlikely to be reusable, but some of the nastier aspects of procedural languages won't get in the way. If small, clean, closely related templates are grouped into a single file, the chances of them being reused increases. How you group them is up to you, whether it's by DTD relationships or some other association will depend on circumstance. Moded templates come into their own when prior selection determines the formatting needed, and the moded templates are then called with clear, well-known formatting requirements.
If you are providing a stylesheet for others to extend or modify, certain considerations must be applied.
The stylesheet must be well documented if you want to avoid frequent requests for support. If possible modifications could change the operation of the stylesheet, let users know of the risks they are taking.
Templates that are not meant to be modified must be identified as such, preferably with a clearly understandable rationale for that identification. Try to control stylesheet operation by using parametric variables. This keeps the stylesheet users away from prime code while satisfying their need to be able to customize stylesheet operation. Note the interaction between parameters. A good example is the use of DocBook table of contents (toc) related parameters. A single parameter is set to enable or disable toc generation. Once you've enabled toc generation, individual parameters are used to switch toc generation on or off within the various major container level divisions such as parts, chapters, and appendixes.
If your files are imported into the modifiers stylesheets, the importing stylesheet will take precedence. If an element is found for which you haven't provided a template, let the user know that it's not being formatted. Example 11-1 shows such a template.
<xsl:template match="*"> <xsl:message> <xsl:value-of select="name(.)"/> <xsl:text> with no styling.</xsl:text> </xsl:message> <fo:block color="red" > <xsl:text> <xsl:value-of select="name(..)"/>/<xsl:value-of select="name(.)"/> </xsl:text> </fo:block> </xsl:template>
This produces a block of red text (via the message) that lets the output reader and whomever is processing the stylesheet know that this element has not been processed. The red text helps the message to stand out.
Stylesheets may be crudely divided into two major parts: the part of the stylesheet that specifies the page layout and the part that specifies the content styling. Of course, these two are fairly intimately related, but it is possible to isolate them for design and update purposes. The other advantage of this separation is the refresh rate. Relatively, the second block of templates is most likely to require modification. With this in mind, when starting from a blank page, one early decision should be how many files you will create. Factors influencing this will be the size of the Schema or DTD, context sensitivity (if similar elements will be processed differently in different contexts), stylesheet life, and likely development period. Each of these impacts the file count. If you are used to dealing with large chunks of code, it will be easier. If you are fairly new to stylesheets, it may be easier to develop incrementally, adding more templates slowly, in smaller files, keeping your work focused on small sections of the Schema at any one time, and relying on default handler templates for the elements you haven't processed yet.
A suggested starting point, given just too many options, might be to encapsulate all paging templates in one file, then to look at either one or more files to hold all other templates below the root of the document instance. This provides a basis on which to build. As the stylesheet grows, it may then be split out further, grouping templates either by similar layout style or by some schema-related division. Further considerations, depending on the circumstances for isolation and inclusion might be:
Font properties, using attribute-set elements
Parameter values, which control stylesheet operation
Literals, if internationalization is an issue
Special page-level processing, for instance, front matter, back matter, or any major branches of the Schema that require special handling
Elements from the Schema that are likely to require similar processing (these could be similar block-related formats, inlines, or elements with special whitespace requirements)
If elements are to be formatted differently within each of the contexts within which they occur, as opposed to being formatted identically in any context, groupings of templates will occur naturally by context. This often happens with Schema that make high levels of reuse of lower-level structures (sometimes inappropriately). An example of this might be a block-level element that is formatted as a float, in one context and an inline block in another.
One way to gain a view of a stylesheet's structure is to use the collapsable tree view provided by Internet Explorer from Microsoft. If you think of your page definitions in this way, the candidates for modularization can be seen. Figure 11-1 shows the overall structure.
Standard XML inclusion methods may be used to keep each file a managable size using entities for any of these sections. An example of such usage is shown in Example 11-2.
<?xml version="1.0" ?> <!DOCTYPE xsl:stylesheet [ <!ENTITY lms SYSTEM "lms.xml"> ]> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" version="1.0"> <fo:root> <-- Include the layout-master-set --> &lms; ......
The entity lms contains the layout-master-set for the stylesheet. The remainder of the stylesheet is not shown. The included entity is a well-formed XML file containing the layout-master-set itself.
This technique reduces the clutter in the main stylesheet (or one of its inclusions) and enables the author to focus on the task at hand. Don't overdo it or you will find yourself constantly opening other files just to see what is included! This can be done incrementally, developing a major section of the stylesheet, then, once it's finalized, separating that section into an included entity.
This is perhaps most useful for the larger sections of the page layout information, which, once working, tend not to be disturbed.
Selecting the flow into which a particular content is included is perhaps one of the most critical aspects of stylesheet design. If you are applying single-document-specific templates within a particular flow, to ensure that the specific content is formatted within a specific page-sequence, don't select it by its current position within the document; instead, use an id value assigned to that element. This allows other siblings to be inserted or reorganized without changing the stylesheet.
How content is selected for flows can be varied. An example is shown in Example 11-3.
<fo:page-sequence master-name="rest"> <xsl:call-template name="page-sequence"> <xsl:with-param name="head-R">Page <fo:page-number/> </xsl:with-param> <xsl:with-param name="foot-L">Chapter title</xsl:with-param> </xsl:call-template> <fo:flow flow-name="xsl-region-body"> <xsl:comment>2D</xsl:comment> <!-- Process section contents --> <xsl:apply-templates select="simpdoca/section[@id='chap03']" /> </fo:flow> </fo:page-sequence>
Example 11-3 uses a named template, page-sequence, to specify the header and footer, uses the xsl-region-body flow specify the page layout, and then applies the selected templates that process the section with an id value of chap03.
For a definitive rationale on whether to select including or importing, read Michael Kay's book, XSLT Programmers' Reference (Wrox Press). Take particular note of the relative precedence. When importing, the importing stylesheet takes precedence. This is extremely useful for overrides. Tailoring a stylesheet for a particular case is possible using the import statement. The base stylesheet (which is imported) provides standard processing, the importing stylesheet then tailors that standard processing by only specifying templates that require non-standard formatting.