Sunday, February 20, 2011

ColdFusion: Flatten an XML object to a Struct

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>

No comments: