XNA: лучший способ загрузить и прочитать файл XML?

У меня возникли трудности с выполнением этой, казалось бы, простой задачи. Я хочу загружать XML-файлы так же просто, как загружать художественные ресурсы:

        content  = new ContentManager(Services);
        content.RootDirectory = "Content";
        Texture2d background = content.Load<Texture2D>("images\\ice");

Я не знаю, как это сделать. Это руководство кажется полезным, но как я получу экземпляр StorageDevice?

У меня сейчас что-то работает, но это выглядит довольно хакерским:

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;
        }

Я применяю это к следующему файлу XML:

<?xml version="1.0" encoding="utf-8" ?>
<zombies>
  <zombie>
    <health>100</health>
    <positionX>23</positionX>
    <positionY>12</positionY>
    <speed>2</speed>
  </zombie>
</zombies>

И он может пройти этот модульный тест:

    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]);
        }
    }

Недостатки текущего подхода: загрузка файлов выполняется плохо, и кажется, что сопоставление ключей со значениями требует больше усилий, чем необходимо. Кроме того, я подозреваю, что этот подход развалится, если в XML будет более одной записи.

Я не могу представить, что это оптимальная реализация.

ОБНОВЛЕНИЕ: следуя совету @Peter Lillevold, я немного изменил это:

    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]);
        }
    }

Загрузка все еще довольно дерьмовая, и почему-то мне не удалось заставить однострочный ToDictionary работать с этими двумя лямбда-выражениями. Мне пришлось прибегнуть к этому циклу foreach. Что я там делаю не так?


person Nick Heiner    schedule 13.03.2010    source источник
comment
У вас там опечатка. @ из моего примера можно использовать только со строковыми литералами, но не со строковыми переменными или параметрами.   -  person Peter Lillevold    schedule 14.03.2010
comment
Вы понимаете, что конвейер контента XNA УЖЕ поддерживает XML-файлы, не так ли? Таким образом, вы можете буквально использовать тот же синтаксис, который вы используете для загрузки художественных файлов, для загрузки файлов XML.   -  person BlueRaja - Danny Pflughoeft    schedule 05.07.2011


Ответы (2)


Существует также новый и блестящий XElement ( который поддерживает Linq to XML). Этот пример загрузит файл xml, найдет зомби и выгрузит значения в словарь:

var doc = XElement.Load("filename");
var zombieValues = doc.Element("zombie").Elements();
var zombieDictionary = 
    zombieValues.ToDictionary(
        element => element.Name.ToString(), 
        element => element.Value);

Если вы предпочитаете выбирать каждое значение явно (и использовать приведение для автоматического преобразования в правильные типы значений), вы можете сделать:

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");

Обновление: исправление некоторых опечаток и небольшая подчистка, ваш метод Get должен выглядеть так:

public IDictionary<string, string> Get(string typeName)
{
    var zombie = root.Element(typeName);
    return zombie.Elements()
          .ToDictionary(
                  element => element.Name.ToString(),
                  element => element.Value);
}
person Peter Lillevold    schedule 14.03.2010
comment
И в чем смысл символа @ перед зомби? - person Nick Heiner; 14.03.2010
comment
Кроме того, как я могу загрузить файл более элегантно, чем uri? (что-то вроде конвейера контента?) - person Nick Heiner; 14.03.2010
comment
Извините, @ здесь не нужно. Он используется перед строками, когда строка должна быть дословной. Вы можете посмотреть на создание собственного обработчика конвейера для вашего типа файла. Взгляните на это: msdn.microsoft.com/en-us/library/ bb447754.aspx - person Peter Lillevold; 14.03.2010
comment
Я думаю, что один из этих .Elements() вызовов является чрезмерным. Но помимо этого, это делает мой код значительно чище. - person Nick Heiner; 14.03.2010

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...
}

Или это не работает в XNA?

person FallenAvatar    schedule 13.03.2010
comment
Я попробую это. Но как лучше всего получить эту xmlString из файла? - person Nick Heiner; 14.03.2010
comment
Обновил код, потому что допустил ошибку. (doc.Load => doc.LoadXml) - person FallenAvatar; 14.03.2010
comment
Похоже, что XmlDocument не включен в дистрибутив XNA .NET, или, по крайней мере, он жалуется, когда я пытаюсь его использовать. - person Arkiliknam; 16.12.2012