I'm having difficulty doing this seemingly simple task. I want to load XML files with the same ease of loading art assets:
content = new ContentManager(Services);
content.RootDirectory = "Content";
Texture2d background = content.Load<Texture2D>("images\\ice");
I'm not sure how to do this. This tutorial seems helpful, but how do I get a StorageDevice
instance?
I do have something working now, but it feels pretty hacky:
public IDictionary<string, string> Get(string typeName)
{
IDictionary<String, String> result = new Dictionary<String, String>();
xmlReader.Read(); // get past the XML declaration
string element = null;
string text = null;
while (xmlReader.Read())
{
switch (xmlReader.NodeType)
{
case XmlNodeType.Element:
element = xmlReader.Name;
break;
case XmlNodeType.Text:
text = xmlReader.Value;
break;
}
if (text != null && element != null)
{
result[element] = text;
text = null;
element = null;
}
}
return result;
}
I apply this to the following XML file:
<?xml version="1.0" encoding="utf-8" ?>
<zombies>
<zombie>
<health>100</health>
<positionX>23</positionX>
<positionY>12</positionY>
<speed>2</speed>
</zombie>
</zombies>
And it is able to pass this unit test:
internal virtual IPersistentState CreateIPersistentState(string fullpath)
{
IPersistentState target = new ReadWriteXML(File.Open(fullpath, FileMode.Open));
return target;
}
/// <summary>
///A test for Get with one zombie.
///</summary>
//[TestMethod()]
public void SimpleGetTest()
{
string fullPath = "C:\\pathTo\\Data\\SavedZombies.xml";
IPersistentState target = CreateIPersistentState(fullPath);
string typeName = "zombie";
IDictionary<string, string> expected = new Dictionary<string, string>();
expected["health"] = "100";
expected["positionX"] = "23";
expected["positionY"] = "12";
expected["speed"] = "2";
IDictionary<string, string> actual = target.Get(typeName);
foreach (KeyValuePair<string, string> entry in expected)
{
Assert.AreEqual(entry.Value, expected[entry.Key]);
}
}
Downsides to the current approach: file loading is done poorly, and matching keys to values seems like it's way more effort than necessary. Also, I suspect this approach would fall apart with more than one entry in the XML.
I can't imagine that this is the optimal implementation.
UPDATE: Following the advice of @Peter Lillevold, I've changed this a bit:
public IDictionary<string, string> Get(string typeName)
{
IDictionary<String, String> result = new Dictionary<String, String>();
IEnumerable<XElement> zombieValues = root.Element(@typeName).Elements();
//result["health"] = zombie.Element("health").ToString();
IDictionary<string, XElement> nameToElement = zombieValues.ToDictionary(element => element.Name.ToString());
foreach (KeyValuePair<string, XElement> entry in nameToElement)
{
result[entry.Key] = entry.Value.FirstNode.ToString();
}
return result;
}
public ReadWriteXML(string uri)
{
root = XElement.Load(uri);
}
internal virtual IPersistentState CreateIPersistentState(string fullpath)
{
return new ReadWriteXML(fullpath);
}
/// <summary>
///A test for Get with one zombie.
///</summary>
[TestMethod()]
public void SimpleGetTest()
{
IPersistentState target = CreateIPersistentState("../../../path/Data/SavedZombies.xml");
string typeName = "zombie";
IDictionary<string, string> expected = new Dictionary<string, string>();
expected["health"] = "100";
expected["positionX"] = "23";
expected["positionY"] = "12";
expected["speed"] = "2";
IDictionary<string, string> actual = target.Get(typeName);
foreach (KeyValuePair<string, string> entry in expected)
{
Assert.AreEqual(entry.Value, actual[entry.Key]);
}
}
The loading is still pretty crappy, and somehow I wasn't able to get the one-line ToDictionary
to work with those two lambdas. I had to resort to that foreach loop. What am I doing wrong there?
There is also the new and shiny XElement (which sports Linq to XML). This sample will load an xml file, look up the zombie and dump the values into a dictionary:
var doc = XElement.Load("filename");
var zombieValues = doc.Element("zombie").Elements();
var zombieDictionary =
zombieValues.ToDictionary(
element => element.Name.ToString(),
element => element.Value);
If you'd rather pick each value explicitly (and use casting for automatically converting into proper value types) you can do:
var zombie = doc.Element("zombie");
var health = (int)zombie.Element("health");
var positionX = (int)zombie.Element("positionX");
var positionY = (int)zombie.Element("positionY");
var speed = (int)zombie.Element("speed");
Update: fixing some typos and cleaning up a bit, your Get
method should look like this:
public IDictionary<string, string> Get(string typeName)
{
var zombie = root.Element(typeName);
return zombie.Elements()
.ToDictionary(
element => element.Name.ToString(),
element => element.Value);
}
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.LoadXml(xmlString);
string health = doc["zombies"]["zombie"]["health"].InnerText;
// etc..
// or looping
foreach( XmlNode node in doc["zombies"].ChildNodes )
{
string health = node["health"].InnerText;
// etc...
}
Or does that not work in XNA?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With