Inside Coldfusion MX

In this section, we are going to look at how to work with two XML-related technologies and W3C recommendations. One is called XPath and the other is XSL/XSLT. ColdFusion MX has built-in XPath and XSLT parsers/processors, giving ColdFusion developers access to the full power of these XML-related technologies.

Using XPath

XPath is a language for finding information in an XML document. Using XPath, we can specify the locations of document structures or data in an XML document and then process the information using XSLT (which we will look at in the next section). In practice, it can be difficult to determine where XSLT stops and where XPath starts, but they were developed as two different recommendations by the W3C.

Note

Be sure to read the XPath recommendation at www.w3c.org/TR/xpath.

In many ways, XPath will seem familiar to you as a ColdFusion developer in that it has uses similar to those that SQL has for data querying. Although it is beyond the scope of this book to totally cover Xpath, we will introduce you to its basic concepts so that you can effectively use it and understand enough of it to be able to easily pick up more if needed.

Location Paths

One of the basic concepts of XPath that is fundamental to its usage (and that of XSLT) is the concept of location paths. In XPath, to specify a node or set of nodes, you use a location path that consists of one or more location steps separated by / or //, much like a file path. You can start your location path from the root node or absolute location path by using /, or you can start from a relative position based on context by using // (which starts your search at the current node you are at).

Location steps are made up of an axis, a node test, and zero or more predicates. Let's look at a quick example before we move on.

In ColdFusion MX, we can perform XPath expressions on XML document objects using the function XMLSearch, which has the following general syntax:

XmlSearch(xmlDoc, xPathString)

ColdFusion takes the XPath expression in string format and returns an array of XML document objects containing the elements that match the expression.

Let's say we want to grab all the FirstNames from our Users.xml document. Well, the XPath expression we need to perform is /Users/sysUser/FirstName, which basically tells the parser that starting from the root node, you want all the nodes that match FirstName. In ColdFusion, to execute this expression and print out the results, we do what is shown in Listing 20.4.

Listing 20.4 xpathgetnames.cfm

<cffile action="read" file="e:\cfusionmx\wwwroot\xml\users.xml" variable="myxml"> <cfscript> myxmldoc = XmlParse(myxml); selectedElements = XmlSearch(myxmldoc, "/Users/sysUser/FirstName"); for (i = 1; i LTE ArrayLen(selectedElements); i = i + 1) writeoutput(selectedElements[i].XmlText & "<br>"); </cfscript>

If you look at this code, you can see we use the XmlSearch function to apply our XPath expression to our XML document object, and the results are returned as an array of elements that matches the expression. So to print them out, we loop over the array and output the results.

You should get the FirstName of all the Users in the Users.xml file. Looking at Figure 20.3, you can see how the XPath expression navigates the tree to return the results. It starts at the root node (which, in this case, is Users) and then goes to sysUsers and then the FirstName element. It returns the result and then loops through the XML file until it finds no more elements that match the expression. Let's look at XPath expressions in a little more detail.

Figure 20.3. Another perspective on a DOM tree.

Path Expressions

An XPath expression is an expression used for selecting a node set by following a path or steps. Although the complete set of path expressions includes a much larger group of operators, Table 20.1 shows some of the most useful ones.

Table 20.1. Path Expressions

Operator

Description

element

Selects all element children of the context node.

Example: article

Selects all the child nodes of the article element.

/

Selects from the root node of the current document of the node in context.

Example: /Users

Selects the Users element, starting at the root of the document.

Example: x/FirstName

Select all FirstName elements that are children of x.

//

Selects nodes in the document from the current context that match the selection no matter where they are. When used with a context, this operator selects all descendant nodes in the context, no matter how many levels deep they are.

Example: //FirstName

Selects all FirstName elements no matter where they are in the document.

Example: x//LastName

Selects all LastName nodes that are descendant of the x element, no matter where they exist under the x element.

Before we move on, Listing 20.5 presents you with a simple script you can use to test most of your XPath expressions and examples as we move forward. It will enable you to submit an XPath expression via a form and will apply that expression to our Users.xml file.

Listing 20.5 expressiontester.cfm

<html> <form action="expressiontester.cfm" method="post"> <input type="test" name="exp"> <input type="Submit" title="test Expression"> </form> </html> <cfif IsDefined("form.exp")> <cfset testexp=#form.exp#> <cffile action="read" file="e:\cfusionmx\wwwroot\xml\users.xml" variable="myxml"> <cfscript> myxmldoc = XmlParse(myxml); selectedElements = XmlSearch(myxmldoc, testexp); for (i = 1; i LTE ArrayLen(selectedElements); i = i + 1) writeoutput(selectedElements[i].XmlText & "<br>"); </cfscript> <cfelse> You need to define your expression! <cfabort> </cfif>

When you perform queries, note that if the XPath expression does not match anything, it will return an empty array.

XPath Axes

XPath has a special concept called an axis. An axis usually returns a list of nodes based on the context of the original node. For example, the expression /Users/sysUser/FirstName refers to the FirstName element that is a child of sysUser. sysUser, in this case, is the axis, and the list of nodes returned will be the FirstName's of the Users.

Table 20.2 shows examples of some the axis location paths.

Table 20.2. Examples of Axis Location Paths

Expression

Abbreviation

Comment

self

.

This selects the current node in the context.

ancestor

 

This selects a path of all parent and parent-of-parent nodes of the current node, starting from the first parent above the context node.

parent

..

This selects only the single parent of the context node.

attribute

@

This selects all the attributes of an element. @sysUser

Selects the PERSONID attribute.

child

 

Selects all the children of the current node. This is the default in the abbreviated syntax.

Predicates

The axis helps us find nodes around the current node. To be able to find a subnode that contains a specific value, we use a predicate. It consists of a "qualifying expression" to do the query.

The syntax for the predicate uses square brackets around the expression. For example:

sysUser[FirstName="robi"]

Here are two more examples of predicates:

sysUser[position()=2]

This is syntax to find the second sysUser element.

sysUser[starts-with(name, "R")]

This is syntax to find all sysUser nodes whose name element starts with R.

XPath Expressions

XPath includes another concept called "expressions," which are basically the axis, a predicate, and some operator, such as +, =, or mod.

Filter patterns can consist of expressions, such as Booleans (AND, OR, and so on), substrings, and others. These expressions are found as the expression in the predicate, or they can stand on their own.

Table 20.3 shows the expressions available with XPath.

Table 20.3. XPath Expressions

Operator

Description

and, or

Logical and, or

=

Equal to

!=

Not equal to

>, >=

Greater than, greater than or equal to

<, <=

Less than, less than or equal to

Remember to code the < character as the XML entity &lt when you are passing it via forms or URLs.

+, -, *, div

Addition, subtraction, multiply, divide

mod

Modular (returns the integer remainder of a division)

|

Computes (unions) two node sets

Here are some examples of the use of expressions:

sysUser[FirstName="Robi" and FirstName="Dan"]

This returns sysUser nodes in which the FirstName is Robi and Dan. It might be necessary to code the quote marks as apostrophes (single quote marks).

sysUser|Users

This returns sysUser and Users nodes using a compute (union).

There is more to XPath than we can deal with in this brief encounter, but these are the basics, and they cover the majority of XPath functionality you might use. In the next section, we will discuss XML eXstensible Stylesheet Language Transformation. We also will get to see more applications of XPath, which XSLT also uses to do pattern matching in XML documents.

XSLT with ColdFusion

The eXtensible Stylesheet Language Transformation (XSLT) is used to transform an XML document from one format to another, which may or may not be XML. XSLT is often used to convert a target XML document from XML to HTML, WML, plain text, or some other format that the requesting client needs to view the data.

XSLT is probably different from most programming languages you have worked with because it is based on template rules that specify how XML documents should be processed. Although conventional programming languages are often sequential, template rules can be based on any order because XSLT is a declarative language. The style sheet declares what output should be produced when a pattern in the XML document is matched.

For example, a style sheet could declare that when the XSLT transformation engine finds a FirstName element, it should add markup by calling the FirstName template.

Note

Once again, make sure to read the XSLT recommendation at www.w3.org/Style/XSL/.

Also note that XSLT 2.0 was in working draft during the writing of this book.

XSLT transforms an XML document by applying an eXtensible Stylesheet Language (XSL) style sheet. (When stored in a file, XSL style sheets typically have the extension .xsl.) ColdFusion provides the XmlTransform function to apply an XSL transformation to an XML document. The function takes an XML document in string format (or an XML document object and an XSL style sheet in string format) and returns the transformed document as a string.

Let's just get right into it. In the next example, we are going to use ColdFusion to change Users.xml to HTML. First let's create a CFML template to call our XSLT style sheet (see Listing 20.6).

Listing 20.6 Test.xslsimple.cfm

<cffile action="read" file="#ExpandPath(".")#\users.xml" variable="myxml"> <cffile action="read" file="#ExpandPath(".")#\simpleexm.xsl" variable="xslDoc"> <cfset mydocAsString=ToString(myxml)> <cfset transformedXML = XmlTransform(mydocAsString, xslDoc)> <cffile action="write" file="#ExpandPath(".")#\resulthtml.html" output="#transformedXML#"> <cfoutput>#transformedXML#</cfoutput>

Notice that we read both the XML file and the XSL file and then convert them into strings before we hand them off to the XMLTransform function. Okay , now for the fun stuff! See Listing 20.7.

Listing 20.7 simplexsl.xsl

<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <html> <head> <title>Users</title> </head> <body> <table border="2"> <thead> <tr> <th>FirstName</th> <th>LastName</th> <th>User Name</th> </tr> </thead> <tbody> <xsl:for-each select="Users/sysUser"> <tr> <td><xsl:value-of select="FirstName"/></td> <td><xsl:value-of select="LastName"/></td> <td><xsl:value-of select="UserName"/></td> </tr> </xsl:for-each> </tbody> </table> </body> </html> </xsl:template> </xsl:stylesheet>

Let's step through the XSL. The first thing you will notice is that the XSL style sheet starts with the following:

<?xml version="1.0" encoding="UTF-8"?>

This means that XSL is XML, which implies something interesting. Because XSL is XML and XSL is for transforming XML, you can actually apply a style sheet to a style sheet or use a style sheet to create a new style sheet.

The second thing you will notice is this:

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

Every XSL file needs to specify the XSL namespace so that the parser knows which version of XSLT to use. The one we are using is the most current but be careful. If you have played with XSL on versions of the Microsoft parser MSXML before version 3.0, you will get an error if you try to use those same style sheets due to Microsoft's use of a temporary namespace (www.w3.org/TR/WD-xsl" version="1.0"). This is now outdated and doesn't conform to the latest W3C recommendation.

The namespace prefix xsl: is used in the rest of the XSL file to identify XSL processing statements. If a statement isn't prefixed with xsl:, it's simply copied to the output without being processed. This is the way to add HTML statements to the output:

<xsl:template match=" ... ">

