facebook google plus twitter
Webucator's Free XML Tutorial

Lesson: Flow Control

Welcome to our free XML tutorial. This tutorial is based on Webucator's Introduction to XML Training course.

In this lesson, you will learn how to implement flow control in XSLT by looping through XPath result-sets, by using simple "if" statements (conditions), and by implementing more complex conditions with the use of the xsl:choose element.

Lesson Goals

  • Learn how to implement flow control in XSLT.
  • Learn how to loop through XPath result-sets.
  • Learn how to sort results.
  • Learn how to write if statements in XSLT.
  • Learn how to use xsl:choose for more complicated conditionals.

Looping in XSLT

The tag for looping in XSLT is <xsl:for-each>. It takes the select attribute, which uses XPath to point to a node-set and it outputs the contents of the xsl:for-each one time for each node in the set.

Code Sample:

FlowControl/Demos/BeatlesForEach.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
	<html>
		<head>
			<title>Beatles</title>
		</head>
		<body>
			<table border="1">
			<xsl:for-each select="beatles/beatle">
				<tr>
				<td><a href="{@link}"><xsl:value-of select="name/lastname"/></a></td>
				<td><a href="{@link}"><xsl:value-of select="name/firstname"/></a></td>
				</tr>
			</xsl:for-each>
			</table>
		</body>
	</html>
</xsl:template>

</xsl:stylesheet>

In the code above, you will also notice that the Beatles' first and last names are made into links. The value of the href attribute of the <a> tag is {@link}. This is the equivalent of <xsl:value-of select="@link"/>. However, it would not be well-formed XML to place the <xsl:value-of /> tag (or any tag, for that matter) inside the angle brackets of another open tag, so the curly-bracket syntax is used instead. The output in a browser would look like this.Output in Browser

Looping with xsl:for-each

Duration: 10 to 15 minutes.

In this exercise, you will use xsl:for-each to transform FlowControl/Exercises/BusinessLetter.xml to produce the following output.

Code Sample:

FlowControl/Exercises/ForEachOutput.xml
<?xml version="1.0" encoding="UTF-8"?>
<Names>
	<Name>
		<Title>Mr.</Title>
		<FName>Joshua</FName>
		<LName>Lockwood</LName>
	</Name>
	<Name>
		<Title></Title>
		<FName>Bill </FName>
		<LName>Smith</LName>
	</Name>
	<Name>
		<Title></Title>
		<FName>Bill </FName>
		<LName>Smith</LName>
	</Name>
</Names>
  1. Open FlowControl/Exercises/ForEach.xsl for editing.
  2. Beneath the comment, add an xsl:for-each tag that will output a Name element that contains three child elements: Title, FName, and LName.
  3. The values of the child elements should be retrieved from the source document (FlowControl/Exercises/BusinessLetter.xml).
  4. To test your solution, transform FlowControl/Exercises/BusinessLetter.xml against FlowControl/Exercises/ForEach.xsl.

Solution:

FlowControl/Solutions/ForEach.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<Names>
		<xsl:for-each select="//Name">
				<Name>
					<Title><xsl:value-of select="@Title"/></Title>
					<FName><xsl:value-of select="FirstName"/></FName>
					<LName><xsl:value-of select="LastName"/></LName>
				</Name>
		</xsl:for-each>
		</Names>
	</xsl:template>
</xsl:stylesheet>

Sorting with XSLT

The tag for sorting in XSLT is <xsl:sort>, which takes the select attribute with a value of an XPath to identify the node to sort by. The xsl:sort element can also take the order attribute, which has two possible values: ascending (the default) and descending.

Code Sample:

FlowControl/Demos/BeatlesSort.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
	<html>
		<head>
			<title>Beatles</title>
		</head>
		<body>
			<table border="1">
			<xsl:for-each select="beatles/beatle">
				<xsl:sort select="name/lastname" order="descending"/>
				<tr>
				<td><a href="{@link}"><xsl:value-of select="name/lastname"/></a></td>
				<td><a href="{@link}"><xsl:value-of select="name/firstname"/></a></td>
				</tr>
			</xsl:for-each>
			</table>
		</body>
	</html>
</xsl:template>

</xsl:stylesheet>

Notice that the xsl:sort is nested within an xsl:for-each element. This is very common as xsl:sort elements need to be nested within repeating structures.

