Search code examples
c#xmlixmlserializable

IXmlSerializable list item


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

Solution

  • 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).