Before processing can begin, the part of the XML document with the information to be copied to the output must be selected with an XPath expression. The selected section of the document is called a node and is normally selected with the match operator. If the entire document is to be selected, match the root node using match="/". Another approach is to match the document element (the element that includes the entire document). In our example, the document element can be selected using match="Users". (If you use this alternative approach, don't include Users in the for-each selection of the following code line.)

<xsl:for-each select=" Users/sysUser">

The expression xsl:for-each finds all sysUser elements in the Users element context using the XPath expression Users/sysUser. If the selected node contains all elements in the root, all of the Users elements will be selected. Because we want to include all sysUser elements in our output document, we have used this expression.

The for-each expression is a loop that processes the same instructions for these elements.

<xsl:value-of select="FirstName"/>

When the xsl:for-each expression has selected a sysUser element, the xsl:value-of expression extracts and copies to the output file the value stored in the selected element. In this case, the value stored in the FirstName element is copied to the output. Okay, now let's revisit XPath again as it pertains to XSLT.

Using XPath Functions in XSLT

XSLT style sheets can take advantage of a large list of functions. The following functions are just a few examples that can be used in XPath queries.

count

The count function takes a node set, which returns a number of the nodes present in that node set. The syntax is as follows:

count(nodes)

In Listing 20.8, we are looking for the number of times Robi and Sen are repeated in the Users.xml.

Listing 20.8 countenodes.xsl

<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match="/"> <p>Firstname equaling Robi = <xsl:value-of select="count(Users/sysUser[FirstName='Robi'])"/></p> <p>Last Name equal to Sen = <xsl:value-of select="count(Users/sysUser[LastName='Sen'])"/></p> </xsl:template> </xsl:stylesheet>

You can execute this style sheet with Listing 20.9.

Listing 20.9 countnodes.cfm

<cffile action="read" file="#ExpandPath(".")#\users.xml" variable="myxml"> <cffile action="read" file="#ExpandPath(".")#\countnodes.xsl" variable="xslDoc"> <cfset xslAsString=ToString(xslDoc)> <cfset mydocAsString=ToString(myxml)> <cfset transformedXML = XmlTransform(mydocAsString, xslAsString)> <cfoutput>#transformedXML#</cfoutput> which will generate The output: Firstname equals Robi = 1 LastName equals Sen = 1

number

The number function converts any value to a number. If the value being parsed is a string, it will return NaN (not a number).

The following code fragment examines a node set that contains a number:

<p>The number is: <xsl:value-of select="number(books/book/price)"/></p>

The resulting output would be as follows:

The number is: 1900

position

The position function returns the position value of the element in its context. Some examples of when you might use this function include when you need to insert numbering next to a list of items or when you need to test whether the position of an element is the last one (which you would test against the last function).

If you sort your elements, it will return the position in its sorted position.

Listing 20.10 inserts the number next to the description. Note that we are using the XSLT element <xsl:number> in this example.

Listing 20.10 getposition.xsl

<?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:for-each select="price-list/price-group"> <p><xsl:number value="position()"/>. <xsl:value-of select="description"/></p> </xsl:for-each> </xsl:template> </xsl:stylesheet>

To test our XSLT, we are going to use Listing 20.11, which will apply our style sheet to our pricelist.xml file from Chapter 19.

Listing 20.11 Listnodes.cfm

<cffile action="read" file="#ExpandPath(".")#\pricelist.xml" variable="myxml"> <cffile action="read" file="#ExpandPath(".")#\getposition.xsl" variable="xslDoc"> <cfset xslAsString=ToString(xslDoc)> <cfset mydocAsString=ToString(myxml)> <cfset transformedXML = XmlTransform(mydocAsString, xslAsString)> <cfoutput>#transformedXML#</cfoutput>

The output will look like this:

1. 2. 3. 4. 5. 6. 7.

substring

The substring function returns a part of a string based on the parameters you pass to it. The format is as follows:

substring(value, start) substring(value, start, length)

The character position (start) begins with 1.

In Listing 20.12, we are selecting the first three letters of the product name.

Listing 20.12 getproductname.cfm

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <xsl:for-each select="price-list/price-group"> <p><xsl:value-of select="substring(name, 1, 3)"/></p> </xsl:for-each> </xsl:template> </xsl:stylesheet>

We can call them by using Listing 20.13.

Listing 20.13 Code to Call First Three Letters of the Product Name

<cffile action="read" file="#ExpandPath(".")#\pricelist.xml" variable="myxml"> <cffile action="read" file="#ExpandPath(".")#\productname.xsl" variable="xslDoc"> <cfset xslAsString=ToString(xslDoc)> <cfset mydocAsString=ToString(myxml)> <cfset transformedXML = XmlTransform(mydocAsString, xslAsString)> <cfdump var="#transformedXML#">

This will return something like:

Col Koj Fla Jru Win VisDre

sum

The sum function calculates totals for a node set. You need to be aware of nodes that do not contain values because this function will return NaN if one of the items is not numeric (that is, empty). You will need to do formatting of your code to overcome this, replacing empty values with a 0. The syntax is as follows:

sum(node)

In our XML example, if we wanted to calculate the total of all the product prices, our code would read as in Listing 20.14.

Listing 20.14 sumprices.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/"> <p>Total Price = <xsl:value-of select="sum(//list-price)"/></p> </xsl:template> </xsl:stylesheet>

We can test this style sheet once again with Listing 20.15.

Listing 20.15 sumprices.cfm

<cffile action="read" file="#ExpandPath(".")#\pricelist.xml" variable="myxml"> <cffile action="read" file="#ExpandPath(".")#\sumprices.xsl" variable="xslDoc"> <cfset xslAsString=ToString(xslDoc)> <cfset mydocAsString=ToString(myxml)> <cfset transformedXML = XmlTransform(mydocAsString, xslAsString)> <cfoutput>#transformedXML#</cfoutput>

The output would yield the following:

Total Price = 5495

The preceding functions are but a few of the functions you can use in XPath and XSLT. Although we have by no means exhausted the power of XPath and XSLT, you now have the basic knowledge you need to understand these technologies and how to use them with ColdFusion.

Категории