When FlowControl/Demos/BeatlesSort.xml is transformed against FlowControl/Demos/BeatlesSort.xsl, the output is the same as it would be when transformed against FlowControl/Demos/BeatlesForEach.xsl , except the results are sorted by last name in descending order.Results Sorted in Descending Order

Looping and Sorting

Duration: 5 to 10 minutes.

In this exercise, you will modify the XSLT you created in the last exercise so that the results are sorted by FirstName.

  1. Open FlowControl/Exercises/ForEachSort.xsl for editing.
  2. Modify the code so that the results will be sorted by FirstName.
  3. To test your solution, transform FlowControl/Exercises/BusinessLetterSort.xml against FlowControl/Exercises/ForEachSort.xsl.

Solution:

FlowControl/Solutions/ForEachSort.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<Names>
		<xsl:for-each select="//Name">
			<xsl:sort select="FirstName"/>
				<Name>
					<Title><xsl:value-of select="@Title"/></Title>
					<FName><xsl:value-of select="FirstName"/></FName>
					<LName><xsl:value-of select="LastName"/></LName>
				</Name>
		</xsl:for-each>
		</Names>
	</xsl:template>
</xsl:stylesheet>

Conditions with XSLT

xsl:if

The <xsl:if> tag is used to create a simple if condition. Its test attribute holds the condition, which is written in the form of an XPath, often using the XPath operators in the "XPath Operators" table.

Code Sample:

FlowControl/Demos/BeatlesIf.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
	<html>
		<head>
			<title>Beatles</title>
		</head>
		<body>
			<table border="1">
			<tr>
				<th>First Name</th>
				<th>Last Name</th>
			</tr>
			<xsl:for-each select="beatles/beatle">
				<xsl:sort select="name/lastname" />
				<xsl:if test="not(@real='no')">
				<tr>
				<td><xsl:value-of select="name/firstname"/></td>
				<td><xsl:value-of select="name/lastname"/></td>
				</tr>
				</xsl:if>
			</xsl:for-each>
			</table>
		</body>
	</html>
</xsl:template>

</xsl:stylesheet>

The xsl:for-each loop causes XSLT to look at each beatle element. The nested xsl:if element uses XPath to test to see if the value of real attribute of the beatle element is no. It then negates the result with the not() function. This way beatle elements that have no real attribute and beatle elements that do have a real attribute with the value of ANYTHING BUT no will both be included in the result set.

When FlowControl/Demos/BeatlesIf.xml is transformed against FlowControl/Demos/BeatlesIf.xsl, the output looks like this in a browser.Output in Browser

Note that there are no <xsl:else> or <xsl:elseif> elements.

xsl:choose

For multi-level conditions, <xsl:choose> is used. It takes as children one or more <xsl:when> tags with an optional <xsl:otherwise> tag at the end.

Code Sample:

FlowControl/Demos/BeatlesChoose.xsl
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
	<html>
		<head>
			<title>Beatles</title>
		</head>
		<body>
			<table border="1">
			<tr>
				<th>First Name</th>
				<th>Last Name</th>
			</tr>
			<xsl:for-each select="beatles/beatle">
				<xsl:sort select="name/lastname" />
				<xsl:choose>
				<xsl:when test="not(@real='no')">
				<tr bgcolor="red">
				<td><xsl:value-of select="name/firstname"/></td>
				<td><xsl:value-of select="name/lastname"/></td>
				</tr>
				</xsl:when>
				<xsl:otherwise>
				<tr bgcolor="orange">
				<td><s><xsl:value-of select="name/firstname"/></s></td>
				<td><s><xsl:value-of select="name/lastname"/></s></td>
				</tr>
				</xsl:otherwise>
				</xsl:choose>
			</xsl:for-each>
			</table>
		</body>
	</html>
</xsl:template>

</xsl:stylesheet>

This code ouputs a table row with a red background for every real Beatle and a table row with an orange background for every fake Beatle. The fake Beatles are also crossed out with the <s> tag.

When FlowControl/Demos/BeatlesChoose.xml is transformed against FlowControl/Demos/BeatlesChoose.xsl, the output looks like this in a browser.Output in Browser

Conditionals

Duration: 20 to 30 minutes.

