facebook google plus twitter
Webucator's Free ColdFusion MX Tutorial

Lesson: ColdFusion and XML

Welcome to our free ColdFusion MX tutorial. This tutorial is based on Webucator's Comprehensive ColdFusion Training course.

Lesson Goals

  • To work with ColdFusion's XML functions
  • To process RSS feeds with ColdFusion
  • To use WDDX to transfer data across the web

XML Functions

ColdFusion provides many XML functions for dealing with XML documents. This section covers those functions and their functionality.

Creating New XML Documents

There are several ways to create new XML documents, which ColdFusion treats as structures. The root element of an XML document can be accessed as follows: xmlDoc.xmlRoot; or by the name of the root element: xmlDoc.rootName, where the root element is <rootName/>.

XmlParse()

The XmlParse() function is used to convert an XML string into an XML document object, which makes the XML searchable through other ColdFusion XML functions. The function takes the following parameters:

XmlParse() Parameters
Parameter Description
xmlText Required. Can be any of the following:
  • An XML string
  • A absolute path or URL to an XML file
caseSensitive Optional boolean. If true, the object's properties (elements and attributes) will be case sensitive. The default is false. When an object is case sensitive, you must use bracket notation to reference elements and attributes.
validator Optional. Can be either of the following:
  • A string containing a DTD or XML schema
  • An absolute path or URL to a DTD or XML schema

The following sample shows how it is used.

Code Sample:

XML/Demos/XmlParse.cfm
<html>
<head>
<title>XmlParse()</title>
</head>
<body>
<h1>XmlParse()</h1>
<cfset xmlGeorge=XMLParse(ExpandPath("George.xml"))>
<cfoutput>
	<h2>#xmlGeorge.Name.XmlAttributes.Title# #xmlGeorge.Name.FirstName# #xmlGeorge.Name.LastName#</h2>
</cfoutput>
<cfdump var="#xmlGeorge#">
</body>
</html>

In a browser, the page looks like this:

As you can see, the XML document's properties can be accessed like a ColdFusion structure. Elements can be accessed as direct properties of their parent element. Attributes must be accessed through the XmlAttributes structure.

XmlNew(), XmlElemNew(), xmlRoot, XmlChildren, XmlAttributes

New XML documents can also be built from scratch using the XmlNew() function. Elements are added with the XmlElemNew() function, which requires the XML document object of which the element will be a part and the name of the new element. The root element should be assigned to the xmlRoot property of the XML document. Elements and attributes are positioned in the document with XmlChildren and XmlAttributes. The following document shows how to build an XML document from scratch.

Code Sample:

XML/Demos/XmlNew.cfm
<!---Create the XML document--->
<cfset docGeorge=XmlNew()>

<!---Set the root element--->
<cfset docGeorge.xmlRoot = XmlElemNew(docGeorge,"Name")>

<!---Set an attribute of the root element--->
<cfset docGeorge.xmlRoot.XmlAttributes.Title = "President">

<!---Create the FirstName and LastName elements--->
<cfset elemFirstName = XmlElemNew(docGeorge,"FirstName")>
<cfset elemLastName = XmlElemNew(docGeorge,"LastName")>

<!---Set the text values of the FirstName and LastName elements--->
<cfset elemFirstName.XmlText = "George">
<cfset elemLastName.XmlText = "Washington">

<!---
	Append the FirstName and LastName elements to the root element
	Notice this can be done using xmlRoot or the name of the root element
--->
<cfset docGeorge.xmlRoot.XmlChildren[1] = elemFirstName>
<cfset docGeorge.Name.XmlChildren[2] = elemLastName>

<!---Convert the XML document to a string--->
<cfset xmlString = ToString(docGeorge)>

<!---Set the content to text/xml, reset the buffer, and output the XML string--->
<cfcontent type="text/xml" reset="yes"><cfoutput>#xmlString#</cfoutput>

In a browser, the page looks like this:

The <cfxml> Tag

The <cfxml> tag can be used to create an XML document object directly in XML. It tends to be easier than using XmlNew() if the XML structure is not determined programatically. The following example shows how to use <cfxml>.

Code Sample:

XML/Demos/cfxml.cfm
               <cfxml variable="docGeorge">
<?xml version="1.0" encoding="iso-8859-1"?>
<Name xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Name.xsd" Title="President">
	<FirstName>George</FirstName>
	<LastName>Washington</LastName>
</Name>
</cfxml>

<!---Convert the XML document to a string--->
<cfset xmlString = ToString(docGeorge)>

<!---Set the content to text/xml, reset the buffer, and output the XML string--->
<cfcontent type="text/xml" reset="yes"><cfoutput>#xmlString#</cfoutput>

XmlTransform()

The XmlTransform() function is used to transform XML documents against XSLT documents. It returns a string containing the result of the transformation. The function takes the following parameters:

XmlTransform() Parameters
Parameter Description
xml Required. Can be either of the following:
  • An XML string
  • An XML document object
xsl Required. Can be either of the following:
  • An XSLT string
  • An absolute path or URL to the XSLT
parameters Optional. A structure containing name-value pairs that map to the parameters defined in the XSLT. The properties of the structure must be added via the StructInsert() function.

To illustrate, we will use the following XML and XSLT documents:

Code Sample:

XML/Demos/George.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<Name xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Name.xsd" Title="President">
	<FirstName>George</FirstName>
	<LastName>Washington</LastName>
</Name>

Code Sample:

XML/Demos/Name.xsl
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:param name="bgcolor" select="'white'"/>
	<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
	<xsl:template match="/">
			<html>
				<head>
					<title>
						<xsl:value-of select="Name/@Title"/>
						<xsl:text> </xsl:text>
						<xsl:value-of select="Name/FirstName"/>
						<xsl:text> </xsl:text>
						<xsl:value-of select="Name/LastName"/>
					</title>
				</head>
				<body bgcolor="{$bgcolor}">
					<h1>
						<xsl:value-of select="Name/@Title"/>
						<xsl:text> </xsl:text>
						<xsl:value-of select="Name/FirstName"/>
						<xsl:text> </xsl:text>
						<xsl:value-of select="Name/LastName"/></h1>
				</body>
			</html>
	</xsl:template>
</xsl:stylesheet>

Notice that Name.xsl can accept a bgcolor parameter, which it uses to set the background color of the page. The following ColdFusion file will transform George.xml against Name.xsl.

Code Sample:

XML/Demos/XmlTransform.cfm
<cffile action="read" variable="strXML" file="#ExpandPath('George.xml')#">
<cfset PathToXslt = ExpandPath("Name.xsl")>
<cfset params = StructNew()>
<cfset StructInsert(params,"bgcolor","silver")>
<cfoutput>#XmlTransform(strXML,PathToXslt,params)#</cfoutput>

Notice that we have to read the XML file into a string with <cffile> before passing it to XmlTransform(), which cannot accept a path to the XML file as the first argument. The resulting HTML code will look like this:

The page will display as follows:

XmlFormat()

The XmlFormat() function is used to escape special XML characters so that they can be displayed in a browser. For example, the < character becomes &lt;. The following example shows how we could display the code resulting from the transformation we just did in the browser window.

Code Sample:

XML/Demos/XmlFormat.cfm
<cffile action="read" variable="strXML" file="#ExpandPath('George.xml')#">
<cfset PathToXslt = ExpandPath("Name.xsl")>
<cfset params = StructNew()>
<cfset StructInsert(params,"bgcolor","silver")>
<cfset result = XmlTransform(strXML,PathToXslt,params)>

<cfset formattedXml = XmlFormat(result)>

<html>
<head>
<title>XmlFormat()</title>
</head>
<body>
<h1>XmlFormat()</h1>
<cfoutput>
<pre>#formattedXml#</pre>
</cfoutput>
</body>
</html>

The resulting page is shown below:

Searching XML Documents

XML documents can be searched using the XmlSearch() function. Specific elements can be located using the XmlChildPos() function.

XmlSearch()

The XmlSearch() function uses an XPath expression to search an XML document object. The function takes two arguments: the document object to search and the XPath expression. It returns an array of XML nodes that match the expression. The code samples below show how the function is used. The first sample is the XML document being searched. The second sample is the ColdFusion file that uses the XmlSearch() function.

Code Sample:

XML/Demos/Beatles.xml
<?xml version="1.0"?>
<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>
</beatles>

Code Sample:

XML/Demos/XmlSearch.cfm
<html>
<head>
<title>XmlSearch()</title>
</head>
<body>
<h1>XmlSearch()</h1>
<cfset xmlBeatles=XMLParse(ExpandPath("Beatles.xml"))>
<cfoutput>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
	<th>XPath</th>
	<th>Result</th>
</tr>
<cfset arrXPs = ArrayNew(1)>
<cfset ArrayAppend(arrXPs,"/beatles")>
<cfset ArrayAppend(arrXPs,"/beatles/beatle/@link")>
<cfset ArrayAppend(arrXPs,"/beatles//firstname")>
<cfset ArrayAppend(arrXPs,"/beatles//firstname/text()")>

<cfloop index="i" from="1" to="#ArrayLen(arrXPs)#">
	<cfset xp = arrXPs[i]>
	<cfset arrResult = XmlSearch(xmlBeatles,xp)>
	<tr valign="top">
		<td>#xp#</td>
		<td>
			<ol>
			<cfloop index="i" from="1" to="#ArrayLen(arrResult)#">
				<li>
					#XmlGetNodeType(arrResult[i])#: 
					<cftry>
						#arrResult[i].XmlText#
						<cfcatch type="any">#arrResult[i]#</cfcatch>
					</cftry>
				</li>
			</cfloop>
			</ol>
		</td>
	</tr>
</cfloop>
</table>
</cfoutput>
</body>
</html>

The code loops through a set of XPaths, uses each one to search the XML document and displays the results as a list. If the resulting node has an XmlText property (e.g, an element), that property is displayed. If it does not (e.g, an attribute), the node itself is displayed. The output is shown below:

XmlChildPos()

The XmlChildPos() function finds the position of a node within its parent element. This can be used for adding or removing elements to the XML DOM. The code samples below show how the function is used. The first sample is the XML document being searched. The second sample is the ColdFusion file that uses the XmlChildPos() function.

Code Sample:

XML/Demos/RockStars.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<rockstars>
	<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>
	<stone link="http://www.mickjagger.com" >
		<name>
			<firstname>Mick</firstname>
			<lastname>Jagger</lastname>
		</name>
	</stone>
	<stone link="http://www.keithrichards.com" >
		<name>
			<firstname>Keith</firstname>
			<lastname>Richards</lastname>
		</name>
	</stone>
	<beatle link="http://www.webucator.com">
		<name>
			<firstname>Nat</firstname>
			<lastname>Dunn</lastname>
		</name>
	</beatle>
</rockstars>

Code Sample:

XML/Demos/XmlChildPos.cfm
<cfset xmlRockStars=XMLParse(ExpandPath("RockStars.xml"))>
<cfset arrRockStars=xmlRockStars.rockstars.XmlChildren>
<html>
<head>
<title>XmlChildPos()</title>
</head>
<body>
<h1>XmlChildPos()</h1>
<cfoutput>
	<ol>
	<cfloop index="i" from="1" to="#ArrayLen(arrRockStars)#">
		<li>#arrRockStars[i]#</li>
	</cfloop>
	</ol>
	
	<cfset notARockStarPos = XmlChildPos(xmlRockStars.rockstars,"beatle",3)>
	
	<div style="color:red; font-size:larger;">
		The person at position #notARockStarPos# is not a rock star and will be deleted.
	</div>
	
	<cfset ArrayDeleteAt(arrRockStars,notARockStarPos)>
	
	<ol>
	<cfloop index="i" from="1" to="#ArrayLen(arrRockStars)#">
		<li>#arrRockStars[i]#</li>
	</cfloop>
	</ol>
</cfoutput>
</body>
</html>

The document displays all the XML children of the rockstars element. The XmlChildPos() function finds the third beatle element and identifies it as the fifth element within rockstars and uses that information to delete the element from the rockstars.XmlChildren array. It then redisplays the XML children of the rockstars element. Notice that the non-rockstar is missing:

Validating XML Documents

ColdFusion provides functions for checking if an XML document is well formed or valid against an XML schema or DTD. There are additional functions for checking different parts of an XML document. These functions are explained below.

IsXML()

The IsXML() function determines whether the passed-in string is well-formed XML. It returns true if it is and false if it is not. The following sample shows how it is used.

Code Sample:

XML/Demos/IsXML.cfm
<cfparam name="FORM.XmlText" default="<Name>
	<FirstName>George</FirstName>
	<LastName>Washington</LastName>
</Name>">
<html>
<head>
<title>IsXML()</title>
</head>
<body>
<h1>XML Test</h1>
<cfoutput>
<form method="post" action="#CGI.SCRIPT_NAME#">
<textarea name="XmlText" cols="60" rows="10">#FORM.XmlText#</textarea>
<br><input name="SubmitButton" type="submit" value="Check XML">
<cfif isDefined("FORM.SubmitButton")>
	<cfif IsXML(FORM.XmlText)>
		<span style="margin-left:200px;color:green">XML is well formed.</span>
	<cfelse>
		<span style="margin-left:200px;color:red">XML is not well formed.</span>
	</cfif>
</cfif>
</form>
</cfoutput>
</body>
</html>

The page simply displays a form with a textarea for holding XML. When the form is submitted, the page uses the IsXML() function to check if the text is well-formed XML and displays a message with the results.

When the page is first opened, it looks like this:

When the form is submitted with well-formed XML, the page displays as follows (notice the little message of success in the bottom right corner):

When the form is submitted with poorly-formed XML, the page displays as follows:

XmlValidate()

The XmlValidate() function is used to validate an XML document against a DTD or XML schema. The function takes the following parameters:

XmlValidate() Parameters
Parameter Description
xmlDoc Required. Can be any of the following:
  • An XML string
  • An absolute path or URL to an XML file
  • An XML document object
validator Optional. Can be either of the following:
  • A string containing a DTD or XML schema
  • An absolute path or URL to a DTD or XML schema

The function returns a structure containing the following fields:

  • Status - A boolean indicating if validation was successful.
  • Errors - An array of errors.
  • FatalErrors - An array of fatal errors, usually indicating that the XML document is not well formed.
  • Warnings - An array of warnings.

To illustrate, we will use the following simple XML schema:

Code Sample:

XML/Demos/Name.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
	<xs:element name="Name">
		<xs:complexType>
			<xs:sequence>
				<xs:element name="FirstName" type="xs:string"/>
				<xs:element name="LastName" type="xs:string"/>
			</xs:sequence>
			<xs:attribute name="Title" type="xs:string" use="optional"/>
		</xs:complexType>
	</xs:element>
</xs:schema>

The schema requires the document element Name to have two children, FirstName and LastName, and an optional attribute, Title.

ColdFusion uses the Xerces parser to validate XML documents. To validate correctly when the validator parameter is being used, the parser requires the xmlns:xsi and xsi:noNamespaceSchemaLocation attributes in the document element of the XML being validated. To illustrate, we will use a demo similar to the IsXML() demo.

Code Sample:

XML/Demos/XmlValidate.cfm
<cfparam name="FORM.XmlText" default="<Name xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation=''>
	<FirstName>George</FirstName>
	<LastName>Washington</LastName>
</Name>">
<html>
<head>
<title>XmlValidate()</title>
</head>
<body>
<h1>XML Validation Test</h1>
<cfoutput>
<form method="post" action="#CGI.SCRIPT_NAME#">
<textarea name="XmlText" cols="60" rows="10">#FORM.XmlText#</textarea>
<br><input name="SubmitButton" type="submit" value="Validate XML">
<cfif isDefined("FORM.SubmitButton")>
	<cfset structResults = XmlValidate(FORM.XmlText,ExpandPath("Name.xsd"))>
	<cfif structResults.status>
		<span style="margin-left:200px;color:green">XML is valid.</span>
	<cfelse>
		<span style="margin-left:200px;color:red">XML is not valid.</span>
		<div style="margin-top:10px">
		<cfif ArrayLen(structResults.Errors)>
			<b>Errors:</b>
			<ol style="margin-top:0px">
				<cfloop index="i" from="1" to="#ArrayLen(structResults.Errors)#">
					<li>#structResults.errors[i]#</li>
				</cfloop>
			</ol>
		</cfif>
		<cfif ArrayLen(structResults.FatalErrors)>
			<b>Fatal Errors:</b>
			<ol style="margin-top:0px">
				<cfloop index="i" from="1" to="#ArrayLen(structResults.FatalErrors)#">
					<li>#structResults.fatalErrors[i]#</li>
				</cfloop>
			</ol>
		</cfif>
		<cfif ArrayLen(structResults.Warnings)>
			<b>Warnings:</b>
			<ol style="margin-top:0px">
				<cfloop index="i" from="1" to="#ArrayLen(structResults.Warnings)#">
					<li>#structResults.warnings[i]#</li>
				</cfloop>
			</ol>
		</cfif>
		</div>
	</cfif>
</cfif>
</form>
</cfoutput>
</body>
</html>

When the page is first opened, it looks like this:

When the form is submitted with valid XML, the page displays as follows (notice the little message of success in the bottom right corner):

When the form is submitted with invalid XML, the page displays as follows:

The XML has several problems:

  • The errors are problems with the validity of the XML document according to the schema. In this case, the title attribute should be spelled with a capital "T" as "Title" and no MiddleName element is allowed.
  • The fatal errors are problems with the well-formedness of the XML document. In this case, the LastName element's close tag should have a capital "N".
  • There are no warnings, which are generally difficult to produce in a validating parser.

IsXmlDoc(), IsXmlRoot(), IsXmlElem(), IsXmlAttribute(), IsXmlNode()

  • The IsXmlDoc() function returns true if the passed-in parameter is an XML document object.
  • The IsXmlRoot() function returns true if the passed-in parameter is the root of an XML document object.
  • The IsXmlElem() function returns true if the passed-in parameter is an XML element.
  • The IsXmlAttribute() function returns true if the passed-in parameter is an XML attribute.
  • The IsXmlNode() function returns true if the passed-in parameter is an XML node.

The following file shows how these functions work.

Code Sample:

XML/Demos/XmlParts.cfm
<cfset xmlGeorge=XMLParse(ExpandPath("George.xml"))>
<html>
<head>
<title>XML Parts</title>
<style type="text/css">
.YES {background-color:#00cc00; font-weight:bold;}
.NO {color:#ff0000;}
</style>
</head>
<body>
<h1>XML Parts</h1>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
	<th>#</th>
	<th>XML Part</th>
	<th>IsXmlDoc()</th>
	<th>IsXmlRoot()</th>
	<th>IsXmlElem()</th>
	<th>IsXmlAttribute()</th>
	<th>IsXmlNode()</th>
</tr>
<cfoutput>
<cfset part = xmlGeorge>
<tr>
	<td>1</td>
	<td>xmlGeorge</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
<cfset part = xmlGeorge.Name>
<tr>
	<td>2</td>
	<td>xmlGeorge.Name</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
<cfset part = xmlGeorge.Name.XmlAttributes.Title>
<tr>
	<td>3</td>
	<td>xmlGeorge.Name.XmlAttributes.Title</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
<cfset part = xmlGeorge.Name.FirstName>
<tr>
	<td>4</td>
	<td>xmlGeorge.Name.FirstName</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
<cfset part = xmlGeorge.Name.XmlChildren[1]>
<tr>
	<td>5</td>
	<td>xmlGeorge.Name.XmlChildren[1]</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
<cfset part = xmlGeorge.Name.FirstName.XmlText>
<tr>
	<td>6</td>
	<td>xmlGeorge.Name.FirstName.XmlText</td>
	<td class="#IsXmlDoc(part)#">#IsXmlDoc(part)#</td>
	<td class="#IsXmlRoot(part)#">#IsXmlRoot(part)#</td>
	<td class="#IsXmlElem(part)#">#IsXmlElem(part)#</td>
	<td class="#IsXmlAttribute(part)#">#IsXmlAttribute(part)#</td>
	<td class="#IsXmlNode(part)#">#IsXmlNode(part)#</td>
</tr>
</table>
</cfoutput>
</body>
</html>

The page will display as follows:

This is pretty straightforward. The only surprises are the way attributes and text nodes are handled. According to row 3, the Title attribute is not an XML attribute, nor is it an XML node. And according to row 6, the text inside of the FirstName element is not an XML node. The Title attribute is, of course, an XML attribute, and all attributes and text in an XML document are XML nodes; however, ColdFusion treats attributes as properties of an element, not as nodes, and it treats text as plain text, rather than as an XML text node.

XmlNodes Array

Every element has an XmlNodes array, which contains the element's element nodes, text nodes, comment nodes, and CDATA section nodes. It does not contain an element's attribute nodes. To access attributes, you must use the XmlAttributes structure.

XmlGetNodeType()

The XmlGetNodeType() function is used to determine the passed-in parameter's node type. The following two examples show how the function works.

Code Sample:

XML/Demos/GetNodeType.cfm
<cfset xmlGeorge=XMLParse(ExpandPath("George.xml"))>
<html>
<head>
<title>XmlGetNodeType()</title>
</head>
<body>
<h1>XmlGetNodeType()</h1>
<cfoutput>
<ol>
	<cfset part = xmlGeorge>
	<li>
		xmlGeorge: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
	
	<cfset part = xmlGeorge.Name>
	<li>
		xmlGeorge.Name: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
	
	<cfset part = xmlGeorge.Name.XmlAttributes.Title>
	<li>
		xmlGeorge.Name.XmlAttributes.Title: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
	
	<cfset part = xmlGeorge.Name.FirstName>
	<li>
		xmlGeorge.Name.FirstName: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
	
	<cfset part = xmlGeorge.Name.XmlChildren[1]>
	<li>
		xmlGeorge.Name.XmlChildren[1]: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
	
	<cfset part = xmlGeorge.Name.FirstName.XmlText>
	<li>
		xmlGeorge.Name.FirstName.XmlText: 
		<cftry>
			#XmlGetNodeType(part)#
			<cfcatch type="any"><span style="color:red">#cfcatch.Message#</span></cfcatch>
		</cftry>
	</li>
</ol>
</cfoutput>
</body>
</html>

The page display is shown below:

Code Sample:

XML/Demos/XmlNodes.cfm
<cfset xmlGeorge=XMLParse(ExpandPath("George2.xml"))>
<cfset nameNodes = xmlGeorge.Name.XmlNodes>
<html>
<head>
<title>XmlNodes()</title>
</head>
<body>
<h1>XmlNodes()</h1>
<ol>
<cfoutput>
<cfloop index="i" from="1" to="#ArrayLen(nameNodes)#">
	<li>#XmlGetNodeType(nameNodes[i])# - #IsXmlNode(nameNodes[i])#</li>
</cfloop>
</cfoutput>
</ol>
</body>
</html>

The page display is shown below:

You'll notice that it matters how the element is accessed. When text nodes are accessed through dot notation (e.g, xmlGeorge.Name.FirstName.XmlText), they are not treated as XML nodes. However, when they are accessed through the XmlNodes array, they are. The same is true for comment and CDATA section nodes. To be safe, you should always test a possible node with the IsXmlNode() function before treating it as an XML node.

Processing RSS Feeds

RSS stands for Really Simple Syndication. It is an XML format for making news items or other bits of content readily available. There are different versions of RSS, but all of them contain <item> elements for holding the different news items. These <item> elements at their simplest have the following structure:

<item> <title>Item Title</title> <link>http://www.webucator.com/link/to/full/article</link> <description>Item description. Sometimes contains escaped HTML characters.</description> </item>

There are other elements that the <item> element can contain, but for our purposes, we'll keep it simple and assume that an RSS document is made up of one or more <item> tags structured as shown above. We will ignore all other elements.

There are several ways to process RSS feeds. Through code samples, we will see how to do it using XmlSearch() and XmlTransform() with XSLT. The output of the pages will look like this:

Processing RSS Feeds with XmlSearch()

Code Sample:

XML/Demos/RssFeed.cfm
<html>
<head>
<title>RSS Feed</title>
</head>
<body>
<cfparam name="FORM.rssURL" default="http://rss.news.yahoo.com/rss/topstories">

<cfoutput>
<form method="post">
	<input type="text" size="50" name="rssURL" value="#FORM.rssURL#">
	<input type="submit" value="Get Feed">
</form>

<cfset rss = XMLParse(FORM.rssURL)>

<cfset items = XMLSearch(rss, "//*[local-name()='item']")> 

<cfloop index="i" from="1" to="#ArrayLen(items)#">
	<cfset title = XMLSearch(rss, "//*[local-name()='item'][#i#]/*[local-name()='title']")>
	<cfset title = title[1].xmlText>
	
	<cfset description = XMLSearch(items[i], "//*[local-name()='item'][#i#]/*[local-name()='description']")>
	<cfset description = description[1].xmlText>
	
	<cfset link = XMLSearch(items[i], "//*[local-name()='item'][#i#]/*[local-name()='link']")>
	<cfset link = link[1].xmlText>
	
	<div style="width:600px">
		<h2 style="clear:both;padding-top:10px; border-top:1px solid blue;">#title#</h2>
		<div style="border:1px solid black; padding:4px; width:90%; margin:10px;">#description#</div>
		<div style="float:right; width:20px; margin-bottom:10px; ">
		<a href="#link#">Read</a>
		</div>
	</div>
</cfloop>
</cfoutput>
</body>
</html>

This file parses an RSS feed from a URL and creates an XML document object:

<cfset rss = XMLParse(FORM.rssURL)>

It then creates an items array containing all nodes that have the local name "item". By using the local name, we avoid the headache of namespaces. You can read this XPath as "any element descending from the root whose local name is 'item'."

<cfset items = XMLSearch(rss, "//*[local-name()='item']")>

It then loops through the items array grabbing the title, description and link nodes, assigning their XmlText to variables, and outputting the results in a div.

<cfloop index="i" from="1" to="#ArrayLen(items)#"> <cfset title = XMLSearch(rss, "//*[local-name()='item'][#i#]/*[local-name()='title']")> <cfset title = title[1].xmlText> <cfset description = XMLSearch(items[i], "//*[local-name()='item'][#i#]/*[local-name()='description']")> <cfset description = description[1].xmlText> <cfset link = XMLSearch(items[i], "//*[local-name()='item'][#i#]/*[local-name()='link']")> <cfset link = link[1].xmlText> <div style="width:600px"> <h2 style="clear:both;padding-top:10px; border-top:1px solid blue;">#title#</h2> <div style="border:1px solid black; padding:4px; width:90%; margin:10px;">#description#</div> <div style="float:right; width:20px; margin-bottom:10px; "> <a href="#link#">Read</a> </div> </div> </cfloop>

Processing RSS Feeds with XmlTransform()

The XmlTransform() function can be used in conjunction with an XSLT to process RSS feeds. The file below shows a very basic XSLT that will transform an RSS feed to output HTML very much like we saw in the last example.

Code Sample:

XML/Demos/rss.xsl
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dc="http://purl.org/dc/elements/1.1/" version="1.0">
	<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
	<xsl:template match="/">
		<xsl:apply-templates select="//*[local-name()='item']"/>
	</xsl:template>
	<xsl:template match="//*[local-name()='item']">
		<div style="width:600px">
			<xsl:apply-templates select="*[local-name()='title' or local-name()='description']"/>
		</div>
	</xsl:template>
	<xsl:template match="*[local-name()='title']">
		<h2 style="clear:both;padding-top:10px; border-top:1px solid blue;">
			<xsl:value-of select="."/>
		</h2>
	</xsl:template>
	<xsl:template match="*[local-name()='description']">
		<div style="border:1px solid black; padding:4px; width:90%; margin:10px;">
			<xsl:apply-templates />
		</div>
		<div style="float:right; width:20px; margin-bottom:10px; ">
			<a href="{parent::*[local-name()='link']}">Read</a>
		</div>
	</xsl:template>
</xsl:stylesheet>

Code Sample:

XML/Demos/RssFeed-xslt.cfm
<html>
<head>
<title>RSS Feed</title>
</head>
<body>
<cfparam name="FORM.rssURL" default="http://rss.news.yahoo.com/rss/topstories">
<cfoutput>

<form method="post">
	<input type="text" size="50" name="rssURL" value="#FORM.rssURL#">
	<input type="submit" value="Get Feed">
</form>

<cfset rss = XMLParse(FORM.rssURL)>
<cfset result = XmlTransform(rss,ExpandPath("rss.xsl"))>

<cfset resultFixed = Replace(result,"&gt;",">","ALL")>
<cfset resultFixed = Replace(resultFixed,"&lt;","<","ALL")>
#resultFixed#
</cfoutput>

</body>
</html>

Again we start by parsing an RSS feed from a URL to create an XML document object:

<cfset rss = XMLParse(FORM.rssURL)>

We then transform the document against our XSLT:

<cfset result = XmlTransform(rss,ExpandPath("rss.xsl"))>

Because some RSS feeds include escaped HTML characters in the description, we use the Replace() function to unescape these characters.

<cfset resultFixed = Replace(result,"&gt;",">","ALL")> <cfset resultFixed = Replace(resultFixed,"&lt;","<","ALL")>

Finally, we output the result:

#resultFixed#

ColdFusion and WDDX

WDDX stands for Web Distributed Data Exchange. It is an XML format for exchanging data between programming languages and across the web. Although it is not a standard format, there are WDDX implementations for JavaScript, PHP, Perl, ASP, and Java as well as ColdFusion. The following diagram shows how WDDX works.

<cfwddx>

The <cfwddx> tag is used to to convert ColdFusion structures to WDDX and WDDX to ColdFusion structures. It can also convert ColdFusion structures and WDDX strings to JavaScript objects. The tag's attributes are shown below:

<cfwddx> Attributes
Parameter Description
action Required. Possible values are:
  • cfml2wddx: converts CFML to WDDX
  • wddx2cfml: converts WDDX to CFML
  • cfml2js: converts CFML to JavaScript
  • wddx2js: converts WDDX to JavaScript
input Required. The value to convert.
output Required if action is "wddx2cfml". Variable name of output object. If action is not "wddx2cfml" and output is omitted, the result is output as text.
topLevelVariable Required when converting to JavaScript. Variable name of JavaScript WddxRecordset object, which can be manipulated with functions made available by the wddx.js file.
useTimeZoneInfo Optional. Default is "yes". Attempts to convert time between server and client.
validate Optional. Default is "no". Applicable when converting from WDDX. If "yes", WDDX is validated before conversion and an error is thrown if it is invalid.

The wddx.js File

The wddx.js file, which is stored in CFIDE/scripts/wddx.js, makes it easy to convert JavaScript objects to WDDX by creating a new object of the WddxSerializer class and calling its serialize() method.

Below we will build a simple application for creating and saving a list of resources to share among websites.

  1. First, we will build the original list as a ColdFusion structure, which we will make available as WDDX for consumption.
  2. Next, we will show how to consume the WDDX produced in step 1 with another ColdFusion page.
  3. Then we will show how to consume the WDDX produced in step 1 with JavaScript and how to modify it, save the content in a form field, and submit it to a ColdFusion page for processing.
  4. Then we will show how to save the modified WDDX content in a file.
  5. Then we will show how to read the WDDX from the file and make it available for consumption.
  6. Finally, we show how to consume that WDDX content with JavaScript. This is the same as step 3, except that it gets the WDDX from a different source.

Code Sample:

XML/Demos/WddxLinks.cfm
<cfcontent type="text/xml">
<cfset objWddx = StructNew()>

<cfset objWddx.Links = ArrayNew(1)>
<cfset i = ArrayLen(objWddx.Links)+1>
<cfset objWddx.Links[i] = StructNew()>
<cfset objWddx.Links[i].Title = "Webucator">
<cfset objWddx.Links[i].URL = "http://www.webucator.com">
<cfset objWddx.Links[i].Description = "Webucator provides customized onsite technical training.">
<cfset objWddx.Links[i].Categories = ArrayNew(1)>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Training")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"IT")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Computers")>

<cfset i = ArrayLen(objWddx.Links)+1>
<cfset objWddx.Links[i] = StructNew()>
<cfset objWddx.Links[i].Title = "Adobe">
<cfset objWddx.Links[i].URL = "http://www.adobe.com">
<cfset objWddx.Links[i].Description = "Adobe develops computer software products and technologies that enable users to express and use information across all print and electronic media.">
<cfset objWddx.Links[i].Categories = ArrayNew(1)>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Software")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"IT")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Computers")>

<cfset i = ArrayLen(objWddx.Links)+1>
<cfset objWddx.Links[i] = StructNew()>
<cfset objWddx.Links[i].Title = "Microsoft">
<cfset objWddx.Links[i].URL = "http://www.microsoft.com">
<cfset objWddx.Links[i].Description = "Microsoft offers a wide range of software, services, and Internet technologies for personal and business computing.">
<cfset objWddx.Links[i].Categories = ArrayNew(1)>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Software")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"IT")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Computers")>

<cfset i = ArrayLen(objWddx.Links)+1>
<cfset objWddx.Links[i] = StructNew()>
<cfset objWddx.Links[i].Title = "Google">
<cfset objWddx.Links[i].URL = "http://www.google.com">
<cfset objWddx.Links[i].Description = "Google is the leading search engine.">
<cfset objWddx.Links[i].Categories = ArrayNew(1)>
<cfset ArrayAppend(objWddx.Links[i].Categories,"Search")>
<cfset ArrayAppend(objWddx.Links[i].Categories,"IT")>

<cfwddx action="cfml2wddx" input="#objWddx#">
            

This file simply creates a ColdFusion structure holding the following information about web resources:

  • Link Title (string)
  • Link URL (string)
  • Link Description (string)
  • Link Categories (array)

It then converts the structure to WDDX and outputs it to the browser using the <cfwddx> tag.

