facebook google plus twitter
Webucator's Free XSLT 1.0 and XPath 1.0 Tutorial

Lesson: Multiple Documents

Welcome to our free XSLT 1.0 and XPath 1.0 tutorial. This tutorial is based on Webucator's XSLT 1.0 and XPath 1.0 Training course.

Lesson Goals

  • To include XSLTs.
  • To import XSLTs.
  • How conflicting templates are handled.
  • To use the document() function.

Including XSLTs

It is often useful to reuse a template created in a separate XSLT document. This is easily done with xsl:include, which effectively copies the contents of one XSLT into another. The following examples illustrate the use of xsl:include. First is the XSLT to be included (HTML.xsl) followed by the XSLT that includes it.

Code Sample:

MultipleDocs/Demos/HTML.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:template name="StartHTML">
		<xsl:param name="Title" select="'Untitled'"/>
		<html>
			<head>
				<title><xsl:value-of select="$Title"/></title>
			</head>
			<body>
				<h1><xsl:value-of select="$Title"/></h1>
				<ul>
					<xsl:apply-templates select="beatles/beatle">
						<xsl:sort select="name/lastname"/>
					</xsl:apply-templates>
				</ul>
			</body>
		</html>
	</xsl:template>
</xsl:stylesheet>

Code Sample:

MultipleDocs/Demos/BeatlesInclude.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:include href="HTML.xsl"/>
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<xsl:call-template name="StartHTML">
			<xsl:with-param name="Title" select="'Beatles'"/>
		</xsl:call-template>
	</xsl:template>
	<xsl:template match="beatle[not(@real='no')]">
		<li style="color:blue;"><xsl:apply-templates/></li>
	</xsl:template>
	<xsl:template match="beatle">
		<li style="color:red; text-decoration:line-through">
			<xsl:apply-templates/>
		</li>
	</xsl:template>
	<xsl:template match="name">
		<xsl:value-of select="lastname"/>,
		<xsl:value-of select="firstname"/>
	</xsl:template>
</xsl:stylesheet>

These two documents together work exactly as if the first were pasted into the second. To see how it works, transform MultipleDocs/Demos/BeatlesInclude.xml against MultipleDocs/Demos/BeatlesInclude.xsl.

Importing XSLTs

Importing one XSLT into another is very similar to including it. The only difference is that, when importing, templates defined in the importing XSLT take precedence over templates in the imported XSLT. In other words, if there are conflicting templates, the template in the imported XSLT is ignored.

The xsl:import element must be the first element after the open xsl:stylesheet tag. Here is an example of a document that imports HTML.xsl.

Code Sample:

MultipleDocs/Demos/BeatlesImport.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:import href="HTML.xsl"/>
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<xsl:call-template name="StartHTML">
			<xsl:with-param name="Title" select="'Beatles'"/>
		</xsl:call-template>
	</xsl:template>
	<xsl:template match="beatle[not(@real='no')]">
		<li style="color:blue;"><xsl:apply-templates/></li>
	</xsl:template>
	<xsl:template match="beatle">
		<li style="color:red; text-decoration:line-through">
			<xsl:apply-templates/>
		</li>
	</xsl:template>
	<xsl:template match="name">
		<xsl:value-of select="lastname"/>,
		<xsl:value-of select="firstname"/>
	</xsl:template>
</xsl:stylesheet>

In these examples there is no difference between including and importing HTML.xsl. The difference only comes into play when the including/importing XSLT has conflicting templates with the included/imported XSLT. To see how it works, transform MultipleDocs/Demos/BeatlesImport.xml against MultipleDocs/Demos/BeatlesImport.xsl.

Conflict Resolution

The difference between xsl:include and xsl:import can be confusing. The first thing to note is that the difference only comes into play if there are conflicting templates. For example, if there is a template called Important in both the including/importing XSLT and the included/imported XSLT, then the XSLT processor has to decide what to do. That decision is handled as follows:

  1. Two Templates with Equal Priority. The specification states that it is an error to have two templates in an XSLT with equal priority. However, it gives the XSLT processor the option of reporting this error or using the last-is-first rule, meaning that the template defined later should be used. In practice, XSLT processors generally use the last-is-first rule rather than report an error.
    • To see an example of how this works, transform MultipleDocs/Demos/BeatlesConflict.xml against MultipleDocs/Demos/BeatlesConflict.xsl. Note that this example does not address includes or imports as the conflicting templates are in the same document.
  2. Two Templates with the Same Name. It is an error to have two templates with the same name in one XSLT. This error will be reported.
    • To see an example of how this works, transform MultipleDocs/Demos/BeatlesConflict-NamedTemplate.xml against MultipleDocs/Demos/BeatlesConflict-NamedTemplate.xsl. Note that this example does not address includes or imports as the conflicting templates are in the same document.
  3. Including a Template with Equal Priority. With xsl:include, the XSLT processor treats the included templates as if they were copied and pasted directly into the including XSLT at the location of the include tag. Any conflicts are resolved with the last-is-first rule, so, in practice, included templates are ignored if they have the same or lower priority as a template in the including XSLT.
    • To see an example of how this works, transform MultipleDocs/Demos/BeatlesIncludeConflict-Apply.xml against MultipleDocs/Demos/BeatlesIncludeConflict-Apply.xsl.
    • To see an example of how this can result in a reported error with named templates, transform MultipleDocs/Demos/BeatlesIncludeConflict-Call.xml against MultipleDocs/Demos/BeatlesIncludeConflict-Call.xsl.
  4. Importing a Template with Equal or Greater Priority. With xsl:import, all matching templates defined in the importing XSLT take precedence over templates in the imported XSLT. In other words, all conflicting templates in the imported XSLT are ignored, regardless of priority.
    • To see an example of how this works with templates with the same priority, transform MultipleDocs/Demos/BeatlesIncludeConflict-Apply.xml against MultipleDocs/Demos/BeatlesIncludeConflict-Apply.xsl. This is the same as with included templates.
    • To see an example how this works when importing a template with greater priority, transform MultipleDocs/Demos/BeatlesPriority-Import.xml against MultipleDocs/Demos/BeatlesImportConflict-Priority.xsl. This is the different with included templates in that the imported template is ignored even though it has a higher priority.
    • To see an example of how this works with named templates, transform MultipleDocs/Demos/BeatlesIncludeConflict-Call.xml against MultipleDocs/Demos/BeatlesIncludeConflict-Call.xsl. Unlike with xsl:include, no error is reported; the imported conflicting template is just ignored.

So, in practice, the only difference between importing and including XSLTs is this:

  • Matching templates in the included XSLT have a lower precedence than matching templates with the same priority in the including XSLT.
  • Matching templates in the imported XSLT have a lower precedence than all matching templates in the importing XSLT, regardless of priority.

The document() Function

XSLT has a document() function, which is used for selecting nodes from an external XML document. To illustrate, let's look at the following two XML documents.

Code Sample:

MultipleDocs/Demos/BeatlesDocument.xml
<?xml version="1.0"?>
<?xml-stylesheet href="BeatlesDocument.xsl" type="text/xsl"?>
<beatles>
	<beatle link="http://www.paulmccartney.com">
		<name>
			<firstname>Paul</firstname>
			<lastname>McCartney</lastname>
		</name>
	</beatle>
	<beatle link="http://www.johnlennon.com">
		<name>
			<firstname>John</firstname>
			<lastname>Lennon</lastname>
		</name>
	</beatle>
	<beatle link="http://www.georgeharrison.com">
		<name>
			<firstname>George</firstname>
			<lastname>Harrison</lastname>
		</name>
	</beatle>
	<beatle link="http://www.ringostarr.com">
		<name>
			<firstname>Ringo</firstname>
			<lastname>Starr</lastname>
		</name>
	</beatle>
	<beatle link="http://www.webucator.com" real="no">
		<name>
			<firstname>Nat</firstname>
			<lastname>Dunn</lastname>
		</name>
	</beatle>
</beatles>

Code Sample:

MultipleDocs/Demos/BeatlesSongs.xml
<?xml version="1.0" encoding="UTF-8"?>
<Songs>
	<Artist>
		<Name>Paul McCartney</Name>
		<Songs>
			<Song>Love Me Do</Song>
			<Song>Help!</Song>
		</Songs>
	</Artist>
	<Artist>
		<Name>John Lennon</Name>
		<Songs>
			<Song>Helter Skelter</Song>
			<Song>Hey Jude</Song>
		</Songs>
	</Artist>
	<Artist>
		<Name>George Harrison</Name>
		<Songs>
			<Song>Come Together</Song>
			<Song>While My Guitar Gently Weeps</Song>
		</Songs>
	</Artist>
	<Artist>
		<Name>Ringo Starr</Name>
		<Songs>
			<Song>Octopus's Garden</Song>
		</Songs>
	</Artist>
</Songs>

The first document provides basic information about the Beatles, while the second document lists songs that the different Beatles wrote. Now let's take a look at the XSLT below.

Code Sample:

MultipleDocs/Demos/BeatlesDocument.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:import href="HTML.xsl"/>
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<xsl:call-template name="StartHTML">
			<xsl:with-param name="Title" select="'Beatles'"/>
		</xsl:call-template>
	</xsl:template>
	<xsl:template match="beatle[not(@real='no')]">
		<xsl:variable name="BeatleName">
			<xsl:value-of select="name/firstname"/>
			<xsl:text> </xsl:text>
			<xsl:value-of select="name/lastname"/>
		</xsl:variable>
		<li style="color:blue;"><xsl:apply-templates/>
			<ol>
	<xsl:for-each
	select="document('BeatlesSongs.xml')//Artist[Name=$BeatleName]/Songs/Song">
			<li><xsl:value-of select="."/></li>
	</xsl:for-each>
			</ol>
		</li>
	</xsl:template>
	<xsl:template match="beatle">
		<li style="color:red; text-decoration:line-through">
			<xsl:apply-templates/>
		</li>
	</xsl:template>
	<xsl:template match="name">
		<a href="{parent::beatle/@link}">
			<xsl:value-of select="lastname"/>,
			<xsl:value-of select="firstname"/>
		</a>
	</xsl:template>
</xsl:stylesheet>

The xsl:variable element above is used to hold the name of the Beatle currently being processed. The xsl:for-each element uses the document() function to loop through the Song elements in the BeatleSongs.xml document for each Beatle as that Beatle template is processed. To see how it works, transform MultipleDocs/Demos/BeatlesDocument.xml against MultipleDocs/Demos/BeatlesDocument.xsl

Reusing Templates

Duration: 15 to 20 minutes.

In this exercise, you will practice reusing templates.

  1. Open MultipleDocs/Exercises/HTML.xsl and review the document.
  2. Open MultipleDocs/Exercises/CourseAsList.xsl for editing.
  3. Modify this document so that it imports or includes HTML.xsl.
  4. Notice that the lines below are not in the StartHTML template in CourseAsList.xsl. You will need to create another template that holds these lines.
    <div id="courseNum">
    	<xsl:value-of select="/course/head/course_num"/>
    </div>
    <div id="courseLength">
    	<xsl:value-of select="/course/head/course_length"/>
    </div>
    <xsl:apply-templates select="/course/body/prerequisites"/>
    <xsl:apply-templates select="/course/body/outline"/>
  5. To test your solution, transform MultipleDocs/Exercises/XML101.xml against MultipleDocs/Exercises/CourseAsList.xsl.

Solution:

MultipleDocs/Solutions/CourseAsList.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:include href="HTML.xsl"/>
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
	<xsl:call-template name="StartHTML">
		<xsl:with-param  name="Title" select="course/head/title"/>
	</xsl:call-template>
</xsl:template>

<xsl:template match="course">
	<div id="courseNum"><xsl:value-of select="/course/head/course_num"/></div>
	<div id="courseLength">
		<xsl:value-of select="/course/head/course_length"/>
	</div>
	<xsl:apply-templates select="/course/body/prerequisites"/>
	<xsl:apply-templates select="/course/body/outline"/>
</xsl:template>

<xsl:template match="prerequisites">
	<h2>Prerequisites</h2>
	<ul>
	<xsl:for-each select="prereq">
		<li>
			<xsl:value-of select="." />
			<xsl:if test="@optional = 'true'">
				(optional, but recommended)
			</xsl:if>
		</li>
	</xsl:for-each>
	</ul>
</xsl:template>

<xsl:template match="outline">
	<h2>Course Outline</h2>
	<div id="outline">
		<xsl:apply-templates />
	</div>
</xsl:template>

<xsl:template match="topics">
	<ul>
		<xsl:apply-templates />
	</ul>
</xsl:template>

<xsl:template match="title">
	<li><xsl:apply-templates /></li>
</xsl:template>
</xsl:stylesheet>