Skip to main content
Ben Nadel at Endless Sunshine 2017 (Portland, OR) with: Bryan Stanley and Brian Blocker
Ben Nadel at Endless Sunshine 2017 (Portland, OR) with: Bryan Stanley Brian Blocker ( @brianblocker )

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

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

39 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?

15,377 Comments

@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.

2 Comments

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?

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel