Convert jQuery XML Documentation To HTML / PDF Using ColdFusion And XSLT
A long time ago, I took the jQuery documentation XML off of www.visualjquery.com and converted it into a PDF using a lot of ColdFusion XmlSearch() calls and a ton of ArrayLen() logic. It ended up being a lot of code. Well, now that I have been doing a lot of learning about XSLT in ColdFusion, I thought it would be fun to redo the task using XSL transformations rather than ColdFusion logic.
It turns out, the ColdFusion and XSLT approach is sooo much easier. There's a lot less conditional logic and I think the code just looks a heck of a lot cleaner. I am still not sure where the balance is between using xsl:template vs. using xsl:for-each. At times, they seem to do the same thing so I'm not sure if there is a best practice as to which one to use.
Before we get into the code, you can check out the links below to see this in action:
Convert jQuery XML to XHTML to PDF
I couldn't find the jQuery 1.2 XML documentation, so for this test, I just used the older jQuery 1.1 documentation. Hopefully the format of the XML has not changed and this algorithm should be applicable to the newest documentation as well.
<!---
Read in the jquery XML documentation. Unfortunately, I
had a real hard time trying to find the most up-to-date
XML docs, so I had to go with the quite outdated 1.1.
--->
<cffile
action="read"
file="#ExpandPath( './jquery-1.1.xml' )#"
variable="xmlDocumentation"
/>
<!---
Create the XSLT document that will transform out jQuery
XML into an XHTML web page.
--->
<cfxml variable="xmlTransform">
<!--- 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">
<xsl:output
omit-xml-declaration="yes"
method="xml"
version="1.0"
encoding="UTF-8"
doctype-public="-//W3C//DTD XHTML 1.1//EN"
doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
indent="yes"
/>
<!--- Match the root-level node. --->
<xsl:template match="/">
<html>
<head>
<title>jQuery Documentation</title>
<cfinclude template="jquery_stylesheet.txt" />
</head>
<body>
<h1>
jQuery 1.1 Documentation
</h1>
<!---
Apply templates to direct descendents of the
root node (should be the CAT nodes).
--->
<xsl:apply-templates />
</body>
</html>
</xsl:template>
<xsl:template match="cat">
<h2>
<!---
Check to see if this category is a
sub-category. If so, we have to add the
conditional class attribute.
--->
<xsl:if test="../../cat">
<xsl:attribute name="class">
<xsl:text>sub</xsl:text>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="@value" />
</h2>
<!---
Apply templates to all direct descendents of the
current category node.
--->
<xsl:apply-templates />
</xsl:template>
<xsl:template match="method">
<h3>
<xsl:value-of select="@name" />
<!--- Output the named params. --->
<span class="params">
<xsl:text>( </xsl:text>
<xsl:for-each select="params">
<xsl:value-of select="@name" />
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text> )</xsl:text>
</span>
</h3>
<div class="methodbody">
<xsl:apply-templates select="desc" />
<xsl:if test="params">
<h4>
<xsl:text>Parameterss</xsl:text>
</h4>
<xsl:apply-templates select="params" />
</xsl:if>
<xsl:if test="options">
<h4>
<xsl:text>Hash Options</xsl:text>
</h4>
<xsl:apply-templates select="options" />
</xsl:if>
<xsl:if test="@type">
<h4>
<xsl:text>Returns</xsl:text>
</h4>
<p>
<xsl:value-of select="@type" />
</p>
</xsl:if>
<xsl:apply-templates select="examples" />
<xsl:if test="see">
<h4>
<xsl:text>See Also</xsl:text>
</h4>
<p>
<xsl:for-each select="see">
<xsl:value-of select="." />
<br />
</xsl:for-each>
</p>
</xsl:if>
</div>
</xsl:template>
<!---
This template will match params or options
which are formatted in the same way.
--->
<xsl:template match="params|options">
<p>
<xsl:if test="@name != ''">
<strong>
<xsl:value-of select="@name" />
</strong>
<xsl:text>: </xsl:text>
</xsl:if>
<xsl:if test="@type != ''">
<em>
<xsl:value-of select="@type" />
</em>
<xsl:text>: </xsl:text>
</xsl:if>
<xsl:value-of select="." />
</p>
</xsl:template>
<xsl:template match="desc">
<p class="description">
<xsl:value-of select="." />
</p>
</xsl:template>
<xsl:template match="examples">
<h4>
<xsl:text>Example</xsl:text>
</h4>
<xsl:if test="desc">
<p>
<xsl:value-of select="desc" />
</p>
</xsl:if>
<xsl:if test="code">
<p>
<xsl:value-of select="code" />
</p>
</xsl:if>
<xsl:if test="before">
<h5>
<xsl:text>Before</xsl:text>
</h5>
<p>
<xsl:value-of select="before" />
</p>
</xsl:if>
<xsl:if test="result">
<h5>
<xsl:text>Result</xsl:text>
</h5>
<p>
<xsl:value-of select="result" />
</p>
</xsl:if>
</xsl:template>
</xsl:transform>
</cfxml>
<!--- Output the jQuery XML documention transformation. --->
<cfset WriteOutput(
XmlTransform(
xmlDocumentation,
xmlTransform
)
) />
The above code takes the XML and transforms it using ColdFusion's XmlTransform() to convert it into an XHTML document. Notice that I am using the xsl:text element around some text literals (ex. h3, h4, h5). This is not required. I am doing this because by using the xsl:text element, the ColdFusion transformation engine reduces the amount of white space that it puts into the resultant XHTML. Minor note, but I thought I would point it out.
Now, we can take that one step further (as I did in my first attempt) and convert that into a PDF using a very simple CFDocument call:
<!--- Create a PDF of the parse jQuery documentation. --->
<cfdocument
format="PDF"
pagetype="letter"
orientation="portrait"
unit="in"
encryption="none"
fontembed="Yes"
backgroundvisible="No">
<!--- Include the parsed jQuery XML. --->
<cfinclude template="./index.cfm" />
</cfdocument>
This XSLT stuff is really growing on me.
Want to use code from this post? Check out the license.
Reader Comments
Keep it up! Another thorough, real-world example.
Though I haven't done a ton of XSLT, in the real-world work I've done with it, using XmlTransform with XSLT has always out-performed nasty structure and array looping, etc. Do you find the same to be true with the performance of ths XSLT example vs. the original?
@Aaron,
It was definitely easier to code, which was cool. As a matter of processing performance, both work super fast. Although it would be curious to run it a bunch of times. Let me see what kind of comparison I can come up with.
Hi Ben!
I know this is a very late response / follow-up question to this post, but is it possible to do the reverse? That is, take an HTML document that contains tabular data and convert / transform it into XML?
fddfaaaaaaaa