Welcome to the second in a series concerning configuration files and ColdFusion applications. In the previous article, I discussed how you could use INI files to supply configuration information for a web application. In this article, I'll talk about how you can use XML files instead. Before we begin, let me state that ColdFusion's XML support is pretty strong, with a good set of XML functions available. This post is not intended to teach you about XML. There are whole books on this topic as well as a specific section in the ColdFusion documentation. So, with that in mind, let's take a simple look at how we can use XML to store configuration values.

Let's start by building a simple XML file that mimics the INI file we had in the previous example. First, here is the INI file:

name=Jacob Camden
age=5
rank=Padawan

Here is how I would write it in XML:

<?xml version="1.0"?>
<settings>
   <name>Jacob Camden</name>
   <age>5</age>
   <rank>Padawan</rank>
</settings>

As I said above, I'm not going to make this an introduction to XML. I will assume that the XML above makes sense to you. (One of the strengths of XML is how readable it is, so even if you have never seen XML in your life, I bet you can understand it!) Let's see how we can read this using ColdFusion.

<cfxml variable="xml">
<?xml version="1.0"?>
<settings>
   <name>Jacob Camden</name>
   <age>5</age>
   <rank>Padawan</rank>
</settings>
</cfxml>

<cfdump var="#xml#">

The above code does a few neat things. First, we use the CFXML tag to save a string as an XML object. This is a bit like using CFSAVECONTENT. It takes everything between the beginning and ending CFXML tags and converts it to an XML object. What do we mean by an XML object? ColdFusion allows you to treat XML as type of data structure. This means you can perform certain actions on it - like finding the child tags, counting the children, etcetera. At the same time, you can treat the XML as a simple string. If I add a #xml# to the above code, it will display the exact same text that was between the two CFXML tags. (Although you won't see anything. Like HTML, it will be hidden by the browser. Use the htmlEditFormat() or htmlCodeFormat() functions to display the XML in the browser.)

So - now that we have the XML data, we need to get the settings out of it. ColdFusion allows us to treat the XML data a bit loosely. For example, we can use structKeyExists() on the XML object to see if settings tag lives inside it.

<cfif not structKeyExists(xml,"settings")>
   <cfthrow message="Invalid settings XML file!">
</cfif>

Obviously this should never happen on a production server, but you can't be too careful. Now that we are sure we have a settings section, we need to get all the children and copy them into Application variables. Once again we can use structure functions:

<cfloop item="key" collection="#xml.settings#">
   <cfset application[key] = xml.settings[key].xmlText>
</cfloop>

<cfdump var="#application#">

The variable, xml.settings, is treated like a structure inside the loop. Each item value, which I call key, will refer to an XML node. This node has various keys in it - but the one we are concerned with is "xmlText". This refers to the simple text that exists between each key in the XML packet. This will work fine for our simple settings, which you will see if you run the example code. However, at this point, we really haven't done cool. I mean, sure, we switched to XML, which makes us Buzzword Compliant, but we really haven't gained anything. Let's look at a situation where we can really use the power of XML.

Imagine your application needs to send email. You need an email address, obviously, but you also need to store information about the mail server. Not only that, you have more than one email address to send out, depending on the form in question. Here is the XML we will use:

<?xml version="1.0"?>
<settings>
   <email>
      <emailaddresses>
         <jobsform>hr@foo.com</jobsform>
         <contactform>contact@foo.com</contactform>
      </emailaddresses>
      <mailserver>
         <server>192.168.1.113</server>
         <username>gbush</username>
         <password>imdumberthanrock</password>
      </mailserver>
   </email>
   <name>Jacob Camden</name>
   <age>5</age>
   <rank>Padawan</rank>
</settings>

This is quite a lot more information here now. We have an email section, and under that, an emailaddresses section and a mailserver section. Both of these sections have values under them as well. This could be done in an INI file, but it would have been much more messier. Because I'm using XML, I can nicely organize my settings no matter how complex they get. Now comes the difficult part. How do I translate that XML into a nice set of Application variables? I could write a recursive function to dynamically traverse the XML and load the data. Definitely doable - but maybe a bit of overkill. My application needs to make assumptions. We've already assumed that <settings> will exist. What I'll do next is simply grab my values by hand, and throw an error when one of my required keys doesn't exist.

<cfxml variable="xml">
<?xml version="1.0"?>
<settings>
   <email>
      <emailaddresses>
         <jobsform>hr@foo.com</jobsform>
         <contactform>contact@foo.com</contactform>
      </emailaddresses>
      <mailserver>
         <server>192.168.1.113</server>
         <username>gbush</username>
         <password>imdumberthanrock</password>
      </mailserver>
   </email>
   <name>Jacob Camden</name>
   <age>5</age>
   <rank>Padawan</rank>
</settings>
</cfxml>


<!--- main settings --->
<cfset application.settings = structNew()>

<cfif not structKeyExists(xml,"settings")>
   <cfthrow message="Invalid settings XML file!">
</cfif>

<cfloop item="key" collection="#xml.settings#">
   <cfif len(trim(xml.settings[key].xmlText))>
      <cfset application.settings[key] = xml.settings[key].xmlText>
   </cfif>
</cfloop>

<!--- email addresses --->
<cfset application.settings.emailaddresses = structNew()>

<cfif not structKeyExists(xml.settings,"email") or not structKeyExists(xml.settings.email,"emailaddresses")>
   <cfthrow message="Invalid settings XML file!">
</cfif>

<cfloop item="key" collection="#xml.settings.email.emailaddresses#">
   <cfif len(trim(xml.settings.email.emailaddresses[key].xmlText))>
      <cfset application.settings.emailaddresses[key] = xml.settings.email.emailaddresses[key].xmlText>
   </cfif>
</cfloop>

<!--- mail server --->
<cfset application.settings.mailserver = structNew()>

<cfif not structKeyExists(xml.settings.email,"mailserver")>
   <cfthrow message="Invalid settings XML file!">
</cfif>

<cfloop item="key" collection="#xml.settings.email.mailserver#">
   <cfif len(trim(xml.settings.email.mailserver[key].xmlText))>
      <cfset application.settings.mailserver[key] = xml.settings.email.mailserver[key].xmlText>
   </cfif>
</cfloop>

<cfdump var="#application#">

At this point, our configuration code is getting a bit complex. If you are using ColdFusion MX7, this would be the perfect thing to stuff into a method that is then called by onApplicationStart. The benefit of putting it into another method is that you could also call the method from onRequestStart, perhaps based on the existence of a URL variable (reinit). Plus, if you ever did get around to rewriting the XML parsing, you don't have to change anything outside of the method. You can even go back and forth between INI files and XML files or reading from a database. Abstraction means not having to say your sorry (err, or changing other bits of code).

So - as a homework assignment - write a user-defined function that, given an XML object, will return a CFML structure. Anyone using evaluate() will be flogged with my VB.net book from CFUNITED. I don't have any prizes unfortunately.