Lately, I've been putting a lot of thought into the way that I am storing content for my blog entries. As part of this thinking, I've been considering more XSLT strategies to address aspects of my content management system that I've never quite liked. One example of this discontent is over the way in which I display images within my content. All of my images get displayed with a border and a dark, surrounding glow. I accomplish this effect by putting each image inside a TABLE tag that contains many additional TD's used purely for formatting.
The use of a TABLE tag for this formatting is not what bothers me (I'm not that anal about TABLE tags); what bothers me is that all the TABLE XHTML is actually stored in my database as part of the content data. This is a serious merging of my data and my display in a way that I'm not comfortable with. If I could go back and do it all over again, I'd store each image as a simple tag and then replace it on display.
Now, I chose the word, "replace," here for a very specific reason - because my first instinct would be to use some sort of regular expression "replace." As much as I think regular expressions are a supreme gift, they are not the right tool for all replace-type situations. When it comes to XHTML, we're not really looking for text patterns - we're looking for particular sets of nodes within a structured, hierarchical document object model.
It is exactly this type of DOM replace action that XSLT and ColdFusion's XMLTransform() excel at. And, as a first step in this direction, I wanted to experiment with transforming content data, wrapping the IMG tags in a TABLE and then copying every other node as-is. Once I can do a generic copy of XHTML data with a single hand-picked exception, I should be able to extend this functionality to encompass all data transformations desired within the entire set of blog content data.
<!--- Define XHTML style data. ---> <cfsavecontent variable="strData"> <div id="contentarea"> <p> Maria Bello is so awesome. Just look at her in this polaroid picture - you can't just tell she has a great attitude. </p> <p class="image"> <img src="http://farm4.static.flickr.com/3201/ 3069379561_2e8cb1be2c.jpg" /> </p> <p> This makes me want to go and watch A History of Violence again; what an awesome film. She is so wicked hot in it! Oh man! </p> </div> </cfsavecontent> <!--- Define the 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"> <!--- Match all generic nodes. ---> <xsl:template match="*"> <!--- Copy this node (non-deep copy). ---> <xsl:copy> <!--- Make sure that all attributes are copied over for the current node. ---> <xsl:copy-of select="@*" /> <!--- Apply templates to all of it's child nodes (so that they can be copied). ---> <xsl:apply-templates /> </xsl:copy> </xsl:template> <!--- Look for any image nodes. We need to take these and format them with our special image display. ---> <xsl:template match="p[ @class = 'image' ]"> <table class="imageborder" cellspacing="0" cellpadding="0" border="0" width="100%"> <tbody> <tr> <td rowspan="3" width="50%"> <xsl:call-template name="nbsp" /> </td> <td class="nw"> <xsl:call-template name="nbsp" /> </td> <td class="n"> <xsl:call-template name="nbsp" /> </td> <td class="ne"> <xsl:call-template name="nbsp" /> </td> <td rowspan="3" width="50%"> <xsl:call-template name="nbsp" /> </td> </tr> <tr> <td class="w"> <xsl:call-template name="nbsp" /> </td> <td class="c"> <!--- Copy the actual image node. Since we don't have any special way in which we want to transform this, we can just apply templates to the child nodes which will call our generic copy template. This is actually a good thing since it allows us to have more than just IMG tags in place (example a LINK tag containing an image). ---> <xsl:apply-templates /> </td> <td class="e"> <xsl:call-template name="nbsp" /> </td> </tr> <tr> <td class="sw"> <xsl:call-template name="nbsp" /> </td> <td class="s"> <xsl:call-template name="nbsp" /> </td> <td class="se"> <xsl:call-template name="nbsp" /> </td> </tr> </tbody> </table> </xsl:template> <!--- Create a named-template for easy NBSP output. By default, the text output escapes certain characters that we actually want to render. ---> <xsl:template name="nbsp"> <xsl:text disable-output-escaping="yes"> &nbsp; </xsl:text> <br /> </xsl:template> </xsl:transform> </cfsavecontent> <!--- Include style shee from site. ---> <link rel="stylesheet" type="text/css" href="content.css"></link> <link rel="stylesheet" type="text/css" href="main.css"></link> <!--- Transfor the XHTML. Let's see if this creates an accurate copy of the XHTML. ---> #XMLTransform( Trim( strData ), Trim( strXSLT ) )#
As you can see, the first chunk of data is my "content." This contains several paragraphs of text, one of which contains just an image. The second chunk of data is my XML Transformation code. As I demonstrated earlier today, XSLT's Copy and Copy-Of commands properly copy XHTML data, so I knew that would work. Then, I have a special template match that is looking for paragraphs flagged as "images." Rather than just blindly copying these paragraphs, this specific template intercepts them and outputs the nested IMG tag within a surrounding TABLE tag.
When I run this code above, I get the following output:
Not only is my content data stored in a very straightforward, data-centric way, but I can still achieve the desired, complex image formatting.
I wish I had known more about XSLT when I first started authoring my blog software; I think it would have totally changed the way I store and output my data. XSLT and ColdFusion's XMLTransform() are really great tools for keeping a strong line between the data and the display of that data.
Want to use code from this post? Check out the license.