Code Sample:

XML/Demos/WddxConsume.cfm
<cfset thisPage = "#CGI.SERVER_NAME##CGI.SCRIPT_NAME#">
<cfset arrFileParts = ListToArray(CGI.SCRIPT_NAME,"/")>
<cfset ArrayDeleteAt(arrFileParts,ArrayLen(arrFileParts))>
<cfset path = ArrayToList(arrFileParts,"/")>
<cfset pathToWddx = "http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT#/#path#/WddxLinks.cfm">
<cfhttp method="get" url="#pathToWddx#">

<cfwddx action="wddx2cfml" input="#cfhttp.FileContent#" output="objWddx" validate="yes">
<html>
<head>
<title>Links Page</title>
</head>

<body>
<cfset arrLinks = objWddx.links>
<cfset linkCat = "Software">
<ul>
<cfoutput>
<cfloop from="1" to="#ArrayLen(arrLinks)#" index="i">
	<cfset strCats = ArrayToList(arrLinks[i].Categories)>
	<cfif ListFind(strCats,linkCat)> 
	<li><a href="#arrLinks[i].URL#">#arrLinks[i].Title#</a> - #arrLinks[i].Description#</li>
	</cfif>
</cfloop>
</cfoutput>
</ul>
</body>
</html>

This file uses the <cfhttp> tag to read in the WddxConsume.cfm file. It then uses the <cfwddx> tag to convert the file's content to a ColdFusion structure and loops through the output producing links that are included in the "Software" category.

Code Sample:

XML/Demos/WddxConsume-js.cfm
<cfset thisPage = "#CGI.SERVER_NAME##CGI.SCRIPT_NAME#">
<cfset arrFileParts = ListToArray(CGI.SCRIPT_NAME,"/")>
<cfset ArrayDeleteAt(arrFileParts,ArrayLen(arrFileParts))>
<cfset path = ArrayToList(arrFileParts,"/")>
<cfset pathToWddx = "http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT#/#path#/WddxLinks.cfm">
<cfhttp method="get" url="#pathToWddx#">

<html>
<head>
<title>Links Page</title>
<script language="javascript" src="wddx.js"></script>
<script language="javascript">
	<cfwddx action="wddx2js" input="#cfhttp.FileContent#" toplevelvariable="objLinks">
	
	              var serializer = new WddxSerializer();
	function addLink(FORM)
	{
		var arrLen = objLinks.links.length;
		objLinks.links[arrLen] = new Object();
		objLinks.links[arrLen]["title"] = FORM.title.value;
		objLinks.links[arrLen]["description"] = FORM.description.value;
		objLinks.links[arrLen]["url"] = FORM.url.value;
		objLinks.links[arrLen]["categories"] = FORM.categories.value.split(",");
		document.forms[0].strWddx.value=serializer.serialize(objLinks);
		addListItem(objLinks.links[arrLen]["title"]);
	}
	
	function addListItem(TEXT)
	{
		var li = document.createElement("li");
		var liText = document.createTextNode(TEXT);
		list.appendChild(li);
		li.appendChild(liText);
	}
	
	var list;
	window.onload = function() {
		document.forms[0].title.focus();
		document.forms[0].strWddx.value=serializer.serialize(objLinks);
		list=document.getElementById("listLinks");
		for (i in objLinks.links)
		{
			addListItem(objLinks.links[i].title);
		}
	}
</script>
</head>
<body>
<form method="post" action="SaveLinks.cfm">
	<input type="hidden" name="strWddx" value="">
	<table>
		<tr>
			<td>Title:</td>
			<td><input type="text" size="50" name="title"></td>
		</tr>
		<tr>
			<td>URL:</td>
			<td><input type="text" size="50" name="url"></td>
		</tr>
		<tr valign="top">
			<td>Description:</td>
			<td><textarea name="description" cols="50" rows="3"></textarea></td>
		</tr>
		<tr valign="top">
			<td>Categories:</td>
			<td>
				Must be comma-delimited<br>
				<textarea name="categories" cols="50" rows="2"></textarea>
			</td>
		</tr>
		<tr>
			<td colspan="2" align="right">
				<input type="button" value="Add Link" onClick="addLink(this.form)">
				<input type="submit" value="Save Links">
			</td>
		</tr>
	</table>
</form>
<h1>Links</h1>
<ul id="listLinks"></ul>
</body>
</html>

This file also uses the <cfhttp> tag to read in the WddxConsume.cfm file. Inside HTML <script> tags, it uses the <cfwddx> tag to create the JavaScript necessary to represent the WDDX as a JavaScript object. When the page finishes loading, JavaScript is used to create list items showing the titles of the different links. An HTML form is provided for adding new links. When the "Add Link" button is clicked, a new link is added to the JavaScript object and the WDDX is stored in the hidden field, strWddx. When the "Save Links" submit button is pressed, the form is submitted to the SaveLinks.cfm page.

Code Sample:

XML/Demos/SaveLinks.cfm
<html>
<head>
<title>WDDX: Save Links</title>
</head>
<body>
	<cftry>
		<cffile action="write" output="#FORM.strWddx#" file="#ExpandPath('links.xml')#" nameconflict="overwrite">
		Links saved.
		<cfcatch type="any">
			Could not save links: <cfoutput>#cfcatch.Message#</cfoutput>
		</cfcatch>
	</cftry>
</body>
</html>

This file simply uses <cffile> to save the WDDX string to a file (links.xml).

Code Sample:

XML/Demos/WddxLinksFromFile.cfm
<cftry>
	<cfcontent type="text/xml">
	<cffile action="read" file="#ExpandPath('links.xml')#" variable="strWddx">
	
	<cfoutput>#strWddx#</cfoutput>
	<cfcatch type="any">
		<cfcontent type="text/html">
		<cfoutput>#cfcatch.Message#</cfoutput>
	</cfcatch>
</cftry>

This file uses <cffile> to read in links.xml and outputs the result.

Code Sample:

XML/Demos/WddxConsume-js2.cfm
<cfset thisPage = "#CGI.SERVER_NAME##CGI.SCRIPT_NAME#">
<cfset arrFileParts = ListToArray(CGI.SCRIPT_NAME,"/")>
<cfset ArrayDeleteAt(arrFileParts,ArrayLen(arrFileParts))>
<cfset path = ArrayToList(arrFileParts,"/")>
<cfset pathToWddx = "http://#CGI.SERVER_NAME#:#CGI.SERVER_PORT#/#path#/WddxLinksFromFile.cfm">
<cfhttp method="get" url="#pathToWddx#">


---- C O D E   O M I T T E D ----

This file is almost the same as WddxConsume-js.cfm. It just reads from WddxLinksFromFile.cfm rather than WddxLinks.cfm.

WDDX vs. Web Services

Like WDDX, web services provide a mechanism for different applications to share data. Web services, however, do much more than that. They provide a mechanism for sharing objects, making their methods invokable over distributed systems. The scope of WDDX is much smaller. WDDX simply provides a way of sharing data between program languages. It provides no built-in way for one system to invoke processes on another system.