Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Guust Nieuwenhuis
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Guust Nieuwenhuis

Printing The Entire jQuery API As A PDF (Using CFDocument And XML Parsing)

By
Published in , Comments (17)

I am just starting to really get into jQuery, the JavaScript API that makes writing sweet-ass JavaScript easy and fun. As with ColdFusion, I like to really dive into it by plowing through the documentation (yeah, I actually do like reading documentation so I can see what the possibilities are!). jQuery.com and VisualjQuery.com provide great documentation and visual representations of the API, but I wanted something that I could print out and take with me on the train. I am sure that I could find something somewhere, but seeing as VisualjQuery.com runs off of an XML API definition file, I thought I would give it a go with ColdFusion XML and PDF generation (what else would I do for fun???).

The first step was downloading the XML file for the documentation. I got my XML file from Visual jQuery:

http://visualjquery.com/index.xml

Once I had that, it was just a matter of parsing it in with CFXML and outputting a formatting HTML document (that could then be consumed by CFDocument). Ok, maybe that was not quite as easy as it sounds. For one thing, it took me a while to figure out all the little nuances of the XML document (such as what the heck the SEE node was). After a few hours of trial an error, I finally came up with something that was "good enough":

<!---
	Create a ColdFusion XML document based on the jQuery XML
	documentation available at:

	http://visualjquery.com/index.xml
--->
<cfxml variable="xmlDoc">
	<cfinclude template="./documentation.xml" />
</cfxml>


<!---
	Get the root XML node of the XML document. This will
	contain all the categories for the documentation.
--->
<cfset xmlRoot = XmlDoc.XmlRoot />


<!--- Output basic CSS for the document. --->
<style type="text/css">

	body {
		font: 11px verdana ;
		}

	h2.categoryname {
		border-bottom: 2px solid #333333 ;
		font-size: 24px ;
		}

	h2.subcategoryname {
		border-bottom: 1px solid #333333 ;
		font-size: 18px ;
		}

	h3 {
		border-bottom: 1px solid #999999 ;
		font-size: 14px ;
		padding-bottom: 3px ;
		padding-top: 15px ;
		}

	h4 {
		font-style: italic ;
		margin: 0px 0px 5px 0px ;
		}

	h5 {
		font-size: 10px ;
		margin: 0px 0px 5px 0px ;
		}

	p,
	ol,
	ul {
		margin: 0px 0px 12px 0px ;
		}

	div.methodbody {
		margin-left: 20px ;
		}

	div.indent {
		margin-left: 20px ;
		}

</style>


<cfoutput>

	<h1>
		jQuery API Documentation
	</h1>

	<!---
		Get the sections/categories that the documentation is
		broken up into. Be careful; the documentation has nested
		categories. By doing a // search, we will be finding all
		categories regardless of nesting.
	--->
	<cfset arrCategories = XmlSearch( xmlRoot, "//cat" ) />


	<!--- Loop over all the sections / categories in the documentation. --->
	<cfloop
		index="intCategory"
		from="1"
		to="#ArrayLen( arrCategories )#"
		step="1">

		<!--- Get category short hand. --->
		<cfset xmlCategory = arrCategories[ intCategory ] />

		<!--- Get methods for this category. --->
		<cfset arrMethods = XmlSearch( xmlCategory, "./method" ) />

		<!---
			Get all the sub categories. Remember, the documentation
			has categories within categories (at times).
		--->
		<cfset arrSubCategories = XmlSearch( xmlCategory, "./cat" ) />


		<!---
			Check to see if we are in a sub category. We will
			know this is true if the node name of the current
			node matches the node name of the parent node.
		--->
		<cfif (xmlCategory.XmlParent.XmlName EQ xmlCategory.XmlName)>

			<!--- We are in a sub category. --->
			<cfset blnSubCategory = true />

		<cfelse>

			<!--- We are NOT in a sub category. --->
			<cfset blnSubCategory = false />

		</cfif>


		<!--- Output the category name. --->
		<h2 class="<cfif blnSubCategory>sub</cfif>categoryname">
			#xmlCategory.XmlAttributes.Value#
		</h2>


		<!--- Loop over methods. --->
		<cfloop
			index="intMethod"
			from="1"
			to="#ArrayLen( arrMethods )#"
			step="1">

			<!--- Get method short hand. --->
			<cfset xmlMethod = arrMethods[ intMethod ] />

			<!--- Get method attributes. --->
			<cfset arrDescription = XmlSearch( xmlMethod, "./desc" ) />
			<cfset arrSee = XmlSearch( xmlMethod, "./see" ) />
			<cfset arrParams = XmlSearch( xmlMethod, "./params" ) />
			<cfset arrExamples = XmlSearch( xmlMethod, "./examples" ) />
			<cfset arrBefore = XmlSearch( xmlMethod, "./before" ) />
			<cfset arrCode = XmlSearch( xmlMethod, "./code" ) />
			<cfset arrResult = XmlSearch( xmlMethod, "./result" ) />
			<cfset arrOptions = XmlSearch( xmlMethod, "./options" ) />


			<!--- Ouptut the method name (with param names). --->
			<h3>
				#xmlMethod.XmlAttributes.Name#(

				<!--- Ouput parameters. --->
				<cfloop
					index="intParam"
					from="1"
					to="#ArrayLen( arrParams )#"
					step="1">

					<!--- Check to see if this parameter has a name. --->
					<cfif StructKeyExists( arrParams[ intParam ].XmlAttributes, "Name" )>
						#arrParams[ intParam ].XmlAttributes.Name#
					</cfif>

					<!---
						Check to see if we have more than one parameter.
						We only need a comma for mulitple params.
					--->
					<cfif (intParam LT ArrayLen( arrParams ))>
						,
					</cfif>

				</cfloop>
				)
			</h3>


			<div class="methodbody">

				<!--- Check to see if we have a metho description. --->
				<cfif ArrayLen( arrDescription )>

					<p class="methoddescription">
						#ToString( HtmlEditFormat( arrDescription[ 1 ].XmlText ) ).ReplaceAll(
							"(\r?\n){2,}",
							"</p><p class=""methoddescription"">"
							)#
					</p>

				</cfif>


				<!--- Check to see if we have any parameters. --->
				<cfif ArrayLen( arrParams )>

					<h4>
						Parameters
					</h4>

					<div class="indent">

						<p>
							<cfloop
								index="intParam"
								from="1"
								to="#ArrayLen( arrParams )#"
								step="1">

								<!--- Get a short hand for the parameters. --->
								<cfset xmlParameter = arrParams[ intParam ] />

								<!--- Get description. --->
								<cfset arrParamDescription = XmlSearch( xmlParameter, "./desc" ) />

								<p>

									<cfif StructKeyExists( xmlParameter.XmlAttributes, "Name" )>
										<strong>#xmlParameter.XmlAttributes.Name#</strong>:
									</cfif>

									<cfif StructKeyExists( xmlParameter.XmlAttributes, "Type" )>
										( #xmlParameter.XmlAttributes.Type# ):
									</cfif>

									<cfif ArrayLen( arrParamDescription )>
										#HtmlEditFormat( arrParamDescription[ 1 ].XmlText )#
									</cfif>

								</p>

							</cfloop>
						</p>

					</div>

				</cfif>


				<!--- Check to see if we have any method options. --->
				<cfif ArrayLen( arrOptions )>

					<h4>
						Hash Options
					</h4>

					<div class="indent">

						<!--- Loop over options. --->
						<cfloop
							index="intOption"
							from="1"
							to="#ArrayLen( arrOptions )#"
							step="1">

							<!--- Get a short hand for the option. --->
							<cfset xmlOption = arrOptions[ intOption ] />

							<!--- Get description. --->
							<cfset arrOptionDescription = XmlSearch( xmlOption, "./desc" ) />

							<p>

								<cfif StructKeyExists( xmlOption.XmlAttributes, "Name" )>
									<strong>#xmlOption.XmlAttributes.Name#</strong>:
								</cfif>

								<cfif StructKeyExists( xmlOption.XmlAttributes, "Type" )>
									( #xmlOption.XmlAttributes.Type# ):
								</cfif>

								<cfif ArrayLen( arrOptionDescription )>
									#HtmlEditFormat( arrOptionDescription[ 1 ].XmlText )#
								</cfif>

							</p>

						</cfloop>

					</div>

				</cfif>


				<!--- Check to see if are method has a return type. --->
				<cfif StructKeyExists( xmlMethod.XmlAttributes, "Type" )>

					<h4>
						Returns
					</h4>

					<div class="indent">

						<p>
							#xmlMethod.XmlAttributes.Type#
						</p>

					</div>

				</cfif>


				<!--- Check to see if we have any examples. --->
				<cfif ArrayLen( arrExamples )>

					<!--- Loop over examples. --->
					<cfloop
						index="intExample"
						from="1"
						to="#ArrayLen( arrExamples )#"
						step="1">

						<!--- Get short hand to example. --->
						<cfset xmlExample = arrExamples[ intExample ] />

						<!--- Get the example attributes. --->
						<cfset arrExampleDesc = XmlSearch( xmlExample, "./desc" ) />
						<cfset arrExampleBefore = XmlSearch( xmlExample, "./before" ) />
						<cfset arrExampleCode = XmlSearch( xmlExample, "./code" ) />
						<cfset arrExampleResult = XmlSearch( xmlExample, "./result" ) />

						<h4>
							Example
						</h4>

						<div class="indent">

							<cfif ArrayLen( arrExampleDesc )>

								<p>
									#HtmlEditFormat( arrExampleDesc[ 1 ].XmlText )#
								</p>

							</cfif>

							<cfif ArrayLen( arrExampleCode )>

								<p>
									#HtmlEditFormat( arrExampleCode[ 1 ].XmlText )#
								</p>

							</cfif>

							<cfif ArrayLen( arrExampleBefore )>

								<h5>
									Before:
								</h5>

								<p>
									#HtmlEditFormat( arrExampleBefore[ 1 ].XmlText )#
								</p>

							</cfif>

							<cfif ArrayLen( arrExampleResult )>

								<h5>
									Result:
								</h5>

								<p>
									#HtmlEditFormat( arrExampleResult[ 1 ].XmlText )#
								</p>

							</cfif>

						</div>

					</cfloop>

				</cfif>


				<!--- Check to see if we have any "See Also" examples. --->
				<cfif ArrayLen( arrSee )>

					<h4>
						See Also
					</h4>

					<div class="indent">

						<p>
							<cfloop
								index="intSee"
								from="1"
								to="#ArrayLen( arrSee )#"
								step="1">

								#arrSee[ intSee ].XmlText#<br />

							</cfloop>
						</p>

					</div>

				</cfif>

			</div>

		</cfloop>

	</cfloop>

</cfoutput>

I didn't comment the above code that much as I wasn't really that interested in demoing the code. I just wanted to get it working so that I could get my PDF going. Now, once I had that code up and running and producing a nice HTML document, I used the CFDocument to generate that PDF. Assuming the above code was stored in a file named, "parse.cfm", generating the PDF was a sinch!

<!--- 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="./parse.cfm" />

</cfdocument>

And that's that! If you want to see the PDF, feel free to download it here.

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

Reader Comments

28 Comments

Thanks for doing this. My one complaint about that visualjquery site is the lack of a search. A PDF will fix that.

76 Comments

Good work mate, I have been using jQuery for ages and wouldn't use anything else - it is so good. This is really handy as an alterative to the online API documentation.

15,848 Comments

@Tony,

Those cheat sheets look awesome! Thanks for pointing me (and everyone else) to those. Once I get through the documentation and get a real taste of everything that is possible, the cheat sheets will provide an invaluable quick look up!

@Rey,

My pleasure dude, jQuery just rocks :)

15,848 Comments

@Sam,

When I wrote this post, I was just learning jQuery and I needed something that I could print out and bring home with me; I don't have internet at home, so hand-held documentation is very useful.

It was also an exploration of the use of XML on a large scale document. This later became an experimentation in XSLT:

www.bennadel.com/index.cfm?dax=blog:961.view

But yes, I agree, getting the documentation from the live site is the most useful.

1 Comments

This is a huge help, thanks. I think it's a terrible oversight to not have a downloadable version of the docs!

1 Comments

This may be slighly off topic but... Is there any way this could parse a dynamic page? For example pass it something like htmlpage.aspx?i=1. Where i is an id used in the .net page to create a dynamic html webpage? Thanks!

15,848 Comments

@Mike,

Because the jQuery only gets the data that is delivered by the server, it is shielded from the inner workings of the page creation. You could have the page as dynamic as you want.

1 Comments

Ben,

Right that is the trick. If I do this:

<cfdocument format="pdf" localUrl="yes"
src="http://report1.aspx?i=1366" />

An error: ERROR encountered loading http://report1.aspx?i=1366: java.io.IOException: Server returned HTTP response code: 500 for URL: http://report1.aspx?i=1366 occured during the rendering process of this document.

How do you actually invoke jQuery to return the html generated from http://report1.aspx?

15,848 Comments

@Mike,

Ahhh, you can't really do that since the CFDocument doesn't load the target HTML in a browser; as such, the Javascript never has a chance to execute.

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