Combining Stylesheets with Include and Import
by Bob DuCharme
November 01, 2000
Combining Stylesheets with Include and
Import
The xsl:include and xsl:import instructions give
you ways to incorporate XSLT stylesheets programmatically.
There are two
situations where this is useful.
Large, complex stylesheets, like large complex programs, are
easier to maintain when you break them into modules with specific
roles to play. In XSLT, the xsl:include and
xsl:import instructions let you assemble the pieces. This
modular approach also makes it possible to share parts of a
stylesheet with other stylesheets that only want certain features
and not the whole thing; they can just include or import the parts
they need.
Customizing an existing stylesheet without actually editing
that stylesheet is easy, because you incorporate it, and then
separately override any template rules that don't do exactly what
you want.
xsl:include
The xsl:include instruction is similar to the
include instruction available in several programming
languages. The XSLT processor replaces it with the contents of the
stylesheet named in the href attribute. For example, the
following makehtml.xsl stylesheet names the
inlines.xsl stylesheet to incorporate that stylesheet:
<!-- makehtml.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:include href="inlines.xsl"/>
<xsl:template match="chapter">
<html><xsl:apply-templates/></html>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="chapter/title">
<h4><xsl:apply-templates/></h4>
</xsl:template>
</xsl:stylesheet>
If inlines.xsl looks like this,
<!-- inlines.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="emphasis">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="literal">
<tt><xsl:apply-templates/></tt>
</xsl:template>
</xsl:stylesheet>
the XSLT processor will treat makehtml.xsl as if it looked like
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="emphasis">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="literal">
<tt><xsl:apply-templates/></tt>
</xsl:template>
<xsl:template match="chapter">
<html><xsl:apply-templates/></html>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="chapter/title">
<h4><xsl:apply-templates/></h4>
</xsl:template>
</xsl:stylesheet>
The complete inlines.xsl stylesheet didn't get inserted; its
contents did. In other words, everything between its xsl:stylesheet
tags (the stylesheet's "emphasis" and "literal" template rules) got inserted
where the makehtml.xsl stylesheet had its xsl:include
instruction.
The included stylesheet must still be a complete stylesheet. Unlike
the include mechanisms offered by some programming languages,
the part you're including can't just be a piece of something -- it
must be a complete stylesheet. (If you really do want to incorporate a
fragment of a stylesheet into another one, you can use XML general
entities, because after all, XSLT stylesheets are XML documents.)
An included stylesheet may in turn include other stylesheets, and they may
include other stylesheets. There's no limit to the levels of inclusion that
you can use, although the more you do it, the more complexity you have to keep
track of.
The xsl:include element can go anywhere you want in a
stylesheet, as long as it's a top-level element -- that is, a child of
the xsl:stylesheet element that makes up the main body of the
stylesheet. Putting an xsl:include instruction inside another
element, such as an xsl:template template rule, wouldn't make
sense, anyway; there would be no point to inserting another
stylesheet's complete contents inside of a template rule.
The example above is simple. A large, complex stylesheet like the
one that turns DocBook files into HTML (see Norm Walsh's DocBook XSL
stylesheet) uses xsl:include on a larger scale. Of the 62
XSLT instructions in version 1.2's main docbook.xsl file, 38
of them are xsl:include instructions that incorporate its
various components such as division.xsl and
titlepage.xsl.
Using xsl:include doesn't change XSLT's approach to
multiple template rules that apply to the same node. If the XSLT
processor can't find one template that is more specific than another
for a particular source tree node, it's an error. Using
xsl:include does increase the chance of this error happening,
especially if you include stylesheets that include other stylesheets,
because it's harder to keep track of the full collection of template
rules being grouped together.
xsl:import
The xsl:import instruction is similar to xsl:include
except that instructions in the imported stylesheet can be be overridden by
instructions in the importing stylesheet and in any included stylesheet. For
example, the following makehtml2.xsl stylesheet tells the XSLT
processor to import the inlines.xsl stylesheet. The syntax is nearly
identical to the use of xml:include; you name the imported stylesheet
with the href attribute.
<!-- makehtml2.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:import href="inlines.xsl"/>
<xsl:template match="chapter">
<html><xsl:apply-templates/></html>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="chapter/title">
<h4><xsl:apply-templates/></h4>
</xsl:template>
<xsl:template match="emphasis">
<i><xsl:apply-templates/></i>
</xsl:template>
</xsl:stylesheet>
If inlines.xsl looks like this,
<!-- inlines.xsl -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="emphasis">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="literal">
<tt><xsl:apply-templates/></tt>
</xsl:template>
</xsl:stylesheet>
the XSLT processor will treat makehtml2.xsl as if it looked like
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="emphasis">
<i><xsl:apply-templates/></i>
</xsl:template>
<xsl:template match="literal">
<tt><xsl:apply-templates/></tt>
</xsl:template>
<xsl:template match="chapter">
<html><xsl:apply-templates/></html>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="chapter/title">
<h4><xsl:apply-templates/></h4>
</xsl:template>
</xsl:stylesheet>
The inlines.xsl stylesheet's "literal" template rule was
added to the normalized makehtml2.xsl stylesheet, but the
same stylesheet's "emphasis" template rule was ignored. Both
makehtml2.xsl and inlines.xsl have template rules
with a match pattern of "emphasis," and because inlines.xsl
was imported and not included, an XSLT processor will use the one in
makehtml2.xsl, not the one in inlines.xsl. The
XSLT processor will add emphasis element nodes to the result
tree surrounded by the i start- and end-tags shown in
makehtml2.xsl and not by the b tags in the imported
"emphasis" template rule in inlines.xsl.
(Note that if the example above had used xsl:include instead of
xsl:import, the presence of two xsl:template elements with a
match pattern of "emphasis" would have been an error.)
As with inclusion, there's no limit to the levels of importing you
may use. Your imported stylesheets may import other stylesheets,
which may import yet more stylesheets, and so on.
Any xsl:import elements must come before any other
elements from the XSLT namespace in a stylesheet. Thus the XSLT
processor knows that when it finds a template rule with the same match
pattern as one in an imported template rule, it can forget about the
imported one and use the most recently found one.
For a more industrial-strength demonstration of the difference
between xsl:include and xsl:import, here's a real
example: the stylesheet I use to create HTML versions of this
column. Because I'm using the DocBook DTD, I use the aforementioned DocBook stylesheets by
Norm Walsh, but I wanted to make various changes to my own copy of the
stylesheets. For example, Norm's stylesheets italicize all
emphasis elements, but I want emphasized text within
literallayout elements to be bolded and not italicized so
that the important parts of sample stylesheets and XML documents stand
out. (The sample stylesheets above all include sections tagged as
emphasis elements.) To make these changes, I used to edit the
various files included as part of Norm's stylesheet and add a comment
with my name in them so that I could locate these changes if I ever
had to make them again to a newer version of the stylesheet.
Since I sometimes write using a notebook computer running Linux and
sometimes on a desktop computer running Windows, I had to remember
which stylesheet files I edited and then copy them to the other
computer. I just zipped up all the stylesheet files and unzipped them
on the other machine. While this is a simple procedure, it still
leaves too much room for error, and the more changes I made, the more
trouble it would be to upgrade to a newer version of the DocBook
stylesheets.
By using xsl:import and xsl:include, most of
these problems go away. The complete main stylesheet that I now use to
create an HTML version of this column looks like the following.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:import href="/usr/local/lib/xml/xsl/docbook/html/docbook.xsl"/>
<xsl:include href="dbMods.xsl"/>
</xsl:stylesheet>
All it does is import the main DocBook stylesheet file (a
stylesheet that, as I mentioned, includes many component files) and
then include a stylesheet that I wrote called
dbMods.xsl. Now, when I want to change something in my copy
of the DocBook stylesheet, I copy the appropriate
xsl:template element from the source file in the
/usr/local/lib/xml/xsl/docbook/html/ directly to my
dbMods.xsl stylesheet and then make my changes there. Here
are two of the edited xsl:template elements:
<!-- (dbMods.xml excerpt) -->
<xsl:template match="literallayout/emphasis">
<b><xsl:apply-templates/></b>
</xsl:template>
<xsl:template match="ulink">
<a>
<xsl:attribute name="href">
<xsl:value-of select="@url"/>
</xsl:attribute>
<!-- bd added following line -->
<tt><xsl:value-of select="@url"/></tt>
<xsl:apply-templates/>
</a>
</xsl:template>
The first adds b tags around any emphasis element that is
a child of a literallayout element. When you download the DocBook
stylesheet, it has a template rule with a pattern of "emphasis" that adds
i tags around emphasis elements. Because the
dbMods.xsl "literallayout/emphasis" template is more specific than
that (it doesn't match all emphasis nodes, but only the ones that are
children of literallayout elements), an XSLT processor will use it
for those emphasis elements.
The second template above overrides one in the imported DocBook
stylesheet. The regular DocBook version of the template for the
ulink element type adds an a element that uses the
source tree ulink element's url attribute as its
href attribute value. However, it doesn't add any contents
between the result tree's a tags, and I wanted the
url value to show up there, so I added the line that puts it
there between tt ("teletype") tags for the HTML result tree
version. The
comment about adding that line helps me to remember the difference
between the original template and my altered version.
My dbMods.xsl file has many more template rules that I
copied from the original DocBook stylesheet files and then
revised. Taking these revisions from my Linux notebook to my Windows
desktop machine is easy: I just bring the dbMods.xsl
file. (The actual stylesheet file that includes docbook.xsl
and imports dbMods.xsl is slightly different on the Windows
machine from the one you see above because docbook.xsl is not
in a directory named /usr/local/lib/xml/xsl/docbook/html/ on
the Windows machine.)
When a new version of the DocBook stylesheet comes out, I'll just
install it into a new directory, edit the value of the href
attribute in my xsl:import element to point at the new
version's docbook.xsl file, and I'll be ready to go.