Over the past few months, I have been learning a lot about XML and especially the use of XPath in ColdFusion. As a natural extension of that, I felt it was time to look into some ColdFusion XML Transformation stuff in the form of XSLT. I don't know anything about it other than I looked at it a long time ago and thought the syntax was just silly. Part of that may have influenced by the fact that I didn't understand XPath, so I thought it was worth re-examining.
As with any new learning endeavour, I must do some sort of Hello World example to get my feet wet. In this ColdFusion XSLT demo, I am taking a small XML document containing messages and outputting them in an HTML page:
<!--- Define the ColdFusion XML document object. ---> <cfxml variable="xmlData"> <messages> <message id="1"> <text>Hello World</text> </message> <message id="2"> <text>Eating kittens is just plain wrong!</text> </message> <message id="3"> <text>Honk if you love justice!</text> </message> </messages> </cfxml> <!--- Define the XSL Transformation (XSLT). ---> <cfsavecontent variable="strXSLT"> <!--- Document type declaration. ---> <?xml version="1.0" encoding="ISO-8859-1"?> <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!--- Bind this template to the root of the XML document using the "/" match attribute. ---> <xsl:template match="/"> <html> <head> <title>My ColdFusion XSLT Hello World</title> </head> <body> <h1> Messages </h1> <!--- Loop over each message node. ---> <xsl:for-each select="//message"> <p> <!--- Output the ID of the current contextual message node. ---> <xsl:value-of select="@id" />: <!--- Get the Text value of the descendent TEXT node of the current contextual message node. ---> <xsl:value-of select="text" /> </p> </xsl:for-each> </body> </html> </xsl:template> </xsl:transform> </cfsavecontent> <!--- Output the transformed XML document. ---> #XmlTransform( xmlData, Trim( strXSLT ) )#
A few things to notice in this example. For starters, the XML that I am passing into ColdFusion's XmlTransform() function is a full-blown ColdFusion XML document object model. According to the documentation, I could have passed in an XML string as well.
The XSLT value that I am passing in is just an XML string, but according to the documentation this could have been:
A string containing XSL text.
The name of an XSTLT file. Relative paths start at the directory containing the current CFML page.
The URL of an XSLT file; valid protocol identifiers include http, https, ftp, and file. Relative paths start at the directory containing the current CFML page.
You will notice that I am trimming the XSLT value as I pass it into the function. Remember, XML documents (what the XSLT document really is) cannot have white space at the beginning of them or you get that "target matching" error. However, when dealing with XmlTransform(), the error is a bit more convoluted:
A [Transformer] object cannot be created that satisfies the configuration requested. This could be due to a failure in compiling the [XSL] text. javax.xml.transform.TransformerConfigurationException: javax.xml.transform.TransformerException: org.xml.sax.SAXParseException: The processing instruction target matching "[xX][mM][lL]" is not allowed.
A bit more wordy, but same error. Trimming the XSLT XML data as you pass it in makes sure that it compiles down properly in the black box.
My XSLT Transform document contains three main parts: the base template, the node loop, and the node values. I am still learning the syntax, which feels very foreign to me, but basically, the template that matches "/" binds the template to the root node of the XML document. Then, my for-each command loops over the nodes returned by the XPath, //message, which of course selected all message nodes anywhere in the document. Within each of the loop iterations, the "context" of the loop is the current Message node; therefore, commands within the loop must be relative to the current context node. That's why my value-of commands don't need a whole lot of explanation - one gets the text value of the Text node, the other gets the text value of the attribute, ID.
Anyway, running the code above, we get the following output:
1: Hello World
2: Eating kittens is just plain wrong!
3: Honk if you love justice!
I can already see that having a good understanding of XPath is really helping me understand the XSL Transformation functionality. Not sure if I see too much of a place for this in my programming just yet, but then again, knowing this functionality will open its own doors.
Oh, and also, those last two quotes are from the TV show, The Tick :) That was a sweet show!
Have you seen this bug in your travels with xml?
I have posted a comment on your blog. I think I need to know more about how you are using the XML document as multiple reads should never corrupt anything??
Just to be confusing, that "Dave" isn't me ;-)
...he is another "Dave" I know...
Ok, now you confused me :) What are you talking about? You mean, that isn't your blog?
The blog I linked to is the "other" David.
He is based in Cork, Ireland (last I heard), and he found the issue with XMLParse().
I guess when I read your blog, I thought of that "gotcha" and wondered if you had encountered it at all.
The main plan is us "David"s are going to take over the world, but don't tell anyone I told you!
Ha ha. Good luck. When the revolution is over, don't forget about us little people :)
XSLT is freakin awesome. It can be used for a lot of stuff, for example I use it as my email templates, which makes it really easy to update any email that gets sent out. A while back this saved me quite a bit of time when DOD changed to INFOCON 4 and had to change to all plain text emails. I also use XSLT for generating base DAO, gateway, and service objects. Definitely good stuff!
XSLT is convoluted as hell, but immensely useful in a pinch. And powerful as hell.
I wrote a mini-feed reader a while back that I wanted to support RDF, RSS2 and Atom 1.0 feed formats, but I didn't want to write 3 separate parsers. I used XSLT to convert all formats into RSS2 (only the elements that were directly transferable or a reasonable approximation) and then wrote and RSS2 parser. Magic. :-)
@Dustin / Rob,
Both those things sound really cool. I agree that it is convoluted, but I think in the long run it will be good to know.
I appreciate your example on XSLT with CF.
I'm still not sold on XSLT though. Mainly because I can't use a WYSIWYG with it, but also because I feel like XSLT does for XML the same thing that CF does for a database. And a database would do it faster.
Now, if I got hired into a company that used XML a lot, then I could see using XSLT. But even then, I could just use CF to parse it directly and skip the middleman... which bring me back to my original thought: XSLT, why do I need you?
I think it really depends on what you want to do with it. To be honest, I very rarely use XSLT; most of the time, I just want to extract data, which ColdFusion is fantastic at. However, I have used XSLT to transform content data. If i have content that has tons of paragraphs, but I want to change P tags with a given class to some other viewlet, I find that XSLT can make this much easier that CF parsing.
Thank you for this article.
I have a very particular case in which I would really like some help. The situation is, I have an .xsl file which I used in a .cfm file. In this .xsl file I create a template which then I use in my .cfm file and then output to my cfm page. I'm trying to use coldfusion code in this .xsl file but given that it's xml I can't. So one attempt was changing the .xsl file to .cfm and place the xsl code inside <cfxml></cfxml>. Well that didn't work. I wish I could elaborate more, but let me know if you more or less undertand my situation. If not, feel free to ask more questions.