Click here to Skip to main content
Click here to Skip to main content

XML configuration files made simple... at last!

By , 14 Jan 2007
Rate this:
Please Sign up or sign in to vote.

Introduction

Well first of all, I think I should start with an introduction. My name is Vandra Akos, and I'm a 12th grade student at High School. I have been programming since I was a kid, well... more or less "programming". I'm only 18 years old, and this is my first article ever, so please help me out if I'm not doing something right, write me to clear out what's a little confusing. Thanks.

In the past, I used Delphi's TIniFile and TMemIniFiles a lot to store my configurations. I really, really liked the way they were designed, easy to use, but they had a small problem: no possibility to nest settings; only sections were defined to group similar settings together, but sometimes that is just not enough.

Usually an .ini file looks like this:

[section1]
option1=value1
option2=value2
option3=value3
[section2]
option1=value1
option2=value2

Sometimes you have to describe complex objects, and then usually you have to use ugly names like:

listbox-width = 640
listbox-item1-child-name="foo"

So when I first heard about XML, and what it is was good for, I decided to make a class similar to these ones. Well, I have to say, I started making the class at least thrice, but surrendered before the mighty complexity of XML. Later, I moved to C#, and I started thinking about XMLConfig again and again. But I couldn't find enough documentation on the subject. But in the end, I gathered enough to hook up the class.

What is this class?

It is similar to accessing .ini files, but this time, settings can be nested. You can simply read and write any value from / to it, without bothering with XML parsing. The configuration file is hierarchical (doh, it's XML!), and any node can contain a value, even those with other children. This could come handy sometimes.

What is a valid XML Config file?

Only two small rules apply:

  • No nodes with two children with the same name. Otherwise, which one to retrieve on xcfg.Settings["foo"]? [This is now possible since v2.0 beta].
  • Only alphanumerical names may be used (numbers and digits). That should be enough. Just not to mess with XPath by any chance, which I didn't look into thoroughly.

When loading and saving the XML files, they are automatically validated, and an exception thrown if they are not valid.

How to construct the XMLConfig object?

You can either load a configuration file from disk, stream, or string, or create a new one from scratch. If you load it from disk, you can specify what should happen if the file does not exist. Create the file, or throw an exception?

  • Create a file from scratch, rootname = "xml"
    XMLConfig xcfg = new XMLConfig();
  • Load a file from disk, create if non-existent
    XMLConfig xcfg = new XMLConfig("config.xml",true);
  • Load a file from disk, throw exception if file does not exist
    XMLConfig xcfg = new XMLConfig("config.xml",false);
  • Loads configuration from an XML string
    XMLConfig xcfg = new XMLConfig();
    xcfg.LoadFromString(xmlstr);
  • Create a new configuration file with myapp as the root element

    XMLConfig xcfg = new XMLConfig();
    xcfg.NewXML("myapp");

Okay, so it's constructed... now... how to use it?

This is broken into three parts

  1. Accessing values which have unique names
  2. Accessing values which are not uniquely named
  3. Removing values

Accessing uniquely named values

The XMLConfig.Settings exposes the root node of the settings, and it will be used to access any setting in the file. Each node is represented by a ConfigSetting object, which contains properties to access its value, and an indexer to access its children. The indexers parameter takes a string, which will be either the child's name to be accessed, or a path, separated either by slashes '/', or backslashes '\'.

For example:

To access screen.window.width, you can either use
  • xcfg.Settings["screen"]["window"]["width"].Value

    or

  • xcfg.Settings["screen/window/width"].Value

The former can be handy to initialize objects by passing them the respective ConfigNode, the latter is shorter, and more comfortable to type in by hand.

Because they are all properties, their use is really simple, as the following example demonstrates:

Xmlconfig xcfg = new Xmlconfig("config.xml",true);
xcfg.Settings["window"]["width"].intValue = 800;
xcfg.Settings["saveonclose"].boolValue = true;
xcfg.Settings["listbox"]["item1"]["name"].Value = "foo";

listbox1.width = xcfg.Settings["listbox/width"].IntValue;
label1.caption = xcfg.Settings["label1"]["caption"].Value;
Myclass.MySubclass.MySubSubclass.Enabled = 
   xcfg.Settings["my1/sub1/sub2/enabled"].boolValue;

Accessing values which are not uniquely named

This can happen when the configuration file contains a list of plug-ins, for example, or a set of display modes. I am going to guide you through using the plug-in example.

I updated the path syntax, so now between the slashes, the correct syntax is "nodename#n", where the "#n" can be omitted, and n specifies the index of the value to return. If omitted, n is 1 by default, so if multiple children exist with the same name, the first one is returned. A special case exists, when n is not an int value, but is "#", so that the path looks like "nodename##". In this case, a new, empty node is created, with the desired name. Actually, ## equals with GetNamedChildrenCount + 1.

Be careful though, accessing nodename#1000000000 will make 1 000 000 000 children, and will probably crash due to insufficient memory!

<xml>
    <startminimized value="False">
  <width value="1024">
  <height value = "768">
  <dummy />
  <plugin>
    <name value="Show Warning Message Box">
    <dllfile value="foo.dll">
    <method value = "warning">
  </plugin>
  <plugin>
    <name value="Show Error Message Box">
    <dllfile value="foo.dll">
    <method value = "error">
  </plugin>
  <plugin>
    <name value="Get Random Number">
    <dllfile value="bar.dll">
        <method value = "random">
  </plugin>
</xml>

There are other methods as well to retrieve children methods with the same name. For example, you can use the ConfigSetting.GetNamedChildren(string) method to get an IList<string> object containing all nodes with the specified name, or use ConfigSetting.GetNamedChildrenCount(string) to only get the number of children with the specific name

xcfg.Settings["plugin#1/name"].Value // = Show Warning Message Box
xcfg.Settings["plugin#3/dllfile"].Value // = bar.dll

Console.Write(xcfg.Setting.GetNamedChildrenCount("plugin")) // 3
foreach(ConfigSetting cs in xcfg.Setting.GetNamedChildren("plugin"))

{
  Console.Write(cs["method"].Value+" ");


} // Outputs "warning error random"

That was about reading / modifying already existent data. But what about creating another node with the specific name? Think about that "##" I have mentioned earlier... Yep, we can use that to simply create new nodes with already existing names. So to create another plug-in, we will use the code below:

ConfigSetting newplugin = xcfg.Settings["plugin##"];
// Append a new plugin node

newplugin["name"] = "Show Info Message Box";
newplugin["dllfile"] = "foo.dll";
newplugin["method"] = "information";

I find that really simple, don't you? Well, I hope you do. No need to check if a node exists, create it otherwise. Just access it like it was there. If it wasn't, it will be created automatically. Of course, this can be overridden, and an exception thrown in that case.

Removing values

You can simply use ConfigSetting.CleanUp() to remove any empty nodes. You can also call the method on the XMLConfig object, it will automatically clean up the whole tree. You could also set the XmlConfig.CleanUpOnSave boolean property to true (false, by default), then empty nodes will automatically be stripped from the tree before saving it.

You can remove a node with all its children (not necessarily empty-valued) by calling ConfigSetting.Remove(), or you could use ConfigSetting.RemoveChildren() to remove each and every child, but keep the node itself, and its value.

xcfg.Settings["plugin#1"].Remove(); 
        // Removes 
        //    <plugin>        
        //    <name value="Show Warning Message Box">
        //    <dllfile value="foo.dll">
        //    <method value = "warning">
        //  </plugin>
        // Portion from the XML file
xcfg.Settings["plugin#2"].RemoveChildren();
        // Removes 
        //    <name value="Show Error Message Box">
        //    <dllfile value="foo.dll">
        //    <method value = "error">
        // Portion from the XML file

xcfg.CleanUp();
// Removes any empty nodes from the tree,
// that is the <dummy /> from the example

Okay, created it... modified it... now let's save it!

Erm.... You don't need to. That is, if you loaded it from disk. The config file exposes a CommitOnUnload property, and, if true, calls the Commit() function when loading another file/string or destroying the XMLConfig object. Of course, you could also use the Save(filename); method to save it to disk or Save(stream); method to save it to a stream.

Warning: The Commit() and Reload() methods work only for files loaded from disk, or configurations which were loaded from memory, but already saved with the Save(filename) method to disk!

History

  • Version 2.0 beta - Updated source on 14 Jan 2007
  • Version 2.0 beta - Released on 2007 Jan. 6
    • Added support for multiple children with the same name, some minor changes in the way multiple values are returned (IList instead of arrays).
    • Some of the code is not optimized, there are pretty ugly hacks in there, if you know a simpler and nicer way to do them, please let me know.
  • Version 1.0 - Released on 2006 Dec. 31

Additional info

The compiled DLL contains an object named MD5 with a static hash (string) method also. It can be useful for getting MD5 sums of strings.

All objects are in the VAkos namespace.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

axos88

Romania Romania
I was born in 1988, and have been working at school since I was 7 Smile | :)

Comments and Discussions

 
GeneralGood work Pinmemberfursoft31-Dec-06 21:56 
AnswerRe: Good work Pinmemberaxos881-Jan-07 4:25 
GeneralMinor quirk in article PinmemberBrad Bruce31-Dec-06 5:25 
GeneralRe: Minor quirk in article Pinmemberaxos881-Jan-07 4:02 
GeneralNice PinmemberGreeeg30-Dec-06 5:46 
GeneralRe: Nice Pinmemberaxos8830-Dec-06 5:57 
GeneralRe: Nice Pinmemberalexiev_nikolay3-Jan-07 4:01 
GeneralRe: Nice Pinmemberaxos883-Jan-07 4:10 
Indexers can be overridden? I didn't know that... Smile | :) Thanks
 
Regards,
Vandra Ákos

NewsRe: Nice Pinmemberaxos886-Jan-07 9:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 14 Jan 2007
Article Copyright 2006 by axos88
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid