Technical Articles
XSLT stylesheet inheritance with xsl:import
Extensible Stylesheet Language Transformations, or XSLT for short, is a powerful language for writing XML transformations. With XSLT, you can create succint and elegant solutions to thorny XML mapping problems. The language has a number of cool tricks up its sleeve, and in this blog post, I’m going to show you one of the lesser known ones: Stylesheet inheritance with the <xsl:import>
declaration.
With this technique, you can solve mapping problems that would otherwise require you to make a copy of an existing stylesheet, and modify the copy. In other words: With stylesheet inheritance, you can uphold the Don’t Repeat Yourself (DRY) principle and avoid code duplication.
To start things off, let’s take a look at how you can include an XSLT stylesheet in another stylesheet.
Importing stylesheets
XSLT supports two ways of including a stylesheet in another stylesheet: <xsl:include>
and <xsl:import>
. Both declarations must be top-level elements in the stylesheet, i.e. children of the <xsl:stylesheet>
element. However, <xsl:import>
elements must always be the first child elements of <xsl:stylesheet>
.
<xsl:include>
and <xsl:import>
are similar, but they differ in one area, which is central to the topic of this blog post: When importing a stylesheet with <xsl:import>
, templates defined in the importing stylesheet, has higher precedence than templates defined in the imported stylesheet.
What that means, is that a stylesheet that imports another stylesheet, can override the templates defined in the imported stylesheet. This lets us create a customised version of a stylesheet, changing only certain aspects of that stylesheet.
A concrete example
Let’s get a little more practical, and take a look at a concrete example. Here’s a simple stylesheet that creates a header, a footer and five <line>
elements in between:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="/">
<doc>
<xsl:call-template name="header"/>
<xsl:call-template name="lines"/>
<xsl:call-template name="footer"/>
</doc>
</xsl:template>
<xsl:template name="header">
<header>
<xsl:text>Header content goes here</xsl:text>
</header>
</xsl:template>
<xsl:template name="lines">
<lines>
<xsl:for-each select="1 to 5">
<xsl:variable name="linenum" select="."/>
<xsl:call-template name="single-line">
<xsl:with-param name="linenum" select="$linenum"/>
<xsl:with-param name="content" select="concat('Line ', $linenum, ' content goes here')"/>
</xsl:call-template>
</xsl:for-each>
</lines>
</xsl:template>
<xsl:template name="single-line">
<xsl:param name="linenum"/>
<xsl:param name="content"/>
<line number="{$linenum}">
<xsl:value-of select="$content"/>
</line>
</xsl:template>
<xsl:template name="footer">
<footer>
<xsl:text>Footer content goes here</xsl:text>
</footer>
</xsl:template>
</xsl:stylesheet>
It generates the following output:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<header>Header content goes here</header>
<lines>
<line number="1">Line 1 content goes here</line>
<line number="2">Line 2 content goes here</line>
<line number="3">Line 3 content goes here</line>
<line number="4">Line 4 content goes here</line>
<line number="5">Line 5 content goes here</line>
</lines>
<footer>Footer content goes here</footer>
</doc>
(For simplicity’s sake, this stylesheet always produces the same output, regardless of which document it is applied to.)
Now imagine that we want to make a slight change. In another integration scenario, we need the <line>
element to look like this:
<line>
<number>x</number>
<content>Line x content goes here</content>
</line>
How can we achieve this? Well, we could make a copy of the original stylesheet, and then rewrite the single-line
template to produce the desired output. That would work, obviously, but it’s not the best solution by far. All updates and fixes made to the original stylesheet would have to be applied to the copy as well. DRY, right?
Instead, let’s use the <xsl:import>
declaration to import the stylesheet general.xsl
into a second, smaller stylesheet, and then override the single-line
template. Here’s the complete stylesheet:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:import href="general.xsl"/>
<xsl:template name="single-line">
<xsl:param name="linenum"/>
<xsl:param name="content"/>
<line>
<number>
<xsl:value-of select="$linenum"/>
</number>
<content>
<xsl:value-of select="$content"/>
</content>
</line>
</xsl:template>
</xsl:stylesheet>
This customised stylesheet produces the following output:
<?xml version="1.0" encoding="UTF-8"?>
<doc>
<header>Header content goes here</header>
<lines>
<line>
<number>1</number>
<content>Line 1 content goes here</content>
</line>
<line>
<number>2</number>
<content>Line 2 content goes here</content>
</line>
<line>
<number>3</number>
<content>Line 3 content goes here</content>
</line>
<line>
<number>4</number>
<content>Line 4 content goes here</content>
</line>
<line>
<number>5</number>
<content>Line 5 content goes here</content>
</line>
</lines>
<footer>Footer content goes here</footer>
</doc>
The <line>
element has been updated according to the new requirement, but the rest of the output remains unchanged. Mission accomplished, without duplicated code!
Stylesheet inheritance in Cloud Integration
There’s not much to implementing the technique discussed here in Cloud Integration. Upload both stylesheets on the Resources tab of your integration flow, and add an XSLT Mapping step that uses the importing stylesheet. That’s it; CPI’s XSLT processor will take it from there.
Hello Morten.
I have generic question - what are those requirements (based on your experience) when we should give preference to XSLT, instead of regular mapping?
Did you faced any tasks when XML transformation can be solved only using XSLT? Thanks.
Hi Olegs
That’s a very good question. I should probably expand on this in a separate blog post, but let me give you a brief answer now. XSLT is particularly well suited for complex mappings, where the source and target structures are very different. XPath is at the core of XSLT, so compared to Message mapping, navigating XML structures, and implementing complex rules is incredibly easy. Message Mapping is easy to pick up and get started with, and for mappings that are not too complicated, it’s a very nice tool. However, complicated mappings can be really hard to implement in Message Mapping, and at the same time almost trivial in XSLT. That power comes at the cost of a quite steep learning curve, though.
Regards,
Morten
Hi Olegs
I finally got around to writing a blog post comparing the mapping options offered by Cloud Integration. You can check it out here: Cloud Integration mapping: Your options explained and compared
Regards,
Morten
Thank you Morten.
It would be nice if it would be possible to add a folder or archive on the CPI. So it would be possible to organize the sub XSLT scripts.
Hi Florian
The resource handling in WebUI has improved quite a lot over time, so I would definitely not rule something like that out. Let's see what happens 🙂
Regards,
Morten
Hi all,
I know the blog is already a little bit older, but I just tried it and it is not successful. Can anybody confirm that the inheritance is still working in CPI for XSLT resources?
Thanks and Regards
Jürgen
Hi Jürgen
I just tried the example, and it still works as intended.
How does it fail for you? Also, are you on Neo or Cloud Foundry?
Regards,
Morten