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