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:
age=5
rank=Padawan
Here is how I would write it in XML:
<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.
<?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
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.
<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:
<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:
<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.
<?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.
Archived Comments
I hope you add another part to your series entitled "Using ColdFusion to Configure ColdFusion". I've used INI files, and I've used plenty of XML, but the best way I've found to configure a CF application is with CF itself. It's like XML, only smarter. :)
Christian
It may appear smarter by it's not cross language friendly. If all of your settings are in cf you cant use those same settings in your php, asp, or perl cgi etc.. applications.
>you cant use those same settings in your php, asp, or perl cgi etc.. applications
Most people don't have those other applications and on the extremely rare occasion that anyone actually needed that, he could rewrite it in 4 minutes.
Of course, if cfml is no better, then might as well use xml.
Ray just thought I'd get your thoughts on two things. First, is this first example better or worse than the second, and if so, why (hopefully they'll show up)?
<emailaddresses>
<jobsform>hr@foo.com</jobsform>
<contactform>contact@foo.com</contactform>
</emailaddresses>
<emailaddresses>
<emailaddress name="jobsform" value="hr@foo.com"/>
<emailaddress name="contactform" value="contact@foo.com"/>
</emailaddresses>
Basically one uses XML text and the other uses XML attributes.
And second, just wanted to point out (you know this but others may not) that you can often make the XML parsing/handling easier by using XPath.
This isn't gonna really add anything to the discussion, but I was all cozied up to my puter last night, had a drink, and was really lookin' forward to this part of the series. I made it to this point:
<username>gbush</username>
<password>imdumberthanrock</password>
and I thought jeeez ... can ya go for like 30 minutes without preaching about Bush? Especially in a programming tutorial?
Kinda soured it a little for me. Just speaking my mind and for myself here ... Yeah I know, I don't *have* to read your blog, etc.. But I like reading it. :)
Will
Will - ok - point taken. Now, I will say I talk politics very rarely on this blog, so it's not like every 30 mins, but it didn't belong in the example. Thanks for the gentle nudge.
I agree with placing settings in XML files, but what about, just using the database. In my case, we use the power of the database for all our setting files. We have our application tables and our setting tables. We include a manager for the settings in all our apps. And basically maintain all our configurations in the database.
No parsing, no file operations. Just queries.
Luis Majano
www.luismajano.com
if the configuration is complex i would use XML but for small applications - i would stick with a ini file as they are alot more intuitive and anybody can read a simple ini file top down.
Thanks Ray! I'm right now in the process of converting .ini files (which aren't unicode friendly and causing me troubles) to XML. Great timing!
I've also noticed that BlueDragon seems to have a problem with the get/setprofilestring, strangely.
Hey Brain, I'm just now getting around to answering your email. The honest answer is - I don't know what is right. My _gut_ tells me the first way is right, since I don't see the email address an an "attribute", but more of a value. I don't think that is very clear. Here is an example... if I wanted o specify html or plain text, I'd use an attribute instead. That feels like maybe it is more appropriate.