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>