2

For fun, I'm trying to develop a simple RPG. I would like my game to use XML files in order to make the game easily customizable by players.

I got a race class and a raceManager class with a static list of all races. I started by using XmlAttributs (XmlRoot, XmlElement...) and the default serializer to write the playable races to Xml.

XmlSerializer xs = new XmlSerializer(managedList.GetType());
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
    xs.Serialize(fs, managedList);
}

It works but in my race class, I have lists for skills, weapons usable... The default serializer generate arrays containing all the properties of these classes.

As I only want a single instance of each object, I'm starting to implement the IXmlSerializable interface to write only id and retrieve object when i read.

Now the Xml file is almost like I wanted :

<?xml version="1.0"?>
<ArrayOfRace xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Race>
    <NameMale>Human</NameMale>
    ...
    <ArmorsTypesAllowed>
      <ArmorType>Cloth</ArmorType>
      <ArmorType>Leather</ArmorType>
      <ArmorType>Metal</ArmorType>
    </ArmorsTypesAllowed>
  </Race>
  <Race>
    <NameMale>Dwarf</NameMale>
    ...
  </Race>

...

But I can no longer read it :'(

I use my manager method (which worked fine before):

protected static List<T> ReadFile(string filePath)
{
    List<T> ManagedList = new List<T>();
    XmlSerializer xs = new XmlSerializer(ManagedList.GetType());
    using (FileStream fs = new FileStream(filePath, FileMode.Open))
    {
        ManagedList = (List<T>)xs.Deserialize(fs);
    }
    return ManagedList;
}

The ReadXml method is used but return a list of 1 element (the last one). Looks like the whole file (contaning the list) is send to my ReadXml which is suppose to read only one element of that list.

public void ReadXml(XmlReader reader)
{
    while(!reader.EOF)
    {
        if(reader.NodeType == XmlNodeType.Element)
        {
            switch(reader.Name)
            {
                case "NameMale":
                    NameMale = reader.ReadElementContentAsString();
                    break;
                case "NameFemale":
                    NameFemale = reader.ReadElementContentAsString();
                    break;
                    ...
                default:
                    reader.Read();
                    break;
            }
        }
        else
        {
            reader.Read();
        }       
    }
}

I thought I should perhaps stop the reader. So I added the following code but it didn't solve my problem (only the first race is retrieve with this)

if(reader.NodeType == XmlNodeType.EndElement && reader.Name == "Race")
{
    return;
}

Thank you in advance and sorry for my poor English.

