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:
Sometimes you have to describe complex objects, and then usually you have to use ugly names like:
listbox-width = 640
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();
- Create a new configuration file with
myapp as the root element
XMLConfig xcfg = new XMLConfig();
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
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 '\'.
, you can either use
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;
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!
<height value = "768">
<name value="Show Warning Message Box">
<method value = "warning">
<name value="Show Error Message Box">
<method value = "error">
<name value="Get Random Number">
<method value = "random">
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
Console.Write(xcfg.Setting.GetNamedChildrenCount("plugin")) foreach(ConfigSetting cs in xcfg.Setting.GetNamedChildren("plugin"))
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##"];
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.
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
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.
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!
- 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
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