In this exercise, you will practice using xsl:if and xsl:choose.

  1. Open FlowControl/Exercises/Conditions.xsl.
  2. Modify the first xsl:for-each element, so the Title element only shows up on the output if the Name element in the source has a child Title attribute.
  3. Modify the second xsl:for-each element, so that
    • All elements containing the text "Webucator" output a Match element with a Text attribute with the value of "W". The Match element should contain the name and text of the matched element. Use the String Functions table as a reference.
    • All elements containing the text "Lockwood & Lockwood", output a Match element with a Text attribute with the value of "L&L". Again, the Match element should contain the name and text of the matched element.
    • If neither string is matched, output a NoMatch element containing the name and text of the element with no match.
  4. To test your solution, transform FlowControl/Exercises/Conditions.xml against FlowControl/Exercises/Conditions.xsl. The intended output is shown below.

Code Sample:

FlowControl/Exercises/ConditionsOutput.xml
<?xml version="1.0" encoding="UTF-8"?>
<Names>
	<Name>
		<Title>Mr.</Title>
		<FName>Joshua</FName>
		<LName>Lockwood</LName>
	</Name>
	<Name>
		<FName>Bill </FName>
		<LName>Smith</LName>
	</Name>
	<Name>
		<FName>Bill </FName>
		<LName>Smith</LName>
	</Name>
</Names>
<Matches>
	<NoMatch>SendDate: November 29, 2005</NoMatch>
	<NoMatch>FirstName: Joshua</NoMatch>
	<NoMatch>LastName: Lockwood</NoMatch>
	<Match Text="L&amp;L">Company: Lockwood &amp; Lockwood</Match>
	<NoMatch>Street: 291 Broadway Ave.</NoMatch>
	<NoMatch>City: New York</NoMatch>
	<NoMatch>State: NY</NoMatch>
	<NoMatch>Zip: 10007</NoMatch>
	<NoMatch>Country: United States</NoMatch>
	<NoMatch>Heading: Along with this letter, I have enclosed the following 
		items:</NoMatch>
	<Match Text="W">ListItem: two original, execution copies of the Webucator 
		Master Services Agreement</Match>
	<Match Text="W">ListItem: two original, execution copies of the Webucator 
		Premier Support for Developers Services Description between Lockwood 
		&amp; Lockwood and Webucator, Inc.</Match>
	<NoMatch>Para: Please sign and return all four original, execution copies
		to me at your earliest convenience.  Upon receipt of the executed copies, 
		we will immediately return a fully executed, original copy of both 
		agreements to you.</NoMatch>
	<NoMatch>Para: Please send all four original, execution copies to my 
		attention as follows:
			</NoMatch>
	<NoMatch>FirstName: Bill </NoMatch>
	<NoMatch>LastName: Smith</NoMatch>
	<Match Text="W">Company: Webucator, Inc.</Match>
	<NoMatch>Street: 4933 Jamesville Rd.</NoMatch>
	<NoMatch>City: Jamesville</NoMatch>
	<NoMatch>State: NY</NoMatch>
	<NoMatch>Zip: 13078</NoMatch>
	<NoMatch>Country: USA</NoMatch>
	<NoMatch>Para: If you have any questions, 
		feel free to call me at </NoMatch>
	<NoMatch>Phone: 800-555-1000 x123</NoMatch>
	<NoMatch>Email: bsmith@webucator.com</NoMatch>
	<NoMatch>FirstName: Bill </NoMatch>
	<NoMatch>LastName: Smith</NoMatch>
	<NoMatch>JobTitle: VP, Operations</NoMatch>
</Matches>

Solution:

FlowControl/Solutions/Conditions.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
				xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<Names>
		<xsl:for-each select="//Name">
				<Name>
					<xsl:if test="@Title">
						<Title><xsl:value-of select="@Title"/></Title>
					</xsl:if>
					<FName><xsl:value-of select="FirstName"/></FName>
					<LName><xsl:value-of select="LastName"/></LName>
				</Name>
		</xsl:for-each>
		</Names>
		<Matches>
			<xsl:for-each select="//*[text()]">
				<xsl:choose>
					<xsl:when test="contains(text(),'Webucator')">
						<Match Text="W"><xsl:value-of select="name()"/>: 
						<xsl:value-of select="text()"/></Match>
					</xsl:when>
					<xsl:when test="contains(text(),'Lockwood &amp; Lockwood')">
						<Match Text="L&amp;L"><xsl:value-of select="name()"/>: 
						<xsl:value-of select="text()"/></Match>
					</xsl:when>
					<xsl:otherwise>
						<NoMatch><xsl:value-of select="name()"/>: 
						<xsl:value-of select="text()"/></NoMatch>
					</xsl:otherwise>
				</xsl:choose>
			</xsl:for-each>
		</Matches>
	</xsl:template>
</xsl:stylesheet>