Edit : Here is a minimal but complete example with a console app :

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace StackOverFlowQuestion
{
    class Program
    {
        static void Main(string[] args)
        {
            if (!File.Exists(Manager.FilePath))
            {
                Manager.WriteFile(Manager.FilePath, Manager.GenerateMyClass());
            }

            Manager.Initialize();
            foreach (MyClass mc in Manager.ListOfMyClass)
            {
                Console.WriteLine("A:" + mc.A);
                Console.WriteLine("B:" + mc.B);
                foreach(AnotherClass ac in mc.MyList)
                {
                    Console.WriteLine("Item ID: " + ac.Id);
                }
            }
            Console.ReadLine();
        }
    }


    public class MyClass : IXmlSerializable
    {
        public string A { get; set; }
        public string B { get; set; }
        public List<AnotherClass> MyList { get; set; }

        public MyClass() { }

        public MyClass(string a, string b, List<AnotherClass> list)
        {
            this.A = a;
            this.B = b;
            this.MyList = list;
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            while (!reader.EOF)
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "A":
                            A = reader.ReadElementContentAsString();
                            break;
                        case "B":
                            B = reader.ReadElementContentAsString();
                            break;
                        case "MyList":
                            MyList = new List<AnotherClass>();
                            reader.Read();
                            break;
                        case "ListItem":
                            string s = reader.ReadElementContentAsString();
                            MyList.Add(Manager.ListOfAnotherClass.Single(x => x.Id == s));
                            break;
                        default:
                            reader.Read();
                            break;
                    }
                }
                else
                {
                    reader.Read();
                }

            }
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteElementString("A", A);
            writer.WriteElementString("B", B);

            writer.WriteComment("Only Id must be serialize !");
            writer.WriteStartElement("MyList");
            if (MyList != null)
            {
                foreach (AnotherClass ac in MyList)
                {
                    writer.WriteElementString("ListItem", ac.Id);
                }
            }
            writer.WriteEndElement();
        }
    }

    public class AnotherClass
    {
        public string Id { get; set; }
        public string someProperty { get; set; }

        public AnotherClass(string id, string prop)
        {
            this.Id = id;
            this.someProperty = prop;
        }
    }


    class Manager
    {
        public static List<MyClass> ListOfMyClass { get; set; }
        public static string FilePath = "MyXmlFile.xml";

        //This list should be from another XML file.
        private static List<AnotherClass> _listofAnotherClass;
        public static List<AnotherClass> ListOfAnotherClass
        {
            get
            {
                if (_listofAnotherClass == null)
                {
                    _listofAnotherClass = GenerateAnotherClass();
                }
                return _listofAnotherClass;
            }
        }

        public static void Initialize()
        {
            ListOfMyClass = ReadFile(Manager.FilePath);
        }

        public static List<MyClass> ReadFile(string filePath)
        {
            List<MyClass> ManagedList = new List<MyClass>();
            XmlSerializer xs = new XmlSerializer(ManagedList.GetType());
            using (FileStream fs = new FileStream(filePath, FileMode.Open))
            {
                ManagedList = (List<MyClass>)xs.Deserialize(fs);
            }
            return ManagedList;
        }


        public static void WriteFile(string filePath, List<MyClass> managedList)
        {
            XmlSerializer xs = new XmlSerializer(managedList.GetType());
            using (FileStream fs = new FileStream(filePath, FileMode.Create))
            {
                xs.Serialize(fs, managedList);
            }
        }

        public static List<MyClass> GenerateMyClass()
        {
            List<MyClass> list = new List<MyClass>();
            MyClass m;

            m = new MyClass("ValueA", "ValueB", new List<AnotherClass>(Manager.ListOfAnotherClass.Where(x => x.Id == "Id1" || x.Id == "Id3")));
            list.Add(m);

            m = new MyClass("ValA", "ValB", new List<AnotherClass>(Manager.ListOfAnotherClass.Where(x => x.Id == "Id2" || x.Id == "Id3")));
            list.Add(m);

            return list;
        }

        public static List<AnotherClass> GenerateAnotherClass()
        {
            List<AnotherClass> anotherList = new List<AnotherClass>();
            anotherList.Add(new AnotherClass("Id1", "Prop1"));
            anotherList.Add(new AnotherClass("Id2", "Prop2"));
            anotherList.Add(new AnotherClass("Id3", "Prop3"));
            return anotherList;
        }
    }
}
  • Your question is quite complicated but is also incomplete in that many classes are missing. Can you boil it down to a [minimal but complete example](http://stackoverflow.com/help/mcve) of the problem that we could copy and paste into Visual Studio and test? – dbc Mar 05 '16 at 18:06
  • If you are trying to edit specific node(s) in an XML file without loading or writing the entire file, then XML isn't really designed for that. See maybe [In C#, is there a way to add an XML node to a file on disk WITHOUT loading it first?](https://stackoverflow.com/questions/4773564/) or maybe http://blogs.msdn.com/b/mfussell/archive/2005/02/12/371546.aspx – dbc Mar 05 '16 at 18:06
  • I edited my question with a minimal but complete example. I'm not trying to edit a node, I would like to understand why after implementing the IXmlSerializable interface, writing a list work but reading it return only 1 element. – Vanbrabant Thomas Mar 06 '16 at 21:10
  • It looks like you already solved your problem, so I won't try to debug it. You are correct that if your `WriteXml()` reads too many or too few elements, it will mess up subsequent deserialization because the `XmlReader` is mis-positioned. Check out http://www.codeproject.com/Articles/43237/How-to-Implement-IXmlSerializable-Correctly and https://stackoverflow.com/questions/279534/proper-way-to-implement-ixmlserializable for guides how to do it correctly. – dbc Mar 07 '16 at 17:55

1 Answers1

1

Finally, I found my mistake. In fact, only one reader is used for the entire list. If this is not at the right position at the end of the reading of the object, the reading stops without throwing exceptions.

I modified my loop with :

while(reader.NodeType != XmlNodeType.EndElement || reader.Name != "MyClass")

At the end of that loop, I added reader.Read(); to skip the </MyClass> and be at the start of the next element in the list (or the end of the file).