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
XSL
need 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
A
requires style sheetsB
andC
. Also, style sheetB
requires style sheetC
for proper functioning. In such case, you have two options: importC
intoB
and then only importB
intoA
. A better way, to make future dependency management straightforward, is to importC
andB
intoA
, in this order. That way, it is guaranteed thatB
has access toC
since 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.