Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Convert jQuery XML Documentation To HTML / PDF Using ColdFusion And XSLT

By Ben Nadel on
Tags: ColdFusion

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

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.



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?