Transforming XML into Content by Using XSLT


CFML is an excellent tool for transforming content from a relational database into HTML, XML, or almost any form of content markup due to its native support for looping over a query set. However, this ease of looping and data handling does not extend to XML. ColdFusion can certainly loop over data in an XML object, and can search for data using XmlSearch(), but there is a much better solution for transforming XML into other forms of content.

XSLT (eXtensible Stylesheet Language for Transformations) was created by the W3C in 1998 as a way to easily define a transformation from an XML document to some other content format. Using XSLT, I could transform a document like Listing 15.1 into the HTML table shown in Figure 15.2 using a simple stylesheet:

Figure 15.2. HTML table.


We will build the stylesheet that creates this table throughout the chapter. If you want to take a look at the stylesheet that created Figure 15.2, look at Listing 15.11 at the end of the chapter.

Creating a basic transformation

Before we can build a complex listing of artists, CDs, ratings, and recommendations, let's start out with a simple nested bulleted list of artists and CDs. The list will display each artist's name in bold, followed by a bulleted list of the artist's genres and a separate list of the artist's CDs. Listing 15.3 shows an XSL stylesheet that gives us this information.

Listing 15.3. BasicTransformation.xslAn XSLT stylesheet to create a simple listing of artists and CDs
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />     <xsl:template match="/cdcollection">     <ul>       <xsl:apply-templates />     </ul>   </xsl:template>      <xsl:template match="/cdcollection/artist">     <li>       <b><xsl:value-of select="@name" /></b>       <br />              Genre:       <ul>         <xsl:apply-templates select="genre" />       </ul>              CDs:       <ul>         <xsl:apply-templates select="cd" />       </ul>     </li>   </xsl:template>      <xsl:template match="/cdcollection/artist/genre">     <li><xsl:value-of select="." /></li>   </xsl:template>      <xsl:template match="/cdcollection/artist/cd">     <li><xsl:value-of select="@name" /></li>   </xsl:template> </xsl:transform> 

Running that transformation (see "Performing the transformation by using XmlTransform()" later in the chapter) produces the HTML in Listing 15.4.

Listing 15.4. ArtistsAndCDs.htmA simple listing of artists and CDs
 <ul>   <li>     <b>Air</b><br/>     Genre:     <ul>       <li>Electronic</li>     </ul>     CDs:     <ul>       <li>10,000 Hz Legend</li>       <li>Talkie Walkie</li>     </ul>   </li>   <li>     <b>Kylie Minogue</b><br/>     Genre:     <ul>       <li>Dance</li>     </ul>     CDs:     <ul>       <li>Fever</li>       <li>Body Language</li>     </ul>   </li>   <li>     <b>Dannii Minogue</b><br/>     Genre:     <ul>       <li>Dance</li>       <li>Electronic</li>     </ul>     CDs:     <ul>       <li>Neon Nights</li>       <li>You Won't Forget About Me EP</li>     </ul>   </li>   <li>     <b>Brooklyn Funk Essentials</b><br/>     Genre:     <ul>       <li>Funk</li>       <li>Dance</li>       <li>Spoken Word</li>     </ul>     CDs:     <ul>       <li>Cool &amp; Steady &amp; Easy</li>     </ul>   </li>   <li>     <b>Felix Da Housecat</b><br/>     Genre:     <ul>       <li>Electronica</li>       <li>Dance</li>       <li>Retro</li>     </ul>     CDs:     <ul>       <li>A Bugged Out Mix</li>       <li>Kittenz and Thee Glitz</li>       <li>Devin Dazzle &amp; The Neon Fever</li>     </ul>   </li> </ul> 

Don't be overwhelmed by the stylesheet. It may seem completely foreign, so let's break it down bit by bit.

<xsl:transform>

XSL stylesheets are well-formed XML documents that use a specific set of tags to tell the XSL processor how to run the transformation. All transformation stylesheets use <xsl:transform> as their root element. (You may sometimes see <xsl:stylesheet> used instead of <xsl:transform>; they are interchangeable in most cases.)

The version attribute of <xsl:transform> specifies the version of the XSL standard that will be used to process the stylesheet, and the xmlns:xsl attribute identifies the namespace that contains the XSL tags. By and large, the transformations you create will always use the same values for these attributes as you see in Listing 15.3.

<xsl:output>

The first tag inside <xsl:transform> in Listing 15.3 is an <xsl:output> tag, which in this case tells the XSL processor not to output the XML declaration in the result HTML. If this <xsl:output> were not present, the resulting HTML file would have as its first line:

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

which is not valid in an HTML document. There are nine other attributes of <xsl:output> that let you specify other behaviors of the output engine; these are summarized in Table 15.1.

Table 15.1. Other attributes of <xsl:output>

ATTRIBUTE NAME

POSSIBLE VALUES

DESCRIPTION

method

xml, html, or text

Tells the XSL processor what rules are used when creating output content. method="html" allows tags to be unclosed and method="text" does not validate the output at all.

version

Any number

Specifies the version number of the output method; usually only useful with method="html"

indent

yes or no

Specifies whether the XSL processor may add whitespace when generating the output content.

encoding

Any valid IANA character set specifier (such as UTF-8 or UTF-16)

Tells the XSL processor what character set to use when outputting content.

media-type

Any valid MIME type specifier.

Specifies the MIME type of the output content.

doctype-system

a valid doctype URI

Tells the XSL processor to put out a <!DOCTYPE> element with the specified system URI.

doctype-public

a valid doctype URI

Tells the XSL processor to put out a <!DOCTYPE> element with the specified public URI.

standalone

yes or no

Tells the XSL processor whether to make the resulting output content a standalone XML document.

cdata-section-elements

a space-delimited list of element names

The XSL processor will put the contents of all elements of this name into a CDATA section in the output document.


By far the most commonly used attribute of <xsl:output> is omit-xml-declaration.

<xsl:template> and <xsl:apply-templates>

After specifying the output rules for the stylesheet by using <xsl:output>, we can now define the templates used to create the output content by using <xsl:template>.

There are four templates in this stylesheet, corresponding to the four elements we want to process. This is not to say that there will always be a one-to-one correspondence between templates and elements; in fact, there are usually only a few templates in a stylesheet compared to the number of elements present. In this first example, however, we want to keep things simple.

The first <xsl:template> is:

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

The first line of the template is the opening <xsl:template> tag, containing a single match attribute. match tells the XSL processor which nodes in the source document this template processes; in this case, this template handles cdcollection nodes located immediately under the document root.

Inside the template is a <ul> element with an <xsl:apply-templates> tag in between its opening and closing tag. <xsl:apply-templates> tells the XSL processor to start looping through the children of the current cdcollection node and to start applying XSL templates to each one.

One of the hardest things for beginning XSL programmers to grasp is exactly how an XSL processor uses the stylesheet. The conception that many have is that the stylesheet tells the processor in which order to process nodes in the XML document. However, the reverse is true. The XSL processor loops over nodes in the XML document, and for each one, attempts to find a template in the stylesheet that matches the current node. Once it finds the template, it calls the template almost as if the template were a function.

<xsl:value-of>

So once the XSL processor sees the <xsl:apply-templates> in the /cdcollection template, the processor loops over all the children of the cdcollection node. When it sees that the child node is an artist node, it looks for a template that matches and finds this one:

 <xsl:template match="/cdcollection/artist">   <li>     <b><xsl:value-of select="@name" /></b>     <br />          Genre:     <ul>       <xsl:apply-templates select="genre" />     </ul>          CDs:     <ul>       <xsl:apply-templates select="cd" />     </ul>   </li> </xsl:template> 

For each artist node, the XSL processor outputs an opening <li> tag, then puts out the artist's name in bold by using <xsl:value-of>, which is another XSL tag. Its select attribute takes an XPath expression and outputs the returned value, which in this case is the name of the current artist.

<xsl:apply-templates> Revisited

After putting out the name of the artist by using <xsl:value-of>, the artist template then generates the lists of genres and CDs for the artist. Unlike the previous use of <xsl:apply-templates>, we are no longer indiscriminately looping over all child nodes. Rather, we are using the select attribute of <xsl:apply-templates> to selectively loop over only certain children. (select works the same way for both <xsl:value-of> and <xsl:apply-templates>; in both cases select specifies an XPath expression that works from the current node.)

The only thing remaining to be explained is the <xsl:value-of> in the genre template:

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

