Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ryan Anklam and Jim Walker
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Ryan Anklam ( @bittersweetryan ) Jim Walker

Nylon Technology Presentation: Introduction To XSLT And XmlTransform() In ColdFusion

By on
Tags:

CAUTION: I am very new to XSLT. This is the stuff that I have worked out in my head. Some it might be incomplete. Some of it might be dead wrong.

XSL stands for "Extensible Stylesheet Language". XSL Transformation (XSLT) provides us with a way to convert any XML document into another XML document. Apparently, there are parts of the XSL language that have to do with formatting, but for our purposes, we are going to concentrate on the transformation aspects only.

ColdFusion And XSLT

ColdFusion supports a subset of the XSLT functionality. While many features of the XSLT language are implemented in ColdFusion, there are a number of functions that are not supported. I am not sure exactly if the set of supported features is listed anywhere, but we should be able to do some really complex stuff without the entire feature set at our disposal.

XSLT And XPath

Before you do anything in XSLT (ColdFusion or otherwise), you are going to need to have a firm grasp on XPath. XPath is a way to pull nodes out of an XML document and it is only through XPath that we can find the XML nodes we wish to include in our transformations.

Basic XSLT Elements

There are more XSLT elements than the ones listed below, but with just these few elements, we can perform every high level transformations. NOTE: All XSLT elements (and all XML elements) should be all lower case when part of an XSLT document; I am simply camel casing them here for my own preference.

Transform

This is the root element of the XSLT document. It provides information about the transformation including the namespace of the XSLT nodes.

Template

This defines rules to be applied to nodes within the XML document. You can think of this as being somewhat equivalent to a ColdFusion custom tag.

Apply-Templates

This applies a template to the selected nodes. You can think of this as being somewhat equivalent to a ColdFusion CFModule tag that executes a custom tag (see Template above).

Value-Of

This outputs the selected value of the selected or current node. You can think of this as being somewhat equivalent to a ColdFusion CFOutput tag.

For-Each

This loops over the nodes in the selected node set. You can think of this as being somewhat equivalent to a ColdFusion CFLoop tag.

If

This tests to see if a condition is true. There is no ELSE element. If you need to check multiple conditions, see Choose (below). You can think of this as being somewhat equivalent to a ColdFusion CFIF tag.

Choose / When / Otherwise

These tags work in conjunction to test for multiple conditions. These take the place of an IF / ELSE statement and you can think of these as being somewhat equivalent to the ColdFusion CFSwitch / CFCase / and CFDefaultCase tags respectively.

Text

This tag allows you output text literals. You can think of this as being somewhat equivalent to an XHTML PRE tag.

Element / Attribute

These tags allow you to build dynamic nodes in the resulting XML document.

Transforming XML In ColdFusion Using XSLT

ColdFusion provides the XmlTransform() function as a way to apply XSL Transformations to an XML document. While the XmlTransform() function takes three parameters, we will only cover the first two for this introduction:

XML: The first argument must be either a ColdFusion XML document object or properly formatted XML string.

XSLT: The second argument must be one of the following:

  • An XSLT string.
  • A ColdFusion XML document object containing XSLT elements.
  • An absolute or relative path to an XSLT file (document containing and XSLT string).
  • A URL (including protocol ex. http) for an XSLT file.

ColdFusion will take the two arguments and return the transformed XML document (in string format).

For the examples in this introduction, we are going to be working off of this ColdFusion XML document object:

<!--- Define ColdFusion XML document object. --->
<cfxml variable="xmlFoot">
	<foot>
		<toes>
			<toe isbigtoe="true">
				<name>Christina</name>
			</toe>
			<toe>
				<name>Julia</name>
			</toe>
			<toe>
				<name>Maria</name>
			</toe>
			<toe>
				<name>Kim</name>
			</toe>
			<toe iscute="true">
				<name>Kit</name>
			</toe>
		</toes>
	</foot>
</cfxml>

Here, we have nested elements, element nodes, attribute nodes, and text nodes; this should give us plenty to test with. For the examples, we are not going to show the Transformation taking place or, in most cases, the Transform element, but it would be something like this:

<!--- Define ColdFusion XML document object (XSLT). --->
<cfxml variable="xmlTransform">
	<xsl:transform
		version="1.0"
		xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

		<!--- Match root element of XML document. --->
		<xsl:template match="/">

			<!--- ... more code here ... --->

		</xsl:template>

	</xsl:transform>
</cfxml>


<!--- Tranform xmlFoot using xmlTransform XSLT. --->
<cfoutput>
	#HtmlEditFormat(
		XmlTransform(
			xmlFoot,
			xmlTransform
			)
		)#
</cfoutput>

Notes On "Select" And "Match"

Many of the XSLT elements use either a Select or Match attribute. These take XPath paths that return a set of nodes. What is done with those returned nodes depends on the element in question.

Transform

Each XSLT document must contain one and only one Transform element (can also be swapped with a Stylesheet element) and it must be the root element.

<xsl:transform
	version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

	<!--- Rest of XSLT elements go in here. --->

</xsl:transform>

Here we are defining the root node and the namespace of all the XSLT elements as being "xsl". This is simply "boiler plate" and needs to be done for every XSLT document (it doesn't need to be exactly like this, but this is a good place to start).

Template

In order for the transformation to result in any useful output, at least one template must be applied to the incoming XML document. The most basic attribute (that we will be looking at) of the Template element is the Match attribute. The Match attribute tells the transformation engine which nodes to which the template should be applied. The Transformation engine will iterate over the XML document in a depth-first approach and apply templates when possible. If the Transformation hits a text node and no template has been applied, it will simply echo the text node value in the resulting document.

Here, we can use the XPath "/" to match the root element of the XML document:

<!--- Match root element. --->
<xsl:template match="/">
	Here
</xsl:template>

This will be executed only once as there is only one root element (outputting the text "Here" once). Note that none of the text node values are echoed because the Transformation engine must apply the template before it gets to the text nodes.

If we define a template that matches more than one node, it will be executed for every matching node in the selected node set. Here, we can use the match "name" to match "name" element nodes anywhere in the XML document:

<!--- Match name elements. --->
<xsl:template match="name">
	HERE
</xsl:template>

This will be executed once for each of the "name" element nodes and will result in "Here" being output 5 times.

The values of the Match attribute can be complex, but do not support all XPath values. here are some additional Match attribute values that are supported:

<!--- Wild cards. --->
<xsl:template match="*">

<!--- Descendents. --->
<xsl:template match="toes/toe">

<!--- Any parent. --->
<xsl:template match="//toe">

<!--- Text nodes. --->
<xsl:template match="text()">

<!--- Predeciate matching. --->
<xsl:template match="//toe[ 2 ]">

Apply-Templates

Using the Template element, we can get ColdFusion to apply implicit transformations based on patterns (match attribute). However, most of the time we are going to want to explicitly execute templates from within other templates. Apply-Templates allows us to do just that.

The Apply-Templates element uses the Select attribute to define which templates to execute (based on which nodes are returned in the Select attribute XPath value). If you exclude the Select element, the Transformation engine will attempt to execute a template for each DIRECT descendent of the context node (the one matched by the currently executing template).

In the following example, we are going to implicitly execute the "toes" template. Then, from within that template, we are going to explicitly execute the "toe" template for each of the toe child nodes:

<!--- Match toes element. --->
<xsl:template match="//toes">

	OPEN-TOES

	<!--- Apply template to all the toe nodes. --->
	<xsl:apply-templates select="toe" />

	CLOSE-TOES

</xsl:template>

<!--- Match toe element. --->
<xsl:template match="toe">
	TOE
</xsl:template>

This results in the output: OPEN-TOES TOE TOE TOE TOE TOE CLOSE-TOES. Notice that OPEN-TOES appears once, then the Apply-Templates element selects all 5 toe nodes, executing the "toe" template for each node (based on the template Match attribute), and then outputs the CLOSE-TOES text.

The Select attribute of the Apply-Templates node doesn't have to select a direct descendent of the context node. It doesn't even have to select any descendent of the context node. All it has to have is a valid XPath value. In the following example, we will select all name nodes of the XML document:

<!--- Match toes element. --->
<xsl:template match="//toes">

	OPEN-TOES

	<!---
		Apply template to all the name nodes that
		are anywhere in the XML document (having nothing
		to do with the "toes" context node).
	--->
	<xsl:apply-templates select="//name" />

	CLOSE-TOES

</xsl:template>

<!--- Match name element. --->
<xsl:template match="name">
	NAME
</xsl:template>

Using this transformation, we get the following output: OPEN-TOES NAME NAME NAME NAME NAME CLOSE-TOES.

The Select attribute doesn't just have to match Element nodes; it can also match attribute nodes. Here, we are going to match all IsCute attributes of the descendent toe nodes:

<!--- Match toes element. --->
<xsl:template match="//toes">

	<!--- Get all cute toe attributes. --->
	<xsl:apply-templates select="toe/@iscute" />

</xsl:template>

<!--- Match iscute Attribute node. --->
<xsl:template match="@iscute">
	A CUTIE!
</xsl:template>

Notice that our Apply-Templates' Select attribute is using an XPath path that selected an attribute node. Also, notice that our Template element matches on the attribute node, @iscute. Running the above transformation, we get the following output: A CUTIE!

Value-Of

Using the Template and Apply-Templates elements, we can apply templates to nodes within the given ColdFusion XML document during the XSL transformation, but it is through the use of the Value-Of element that we can actually reference values within the XML data. Using Value-Of, we can output the value of an XML node, wether it is a text node or attribute node (and I am sure other node types work as well).

It has two attributes: Select and Disable-Output-Escaping. The Select attribute uses XPath to determine which node value to get. The Disable-Output-Escaping attribute turns off the escaping of special characters. This is especially useful when we are outputting CDATA values. By default, the Transformation engine will escape special values like "<" and ">"; when outputting CDATA (or node data in general), we want those value to come through as-is so that XHTML can be stored in an XML document and therefore can set the Disable-Output-Escaping value to "yes".

In the following example, we are going to implicitly match all the toe nodes. Once inside the toe node, we are going to output the name of the toe: the text node within the nested name element node.

<!--- Match toe element. --->
<xsl:template match="toe">

	<!--- Output name of toe. --->
	<xsl:value-of select="name" />

</xsl:template>

This gives us the output: Christina Julia Maria Kim Kit.

Just like the Apply-Templates element, the Value-Of Select attribute does not have to be relevant to the context node. By default, it is relevant to the context node, but it could certainly use an XPath path that selects from anywhere in the current XML document.

If you want to select an attribute value, all you would need to do is use an XPath path to select the attribute node. The following code will output the @IsBigToe attribute value.

<!--- Match toe element. --->
<xsl:template match="toe">

	<!--- Output big toe flag. --->
	[<xsl:value-of select="@isbigtoe" />]

</xsl:template>

Performing this transformation, we get the following output: [true] [] [] [] []. I have included the brackets to demonstrate that while the toe template still matches all 5 toes, the Value-Of only gets one value. Notice that is does NOT throw an error if the context node in question does NOT have the desired attribute. Value-Of always returns a string; this string might be empty a lot of the time.

For-Each

The For-Each element allows us to loop over the selected nodes. This is like applying a template to a set of nodes without having to leave the currently executing template. The For-Each element has only one attribute - Select - which defines the node set over which we will iterate.

In this example, we are going to apply a toes template. Then, from within that template, we are going to loop over each toe node and output the name of the toe.

<!--- Match toes element. --->
<xsl:template match="toes">

	<!--- Loop over each toe. --->
	<xsl:for-each select="toe">

		<!--- Output name of the context toe. --->
		[<xsl:value-of select="name/text()" />]

	</xsl:for-each>

</xsl:template>

Running this, we get the following output: [Christina] [Julia] [Maria] [Kim] [Kit]. For each iteration of the For-Each loop, the Transformation engine takes the current "toe" node and makes it the context node.

If

The If element allows us to run code based on conditions. The If element has only one attribute, test, which must evaluate to true or false. What is considered True can be a bit confusing. It can be any valid XPath or expression such a function return or even the existence of a nested element. For example, in the following code, we are only going to show the name of toes that actually have a nested name element.

<!--- Match toes element. --->
<xsl:template match="toes">

	<!--- Loop over each toe. --->
	<xsl:for-each select="toe">

		<!---
			Only output names of toe elements that
			have a nested name element.
		--->
		<xsl:if test="name">

			<!--- Output name of the context toe. --->
			[<xsl:value-of select="name/text()" />]

		</xsl:if>

	</xsl:for-each>

</xsl:template>

Here, as we are looping over the toes, we use the test condition "name" to make sure the context toe has a nested name element. If it does, we then execute that transformation code within the If element.

Choose / When / Otherwise

XSLT does not allow the ELSE part of a traditional IF-ELSE condition. Furthermore, it has no ElseIF statement. To perform this kind of conditional testing, we have to use the XSLT equivalent of a Switch statement which uses the Choose, When, and Otherwise elements.

Here, we are going to apply a template for each toe node and describe the toe. The description of the toe will be based on the existence of certain attributes:

<!--- Match toe element. --->
<xsl:template match="toe">

	<!--- Describe toe. --->
	<xsl:choose>
		<xsl:when test="@isbigtoe">
			Big!
		</xsl:when>
		<xsl:when test="@iscute">
			Cute!
		</xsl:when>
		<xsl:otherwise>
			N/A
		</xsl:otherwise>
	</xsl:choose>

</xsl:template>

Running the above transformation, we get the following output: Big! N/A N/A N/A Cute!

Text

White space output that is generated during an XSL transformation can be a little confusing. I don't fully understand the rules. Sometimes it outputs white space, sometimes it does not. I think it has to do with a combination of which XSLT elements you are using and what text literals you are using. You can use the Text element to make sure all characters get output as-is in the resultant transformation.

The Text element has one attribute: Disable-Output-Escaping. Like the Value-Of element, this attribute flags whether or not to escape special output characters (defaults to "no"). Other than that, it simply outputs the text contained within its open and close tags.

If we run this transformation:

<!--- Match toes element. --->
<xsl:template match="toes">

	<!--- Loop over toes. --->
	<xsl:for-each select="toe">
		<xsl:value-of select="name" />
	</xsl:for-each>

</xsl:template>

We get the following output: ChristinaJuliaMariaKimKit. Notice that none of the white space between the XSLT elements was carried through. Now, we can modify that to add explicit white space:

<!--- Match toes element. --->
<xsl:template match="toes">

	<!--- Loop over toes. --->
	<xsl:for-each select="toe">
		<xsl:value-of select="name" />
		<xsl:text> - </xsl:text>
	</xsl:for-each>

</xsl:template>

Here, we are adding a dash between elements and we get the following output: Christina - Julia - Maria - Kim - Kit -.

Element / Attribute

In ColdFusion, it is easy to build dynamic XHTML elements; all you have to do is put CFIF tags within the XHTML tag markup. This kind of thing cannot be done quite as easily in XSLT. Instead of putting conditional statements inside the markup of a tag, you can use the Element and Attribute elements to build nodes one an attribute at a time.

For example, if we wanted to convert the toes XML node to an XHTML unordered list (OL), including the attributes, we would have to do something like this:

<!--- Match toes element. --->
<xsl:template match="toes">

	<ul>
		<!--- Loop over toes. --->
		<xsl:for-each select="toe">

			<!--- Build an LI element. --->
			<xsl:element name="li">

				<!--- Check for class. --->
				<xsl:if test="@isbigtoe = 'true'">
					<xsl:attribute name="class">
						<xsl:text>bigtoe</xsl:text>
					</xsl:attribute>
				</xsl:if>

				<!--- Output this value for the LI text. --->
				<xsl:value-of select="name/text()" />

			</xsl:element>

		</xsl:for-each>
	</ul>

</xsl:template>

As you can see, building a dynamic XHTML element is no easy task in XSL transformations.

So there you go. I am not sure how I feel about XSL Transformation just yet. There is certainly some cool stuff here, but I am not sure if the overhead of its complexity makes it worth while; I feel like many of the things it does can be more easily done in ColdFusion. I think the real power here lies in the Template matching - it has some very nice recursive possibilities. I think now that I have a better understanding of XSLT, it will start to look like a solution in more places.

Want to use code from this post? Check out the license.

Reader Comments

13 Comments

Good article with all the transformation related tags,attributes explained. Very helpful.

Thanks,
Raghuram Reddy Gottimukkula
Adobe Certified Professional in Advanced ColdFusion
Hyderabad India

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel