Showing posts with label config data. Show all posts
Showing posts with label config data. Show all posts

Wednesday, September 16, 2009

Loading XML data from a config file using XmlDocument

Part 1: where to save application config data under Vista
Part 2: how to save data in an XML file using XmlDocument
Part 3: this part, how to load (parse) an XML file using XmlDocument

All code samples in C#, cuz it's my drug of choice.

In the previous two parts, I covered where to save data to, and provided some sample code for creating an XmlDocument that can then be written to disk. The idea here is loading and saving application config data. For games, stuff like the user's preferred screen resolution -- which is the specific purpose I had when I dug up this code.

So let me just get straight to the code. That's why you're here, right?
const string kIntId = "ints";
const string kStrId = "strs";
const string kConfigFile = "config.xml";
private void LoadData()
{
string myAppFile = GetConfigPath() + "/" + kConfigFile;
if (!File.Exists(myAppFile))
return;

try
{
XmlDocument doc = new XmlDocument();
doc.Load(myAppFile);
XmlNode root = doc.DocumentElement;

XmlNode intsNode = root.SelectSingleNode(kIntId);
foreach (XmlNode child in intsNode.ChildNodes)
{
string key = child.LocalName;
int value = Convert.ToInt32(child.Attributes["value"].Value);
_intList[key] = value;
}

XmlNode strsNode = root.SelectSingleNode(kStrId);
foreach (XmlNode child in strsNode.ChildNodes)
{
string key = child.LocalName;
string value = child.Attributes["value"].Value;
_stringList[key] = value;
}
}
catch
{
// feh
}
}
The try/catch is there cuz you should be worried about your users being dumbasses and manually editing your config files. And/or you being a dumbass and screwing it up. Cuz that's what I did. Plus, when I changed formats, some of this stopped working.

Note that I'm using a couple constants to specify the names of the groups that I'm looking for. I do that because of Once and Only Once: mostly to keep myself from mistyping data.

I covered a way to obtain the name for the directory in which to store application data back in part 1, but here's the relevant snippet here:
private static string GetConfigPath()
{
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string myAppData = appData + "/MyAppName";
return myAppData;
}
This is for per-user app config data, as opposed to shared (common) config data -- both described back there in part 1.

How to save config data into an XML file

Part 1: where to save application config data
Part 2: this part, how to save data in an XML file (using XmlDocument)
Part 3: how to load (parse) an XML file

There are many different ways of working with XML files under .NET. You can hand-roll your own code, use XmlReader, use XmlDocument, use someone's third-party library, and who knows what else. I find using XmlDocument the most sensible approach -- why write my own code? I'll let someone else worry about that.

I'll just dump the code here:
public void SaveData()
{
XmlDocument doc = CreateSaveDoc();
string myAppPath = GetConfigPath();
string myAppFile = myAppPath + "/" + kConfigFile;
if (!File.Exists(myAppFile))
{
Directory.CreateDirectory(myAppPath);
}
doc.Save(myAppFile);
}

private XmlDocument CreateSaveDoc()
{
XmlDocument doc = new XmlDocument();
XmlElement root = doc.CreateElement("root");
XmlElement ints = doc.CreateElement(kIntId);
int idNum = 0;
foreach (KeyValuePair kvp in _intList)
{
string id = "int" + idNum.ToString();
++idNum;
XmlElement node = doc.CreateElement(id);
node.SetAttribute("key", kvp.Key);
node.SetAttribute("value", kvp.Value.ToString());
ints.AppendChild(node);
}
root.AppendChild(ints);
XmlElement strs = doc.CreateElement(kStrId);
int strNum = 0;
foreach (KeyValuePair kvp in _stringList)
{
string id = "str" + strNum.ToString();
++strNum;
XmlElement node = doc.CreateElement(id);
node.SetAttribute("key", kvp.Key);
node.SetAttribute("value", kvp.Value);
strs.AppendChild(node);
}
root.AppendChild(strs);
doc.AppendChild(root);
return doc;
}
I used attributes to store info, and ignored the tag names for the contained data. I think the whole 6bytes part of XML is poopy. Yes, I said it, poopy. Attributes work better for me, cuz then you get XML that looks like:

This could be shortened to:

but, well, I got it working and I stopped caring. If you implement this yourself, feel free to take that extra step.

Hmm, I say that now... ok, I went back and changed my code. This complicates loading a bit, because I now care about tags, but you'll see that in the next section. For completeness, those inner loops are now:
{
string id = kvp.Key;
XmlElement node = doc.CreateElement(id);
node.SetAttribute("value", kvp.Value);
strs.AppendChild(node);
}
Much cleaner!

Saving application config data under Vista

Part 1: this part, where to save application config data
Part 2: how to save config data in an XML file using XmlDocument
Part 3: how to load (parse) XML-based config data using XmlDocument

I was a bit frustrated at finding this info on the net. It required a bunch of searches to pull it all together. I finally got what I needed, but, you know, bitch/moan/whine and all that. So here's everything in one place!

I'm assuming you're coding in C# (or at least can read it), and using .NET. And, like, Windows? Yeah.

So, part 1 of a 3-part series: where do I put my config data?

In the olden days (ie, under XP), you could just create a "config.ini" file in the current directory, ie with no path info, and it would save it in the same location that your application's exe was located. Under Vista and User Access Control (UAC), applications by default do not have write access to the Program Files folder. Plus, that folder might not be on the C: drive, and it might not be called "Program Files". So where do you save stuff now?

The correct location is in the AppData folder for the current user -- or, if you don't want to store user-specific data, in the common AppData folder. You can get these paths using the following code:
string userAppData = Environment.GetFolderPath(
Environment.SpecialFolder.ApplicationData);
string commonAppData = Envrionment.GetFolderPath(
Environment.SpecialFolder.CommonApplicationData);
You can also hunt for the environment variable %AppData% if you want. The above is .NET friendly, so it's what I used.

I encapsulated the above into a function, which I use a couple places in my config save/load code:
private static string GetConfigPath()
{
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string myAppData = appData + "/MyAppName";
return myAppData;
}
Loading it requires the following:
string myAppFile = GetConfigPath() + "/" + kConfigFile;
if (!File.Exists(myAppFile))
return;
XmlDocument doc = new XmlDocument();
doc.Load(myAppFile);
// insert parsing here, see part 3
and saving it requires just a bit more work:
public void SaveData()
{
XmlDocument doc = CreateSaveDoc();
string myAppPath = GetConfigPath();
string myAppFile = myAppPath + "/" + kConfigFile;
if (!File.Exists(myAppFile))
{
Directory.CreateDirectory(myAppPath);
}
doc.Save(myAppFile);
}
In part 2, I cover storing data in an XmlDocument, and in part 3 I cover parsing data back out of an XmlDocument and into some local format.

(Apologies for the poor code formatting. Me and Blogger don't get along.)