The period is an XPath expression meaning "current node." Getting the value of the current node returns the content between the opening and closing tags; as such, the <xsl:value-of> means to output whatever the current genre is.

Performing the transformation by using XmlTransform()

An XSL stylesheet by itself does nothing. In order to transform the input XML according to the stylesheet, you must use ColdFusion to run the transformation as shown in Listing 15.5.

Listing 15.5. transformArtistAndCDList.cfmTRansforming XML content using XSL
 <cffile action="READ"   file="#ExpandPath('CDCollection.xml')#"   variable="xmlDocument"> <cfset transformedContent = XmlTransform(xmlDocument, "BasicTransformation.xsl")> <cffile action="WRITE"   file="#ExpandPath('ArtistsAndCDs.htm')#"   output="#transformedContent#">    Finished transforming content! 

The XmlTransform() function applies the specified stylesheet to the passed-in XML document and returns the generated output as a string. In Listing 15.5 we are writing it to a file, but you could just as easily output it to the screen or use it in some other way.

The XSL file argument can also be an XSL stylesheet stored in a string or a URL to the file.

Ignoring nodes in the hierarchy

So far we've been able to completely ignore the recommend nodes in the XML document by simply never including them in the select attributes that retrieve sets of nodes. But what if we wanted to skip elements in the document hierarchy (for instance, what if we wanted to show a list of CDs without showing artists at all)? Listing 15.6 shows a stylesheet that does just that.

Listing 15.6. CDList.xslA stylesheet that bypasses artist information
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />      <xsl:template match="/cdcollection">     <ul>       <xsl:apply-templates select="//cd" />     </ul>   </xsl:template>      <xsl:template match="/cdcollection/artist/cd">     <li><xsl:value-of select="@name" /></li>   </xsl:template> </xsl:transform> 

All we have to do is create a template for the root node, then use XPath to find all cd nodes in the file and output each one using the separately defined template.

NOTE

Why did we create a match for the root node of the file? One of the lesser-known caveats of XSL development is that if you don't include a root node, the XSL processor will output the value of every element it comes across until it finds the first match. This means that you run the risk of having errant text pop up if you don't always include a match for the root node.


Creating a more complex transformation

So now that we've created a few simple transformations, let's tackle the complex listing seen at the beginning of the chapter.

<xsl:if>

Although XSL is not a programming language in the traditional sense, it does have some of the standard flow control constructs present in other languages. The first of these is <xsl:if>, as demonstrated in Listing 15.7.

Listing 15.7. IfInATransformation.xslUsing <xsl:if>
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />      <xsl:template match="/cdcollection">     <style>       td {         vertical-align: top;       }     </style>     <table cellspacing="0">       <tr>         <th>Artist/CD Name</th>       </tr>       <xsl:apply-templates />     </table>   </xsl:template>      <xsl:template match="/cdcollection/artist">     <tr>       <td>         <b><xsl:value-of select="@name" /></b>         <xsl:if test="count(cd) = 1">           <br /><span style="color: red;">New Artist!</span>         </xsl:if>       </td>     </tr>     <xsl:apply-templates select="cd" />     <tr>       <td><hr /></td>     </tr>   </xsl:template>      <xsl:template match="/cdcollection/artist/cd">     <tr>       <td><xsl:value-of select="@name" /></td>     </tr>   </xsl:template> </xsl:transform> 

In Listing 15.7, we use <xsl:if> to output a block of text that says "New Artist" if the artist only has one CD in the collection. count() is an XPath function that counts the number of nodes that match the given expression.

It is worth noting that XSL does not have an else construct like CFELSE or the else keyword in other languages. One alternative is to use syntax like:

 <xsl:if test="count(cd) = 1">   ... code goes here ... </xsl:if> <xsl:if test="not(count(cd) = 1)">   ... other code goes here ... </xsl:if> 

The other alternative is to use <xsl:choose> and <xsl:otherwise> as described in the next section.

<xsl:choose>, <xsl:when>, and <xsl:otherwise>

The second XSL flow control construct is almost like the equivalent of CFSWITCH. In Listing 15.8, we use <xsl:choose>, <xsl:when>, and <xsl:otherwise> to convert the numeric rating into a user-friendly text string.

Listing 15.8. ChooseInATransformation.xslUsing <xsl:choose>
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />   <xsl:template match="/cdcollection">     <style>       td {         vertical-align: top;       }     </style>     <table cellspacing="0">       <tr>         <th>Artist/CD Name</th>         <th>Rating (for CDs)</th>       </tr>       <xsl:apply-templates />     </table>   </xsl:template>   <xsl:template match="/cdcollection/artist">     <tr>       <td colspan="2">         <b><xsl:value-of select="@name" /></b>         <xsl:if test="count(cd) = 1">           <br /><span style="color: red;">New Artist!</span>         </xsl:if>       </td>     </tr>     <xsl:apply-templates select="/cdcollection/artist/cd" />     <tr>       <td colspan="2"><hr /></td>     </tr>   </xsl:template>   <xsl:template match="cd">     <tr>       <td><xsl:value-of select="@name" /></td>       <td>         <xsl:choose>           <xsl:when test="@rating = 1">It's OK</xsl:when>           <xsl:when test="@rating = 2">Decent</xsl:when>           <xsl:when test="@rating = 3">Like It</xsl:when>           <xsl:when test="@rating = 4">Love It</xsl:when>           <xsl:when test="@rating = 5">Favorite!</xsl:when>           <xsl:otherwise>Unknown</xsl:otherwise>         </xsl:choose>       </td>     </tr>   </xsl:template> </xsl:transform> 

The major difference between <xsl:choose> and switch constructs in other languages is that XSL evaluates each condition separately, making <xsl:choose> more flexible (but also slower) than switch constructs other languages.

<xsl:for-each> and <xsl:text>

The last of the flow control constructs in XSL is the loop construct, <xsl:for-each>. Where <xsl:apply-templates> tells the XSL processor to loop over child nodes and find a matching template for each one, <xsl:for-each> gives a specific action to be taken for each node.

In Listing 15.9, I am using <xsl:for-each> to put out the list of genres in which each artist performs.

Listing 15.9. LoopInATransformation.xslUsing <xsl:for-each>
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />      <xsl:template match="/cdcollection">     <style>       td {         vertical-align: top;       }     </style>          <table cellspacing="0">       <tr>         <th>Artist/CD Name</th>         <th>Rating (for CDs)</th>       </tr>       <xsl:apply-templates />     </table>   </xsl:template>      <xsl:template match="/cdcollection/artist">     <tr>       <td colspan="2">         <b><xsl:value-of select="@name" /></b><br />         <xsl:if test="count(cd) = 1">           <span style="color: red;">New Artist!</span><br />         </xsl:if>         <xsl:for-each select="genre">           <xsl:value-of select="." />           <xsl:if test="position() &lt; last()">             <xsl:text>, </xsl:text>           </xsl:if>         </xsl:for-each>       </td>     </tr>     <xsl:apply-templates select="cd" />     <tr>       <td colspan="2"><hr /></td>     </tr>   </xsl:template>   <xsl:template match="/cdcollection/artist/cd">     <tr>       <td><xsl:value-of select="@name" /></td>       <td>         <xsl:choose>           <xsl:when test="@rating = 1">It's OK</xsl:when>           <xsl:when test="@rating = 2">Decent</xsl:when>           <xsl:when test="@rating = 3">Like It</xsl:when>           <xsl:when test="@rating = 4">Love It</xsl:when>           <xsl:when test="@rating = 5">Favorite!</xsl:when>           <xsl:otherwise>Unknown</xsl:otherwise>         </xsl:choose>       </td>     </tr>   </xsl:template> </xsl:transform> 

There are several new concepts in the <xsl:for-each> loop above, so let's break it down line-by-line.

The first line is the <xsl:for-each> loop, and the select attribute works exactly like the select attribute of <xsl:apply-templates>.

You're familiar with <xsl:value-of> by now, but there's something peculiar about the <xsl:if> test. What we're doing is putting out a comma only if the current node is not the last genre in the selected set. position() and last() are XPath functions that return the position of the current node and last node in the current set, respectively.

However, the &lt; seems out of place. In any other language, we would just say:

 position() < last() 

But remember that an XSL stylesheet is just another XML document. Meaning that it must be well-formed, so special characters must be escaped. As such, we must replace the < with &lt;.

Finally, let's turn our attention to the <xsl:text> element. Unfortunately, XSL can be very sloppy with whitespace, but there are times when we want to include literal whitespace or other text within a stylesheet, and embedding text within an <xsl:text> does just that.

<xsl:element> and <xsl:attribute>

Now that we've covered flow control in XSL, it's time for one of the most difficult concepts for many programmers to master. I want to surround the names of artists and CDs in my list with a link to another ColdFusion page, such that in the output stream I get markup like

 <a href="ArtistDetail.cfm?id=1">10,000 Hz Legend</a> 

Most developers' first instinct would be something like the following:

[View full width]

<xsl:template match="/cdcollection/artist"> <a href="ArtistDetail.cfm?id=<xsl:value-of select="@id" />"><xsl:value-of select="@name" /></a> </xsl:template>

This is much like what would be done in ColdFusion, but it is not valid in XSL because the markup is not valid XML. It would seem that we are out of luck, but XSL provides the <xsl:element> and <xsl:attribute> tags to do just what we're trying to do, as shown in Listing 15.10.

Listing 15.10. DynamicElements.xslUsing <xsl:element> to create dynamic HTML elements
  <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />      <xsl:template match="/cdcollection">     <style>       td {         vertical-align: top;       }     </style>          <table cellspacing="0">       <tr>         <th>Artist/CD Name</th>         <th>Rating (for CDs)</th>       </tr>       <xsl:apply-templates />     </table>   </xsl:template>      <xsl:template match="/cdcollection/artist">     <tr>       <td colspan="2">         <b>           <xsl:element name="a">             <xsl:attribute name="href">               <xsl:text>ArtistDetail.cfm?id=</xsl:text>               <xsl:value-of select="@id" />             </xsl:attribute>             <xsl:value-of select="@name" />           </xsl:element>         </b><br />         <xsl:if test="count(cd) = 1">           <span style="color: red;">New Artist!</span><br />         </xsl:if>         <xsl:for-each select="genre">           <xsl:value-of select="." />           <xsl:if test="position() &lt; last()">             <xsl:text>, </xsl:text>           </xsl:if>         </xsl:for-each>       </td>     </tr>     <xsl:apply-templates select="cd" />     <tr>       <td colspan="2"><hr /></td>     </tr>   </xsl:template>   <xsl:template match="/cdcollection/artist/cd">     <tr>       <td>         <xsl:element name="a">           <xsl:attribute name="href">             <xsl:text>CDDetail.cfm?id=</xsl:text>             <xsl:value-of select="@id" />           </xsl:attribute>           <xsl:value-of select="@name" />         </xsl:element>       </td>       <td>         <xsl:choose>           <xsl:when test="@rating = 1">It's OK</xsl:when>           <xsl:when test="@rating = 2">Decent</xsl:when>           <xsl:when test="@rating = 3">Like It</xsl:when>           <xsl:when test="@rating = 4">Love It</xsl:when>           <xsl:when test="@rating = 5">Favorite!</xsl:when>           <xsl:otherwise>Unknown</xsl:otherwise>         </xsl:choose>       </td>     </tr>   </xsl:template> </xsl:transform> 

Because the two sections containing <xsl:element> are so similar, let's just dissect the first one:

 <xsl:element name="a">   <xsl:attribute name="href">     <xsl:text>ArtistDetail.cfm?id=</xsl:text>     <xsl:value-of select="@id" />   </xsl:attribute>   <xsl:value-of select="@name" /> </xsl:element> 

Here I am creating an <a> element with a single href attribute. I can break the attribute value across multiple lines because <xsl:text> ensures that the only whitespace in the attribute is what's inside of the <xsl:text> block.

Using named templates

Now we finally come to displaying artist and CD recommendations. Basically, we want to display the name of the recommended artists and/or CDs next to each artist and/or CD that has recommend nodes in the source document. This means that we need a way to look up an artist's or CD's name given its ID. In most languages, you'd create a function to do this work for you, but remember that XSL for the most part does not have the concept of a "function."

However, XSL does have templates. Until now, all of our templates have used the match attribute to specify that they be called automatically for certain nodes in the input XML document:

 <xsl:template match="/cdcollection/artist/cd"> ... </xsl:template> 

There is also, however, a name attribute used to specify that a given template is programmatically called from within another template:

 <xsl:template name="MyTemplateName"> ... </xsl:template> 

This second form of <xsl:template> can be called using the <xsl:call-template> tag:

 <xsl:call-template name="MyTemplateName" /> 

The real beauty of named templates, however, is the ability to pass parameters. To define a parameter in the named template, you use the <xsl:param> tag:

 <xsl:template name="MyTemplateName">   <xsl:param name="MyParamName" />   ... </xsl:template> 

And to pass the parameter in the template call, you use the <xsl:with-param> tag:

 <xsl:call-template name="MyTemplateName">   <xsl:with-param name="MyParamName" select="XPathExpression" /> </xsl:call-template> 

So coming back to the main Artist/CD Listing transformation, Listing 15.11 shows the complete transformation with all the new features present.

Listing 15.11. CompleteTransformation.xslA complete listing of artists, CDs, and recommendations
 <xsl:transform   version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output omit-xml-declaration="yes" />      <xsl:template match="/cdcollection">     <style>       td {         vertical-align: top;       }     </style>          <table cellspacing="0">       <tr>         <th>Artist/CD Name</th>         <th>Rating (for CDs)</th>         <th>Recommendations</th>       </tr>       <xsl:apply-templates />     </table>   </xsl:template>      <xsl:template match="/cdcollection/artist">     <tr>       <td colspan="2">         <b>           <xsl:element name="a">             <xsl:attribute name="href">               <xsl:text>ArtistDetail.cfm?id=</xsl:text>               <xsl:value-of select="@id" />             </xsl:attribute>             <xsl:value-of select="@name" />           </xsl:element>         </b><br />         <xsl:if test="count(cd) = 1">           <span style="color: red;">New Artist!</span><br />         </xsl:if>         <xsl:for-each select="genre">           <xsl:value-of select="." />           <xsl:if test="position() &lt; last()">             <xsl:text>, </xsl:text>           </xsl:if>         </xsl:for-each>       </td>       <td>         <xsl:if test="count(recommend)">           <ul>             <xsl:for-each select="recommend">               <li>                 <xsl:call-template name="FindArtistName">                   <xsl:with-param name="id" select="@artist" />                 </xsl:call-template>               </li>             </xsl:for-each>           </ul>         </xsl:if>       </td>     </tr>     <xsl:apply-templates select="cd" />     <tr>       <td colspan="3"><hr /></td>     </tr>   </xsl:template>      <xsl:template match="/cdcollection/artist/cd">     <tr>       <td>         <xsl:element name="a">           <xsl:attribute name="href">             <xsl:text>CDDetail.cfm?id=</xsl:text>             <xsl:value-of select="@id" />           </xsl:attribute>           <xsl:value-of select="@name" />         </xsl:element>       </td>       <td>         <xsl:choose>           <xsl:when test="@rating = 1">It's OK</xsl:when>           <xsl:when test="@rating = 2">Decent</xsl:when>           <xsl:when test="@rating = 3">Like It</xsl:when>           <xsl:when test="@rating = 4">Love It</xsl:when>           <xsl:when test="@rating = 5">Favorite!</xsl:when>           <xsl:otherwise>Unknown</xsl:otherwise>         </xsl:choose>       </td>       <td>         <xsl:if test="count(recommend)">           <ul>             <xsl:for-each select="recommend">               <li>                 <xsl:call-template name="FindCDName">                   <xsl:with-param name="id" select="@cd" />                 </xsl:call-template>               </li>             </xsl:for-each>           </ul>         </xsl:if>       </td>     </tr>   </xsl:template>      <xsl:template name="FindArtistName">     <xsl:param name="id" />          <xsl:value-of select="//artist[@id = $id]/@name" />   </xsl:template>      <xsl:template name="FindCDName">     <xsl:param name="id" />          <xsl:value-of select="//cd[@id = $id]/@name" />   </xsl:template> </xsl:transform> 

In the main templates for cd and artist, I loop over the recommend nodes (if they exist) using <xsl:for-each>, and for each one, I call the FindArtistName or FindCDName template, passing in the artist or cd attribute. Notice that inside the named templates, I reference the parameter value by using $id. $ is an XPath prefix that means "parameter" or "variable."

Listing 15.12 shows the HTML markup generated by CompleteTransformation.xsl. To generate this file, run TransformComplexList.cfm, which is included in the sample code for this chapter. Note that I have cleaned up the markup to make it easier to read; this in no way affects the displayed content.

Listing 15.12. CompleteArtistsAndCDs.htmThe output of the final transformation
 <style>   td {     vertical-align: top;   } </style> <table cellspacing="0"> <tr>   <th>Artist/CD Name</th>   <th>Rating (for CDs)</th>   <th>Recommendations</th> </tr> <tr>   <td colspan="2">     <b><a href="ArtistDetail.cfm?id=1">Air</a></b><br/>     Electronic   </td>   <td/> </tr> <tr>   <td><a href="CDDetail.cfm?id=1">10,000 Hz Legend</a></td>   <td>Like It</td>   <td>     <ul>       <li>Talkie Walkie</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=2">Talkie Walkie</a></td>   <td>Love It</td>   <td>     <ul>       <li>10,000 Hz Legend</li>       <li>You Won't Forget About Me EP</li>     </ul>   </td> </tr> <tr><td colspan="3"><hr/></td></tr> <tr>   <td colspan="2">     <b><a href="ArtistDetail.cfm?id=2">Kylie Minogue</a></b><br/>     Dance   </td>   <td>     <ul>       <li>Dannii Minogue</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=3">Fever</a></td>   <td>Like It</td>   <td>     <ul>       <li>Body Language</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=4">Body Language</a></td>   <td>Love It</td>   <td>     <ul>       <li>Neon Nights</li>     </ul>   </td> </tr> <tr><td colspan="3"><hr/></td></tr> <tr>   <td colspan="2">     <b><a href="ArtistDetail.cfm?id=3">Dannii Minogue</a></b><br/>     Dance, Electronic   </td>   <td>     <ul>       <li>Kylie Minogue</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=5">Neon Nights</a></td>   <td>Favorite!</td>   <td>     <ul>       <li>Body Language</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=6">You Won't Forget About Me EP</a></td>   <td>Favorite!</td>   <td>     <ul>       <li>Neon Nights</li>     </ul>   </td> </tr> <tr><td colspan="3"><hr/></td></tr> <tr>   <td colspan="2">     <b><a href="ArtistDetail.cfm?id=4">Brooklyn Funk Essentials</a></b><br/>     <span style="color: red;">New Artist!</span><br/>     Funk, Dance, Spoken Word   </td>   <td>     <ul>       <li>Air</li>       <li>Felix Da Housecat</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=7">Cool &amp; Steady &amp; Easy</a></td>   <td>Favorite!</td>   <td>     <ul>       <li>Body Language</li>       <li>Neon Nights</li>     </ul>   </td> </tr> <tr><td colspan="3"><hr/></td></tr> <tr>   <td colspan="2">     <b><a href="ArtistDetail.cfm?id=5">Felix Da Housecat</a></b><br/>     Electronica, Dance, Retro   </td>   <td>     <ul>       <li>Kylie Minogue</li>       <li>Brooklyn Funk Essentials</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=8">A Bugged Out Mix</a></td>   <td>Like It</td>   <td>     <ul>       <li>Body Language</li>       <li>You Won't Forget About Me EP</li>       <li>Kittenz and Thee Glitz</li>     </ul>   </td> </tr> <tr>   <td><a href="CDDetail.cfm?id=9">Kittenz and Thee Glitz</a></td>   <td>Favorite!</td>   <td/> </tr> <tr>   <td><a href="CDDetail.cfm?id=10">Devin Dazzle &amp; The Neon Fever</a></td>   <td>Favorite!</td>   <td>     <ul>       <li>Kittenz and Thee Glitz</li>     </ul>   </td> </tr> <tr><td colspan="3"><hr/></td></tr> </table> 

Take some time to compare Listings 15.11 and 15.12 to see how the XSL stylesheet generated the HTML markup. Also compare these listings to Figure 15.2 to see how it all fits together.



Advanced Macromedia ColdFusion MX 7 Application Development
Advanced Macromedia ColdFusion MX 7 Application Development
ISBN: 0321292693
EAN: 2147483647
Year: 2006
Pages: 240
Authors: Ben Forta, et al

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net