Cette page n'est pas disponible en français. Veuillez-nous en excuser.

ABIS Infor - 2013-04

Recursive copying in XSLT

Ludo Van den dries (ABIS) - 15 March 2013

Abstract

When exchanging data in XML format (e.g. through an Enterprise Service Bus), we often use XSLT for transforming the XML data (rearrange the structure, omit & add data) to realize the required mapping between the source and target applications. By leveraging XSLT's 'odd' programming paradigm (i.e. functional programming, rather than procedural), some seemingly complex tasks can be accomplished with very little -- and generic -- coding.

The problem

Here is your source:

<Bestsellers>
	<Book nr="15">
		<Title>Living in the Cloud</Title>
		<Author>James Oon</Author>
		<Price>17.5</Price>
	</Book>
	<CD nr="18">
		<Title>Hurry Up</Title>
		<Artists>
			<Artist>Glenn Miller</Artist>
			<Artist>Duke Ellington</Artist>
		</Artists>
		<Price>44.5</Price>
	</CD>
	<Book nr="33">
		<Title>Me and my Tablet</Title>
		<Author>Mary Queen</Author>
		<Price>22.99</Price>
	</Book>
</Bestsellers>

Here's a typical mapping requirement: copy all the bestsellers, but omit the price, and 'flatten' the structure by skipping the Artists level, so that it looks like this:

<Bestsellers>
	<Book nr="15">
		<Title>Living in the Cloud</Title>
		<Author>James Oon</Author>
	</Book>
	<CD nr="18">
		<Title>Hurry Up</Title>
		<Artist>Glenn Miller</Artist>
		<Artist>Duke Ellington</Artist>
	</CD>
	<Book nr="33">
		<Title>Me and my Tablet</Title>
		<Author>Mary Queen</Author>
	</Book>
</Bestsellers> 

For next do while if then else?

This problem can be handled in XSLT in a traditional procedural way, using the PULL technique: just like you would do in Cobol or Basic or Java, you can use loops (xsl:for-each), selections (xsl:if) and clever XPath expressions, to walk through the tree and copy exactly what you want, but that would require nested loops, tightly matched to the specific structure of the source.

It can be done in a shorter, more elegant, more generic and more maintainable way, using the PUSH technique with its inherent recursive power.

Recursion

This is the true power of XSLT as a functional programming language: the data at the steering wheel of your program flow, and not vice versa.

Here is a generic stylesheet that literally copies the entire source to the output:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="node()|@*">
		<xsl:copy>
			<xsl:apply-templates select="node()|@*"/>
		</xsl:copy>
	</xsl:template>
</xsl:stylesheet>

This is how it works: whenever you stumble onto an attribute or any other node, copy the thing itself and proceed in the same way with its attributes and its direct child nodes.

Notice the special handling of attributes (since attributes are not children of their parent element -- don't try to explain this to genealogists...).

Just add the specifics

The only thing you have to add to this generic stylesheet is the special handling for the special stuff (like in our example: omit the price, and flatten the artists): just 'override' their generic handling with a more specific one by writing a more specific template for them:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template match="node()|@*">
		<xsl:copy>
			<xsl:apply-templates select="node()|@*"/>
		</xsl:copy>
	</xsl:template>
	<xsl:template match="Price">
		<!--do nothing-->
	</xsl:template>
	<xsl:template match="Artists">
		<xsl:apply-templates select="node()|@*"/>
	</xsl:template>
</xsl:stylesheet>

Put it in your toolkit

If you put the generic copy code in a separate stylesheet (e.g. CopyAll.xsl), then your final stylesheet would look like this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:include href="CopyAll.xsl"/>
	<xsl:template match="Price">
	</xsl:template>
	<xsl:template match="Artists">
		<xsl:apply-templates select="node()|@*"/>
	</xsl:template>
</xsl:stylesheet>

Bottom line

You cannot do everything with XSLT, but there are many things you can do in a surprisingly simple and elegant way.

Want to discover some more? Try our XSLT course.