Organization & Reusability
One of the main challenges when building XSL style sheets is devising sensible project structure. Decent project design may greatly reduce the complexity and verbosity of your style sheets, thus making them easier to both understand and maintain. For us, ease of maintenance of XSL style sheets has become one of the most important properties of any XSL project.
Using templates and functions is - naturally - the first step in the direction of creating more succinct style sheets. But these only allow you to use chunks of functionality in a single file. Once you would like to leverage the power of certain functions and templates across multiple files, you need to start breaking your style sheets apart and then reconnecting them again using import.
xsl:import
To import an externa style sheet into another one, you can use a simple xsl:import tag:
<xsl:import href="common/utils.xsl"/>
The above imports a file in common/utils.xsl into your style sheet, allowing you to use all variables, functions and templates defined in that file.
There are two important things to keep in mind when importing style sheets:
- XSL namespaces: User-defined functions in
XSLneed be declared in custom namespaces, which results in a bit of an overhead when importing them from other files. Apart from the obvious specification of the namespace in the file where the functions are declared, you also need to declare a corresponding namespace in the file into which you are importing them. - Imagine the following scenario: style sheet
Arequires style sheetsBandC. Also, style sheetBrequires style sheetCfor proper functioning. In such case, you have two options: importCintoBand then only importBintoA. A better way, to make future dependency management straightforward, is to importCandBintoA, in this order. That way, it is guaranteed thatBhas access toCsince it has been imported toA.
Let's look at an application of the points mentioned above:
# file: `common/utils.xsl`
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:utils="utils">
<xsl:function name="utils:double">
<xsl:param name="value" />
<xsl:value-of select="$value * 2" />
</xsl:function>
<xsl:function name="utils:triple">
<xsl:param name="value" />
<xsl:value-of select="$value * 3" />
</xsl:function>
</xsl:stylesheet>
# file: `common/layout.xsl`
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:utils="utils">
<xsl:variable name="margin" select="utils:double(10)" />
</xsl:stylesheet>
# file: `main.xsl`
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:utils="utils">
<xsl:import href="common/utils.xsl" />
<xsl:import href="common/layout.xsl" />
<xsl:variable name="padding" select="utils:triple($margin)" />
...
...
...
</xsl:stylesheet>
As you can see, ordering the imports in the correct way allows you to use functions from the utils namespace in both layout.xsl and main.xsl while only importing it once in main.xsl. utils namespace declaration needs to appear in all of them, however.
Relative vs. Absolute
As long as you develop in a single environment, it makes virtually no difference whether you use relative or absolute paths to import style sheets. That is a very unrealistic scenario in real-life, however, and the problems present themselves rather swiftly once you start to develop in and deploy into multiple environments.
Long story short: when importing a style sheet, relative paths are resolved relative to the processor, not the style sheet. (Or at least it would seem that way from our experience.) Therefore, absolute paths are needed. Naturally, another obstacle arises from this requirement: how do you supply XSL with a correct absolute path in a particular environment?
This is where the BIP's xdo configuration file enters the scene. It enables you to define properties later available in the href attribute of xsl:import tags (and, unfortunetaly, nowhere else). Such property can be defined as follows:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://xmlns.oracle.com/oxp/config/" version="1.0.0">
<properties>
...
<property name="xslt.PATH_TO_PROJECT">'/path/to/project'</property>
</properties>
...
</config>
It then subsequently be used as follows:
<xsl:import href="${PATH_TO_PROJECT}/tmp/Context.xsl"/>
(Note the dollar sign position: it is placed before the opening curly brace instead of afterwards, as is the case with typical XSL variables.)
As was implied earlier, BIP variables defined in the xdo can only be used in the context of the xsl:import tag. They may not be accessed anywhere else. Since absolute paths should be used for any other resources such as images as well, it is unfortunately necessary to create an XSL variable that contains an identical absolute path. This can then be used in other contexts than the import tag.
<xsl:variable name="PATH_TO_PROJECT select="'/path/to/project'" />
...
<fo:external-graphic src="{$PATH_TO_PROJECT}/stylesheets/media/ikea.bmp" width="106"/>
(Note that in the example above, the dollar sign is inside the curly braces since we are using a regular XSL variable.)
Resolving Absolute Paths in Multiple Environments
Alright, so now we know that we need to have the correct absolute path in two places in our project for every environment. How do we achieve this without having to enter them manually, which is horribly cumbersome and error-prone?
The best way we have managed to conceive goes as follows: every time the Java project generates a template (or every time it starts, if it runs for prolonged periods of time), it searches for a placeholder such as {#absolute-path#} (utterly dependent on your liking, naturally) in both the stylesheets/xdo.xml configuration file and one agreed-upon XSL style sheet such as stylesheets/context.xsl. It then simply replaces the placeholder with the actual absolute path in the given environment and saves the new files in a tmp directory of the project. It is these files that are then used when generating PDFs, XLSs, etc.