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.

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

Accessing values which have unique names

Accessing values which are not uniquely named

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:

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!

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

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:

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.

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.