On a recent ColdFusion project I had to make a PDF file respond like a traditional web page. You know, add form fields with a submit button, capture the submitted data server-side, and show the new PDF with the data submitted by the user. Even though this Adobe technology has been around a while and I knew it was possible, I had never done it before. To accomplish this, I used several tools:
- Adobe Professional - To add form fields and JavaScript validation
- IText - To populate form fields and flatten the PDF
So began a new coding adventure and, again, I found myself intrigued by this software challenge. I created a simple PDF, added JavaScript validation via Adobe Professional, and then began playing around with how submission works. I discovered a few differences from a traditional HTML page, but that's another post.
I set the PDF form submit options to submit form field data as XML. I quickly discovered I had to "loop through" the PDF form fields in an XML object. However, I wanted the XML data converted into a ColdFusion structure.
Q: Surely someone has already written a function like this right?
A: Nope. Or at least I didn't find anyone doing it.
Fortunately, I did find something that helped me refactor a solution. On www.cflib.org I found a flattenStruct function. It recursively traversed through a structure and created a new structure, a flatten structure.
From this, I created a new function, flattenXmlToStruct.
<cffunction name="flattenXmlToStruct" access="public" output="false" returntype="struct">
<cfargument name="xmlObject" required="true" type="xml" />
<cfargument name="delimiter" required="false" type="string" default="." />
<cfargument name="prefix" required="false" type="string" default="" />
<cfargument name="stResult" required="false" type="struct" />
<cfargument name="addPrefix" required="false" type="boolean" default="true" />
<cfset var sKey = '' />
<cfif NOT isDefined("arguments.stResult")>
<cfset arguments.stResult = structNew()>
</cfif>
<cfloop from="1" to="#ArrayLen(arguments.xmlObject.XmlChildren)#" index="arrIndx">
<cfset sKey = arguments.xmlObject.XmlChildren[arrIndx].XmlName>
<cfif ArrayLen(arguments.xmlObject.XmlChildren[arrIndx].XmlChildren) EQ 0>
<cfif arguments.addPrefix and len(arguments.prefix)>
<cfset arguments.stResult["#arguments.prefix##arguments.delimiter##sKey#"] =
arguments.xmlObject.XmlChildren[arrIndx].XmlText />
<cfelse>
<cfset arguments.stResult[sKey] =
arguments.xmlObject.XmlChildren[arrIndx].XmlText />
</cfif>
<cfelse> <!--- Node has more children... --->
<cfif arguments.prefix EQ "">
<cfset currentKey = sKey>
<cfelse>
<cfset currentKey = "#arguments.prefix##arguments.delimiter##sKey#">
</cfif>
<cfset flattenXmlToStruct(arguments.xmlObject.XmlChildren[arrIndx],
arguments.delimiter, currentKey, arguments.stResult) />
</cfif>
</cfloop>
<cfreturn arguments.stResult />
</cffunction>