1

I have the following XML data:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data-set xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <record>
        <LevelNum>0</LevelNum>
        <LevelName>Level 0</LevelName>
        <MaxBubbles>40</MaxBubbles>
        <MaxVisibleBubbles>30</MaxVisibleBubbles>
        <StartingPointValue>11</StartingPointValue>
        <MaxLevelTime>78</MaxLevelTime>
        <LevelPassScore>3000</LevelPassScore>
        <TapsToPopStandard>1</TapsToPopStandard>
        <InitialVisibleBubbles>9</InitialVisibleBubbles>
        <LevelDescription>Level 80</LevelDescription>
        <SeqLinear>1</SeqLinear>
        <SeqEven>0</SeqEven>
        <SeqOdd>0</SeqOdd>
        <SeqTriangular>0</SeqTriangular>
        <SeqSquare>0</SeqSquare>
        <SeqLazy>0</SeqLazy>
        <SeqFibonacci>0</SeqFibonacci>
        <SeqPrime>0</SeqPrime>
        <SeqDouble>0</SeqDouble>
        <SeqTriple>0</SeqTriple>
        <SeqPi>0</SeqPi>
        <SeqRecaman>0</SeqRecaman>
    </record>
</data-set>

I am currently reading this data with the following:

    //---------------------------------------------------------------------------------------------------------
    // ReadLevels
    //---------------------------------------------------------------------------------------------------------
    // Reads the contents of the levelInfo.xml file and builds a list of level objects with the data in the 
    // xml file.  Allows for easy changing of level setup and addint new levels
    //---------------------------------------------------------------------------------------------------------
    public List<Level> ReadLevels(XDocument levelInfo)
    {
        levelInfo = XDocument.Load ("./levelinfo.xml");

        List<Level> lvl = (from level in levelInfo.Root.Descendants ("record") // "record" has to match the record level identifier in the xml file
            select new Level {
                LevelNum = int.Parse (level.Element ("LevelNum").Value),
                LevelName = level.Element ("LevelName").Value,
                MaxBubbles = int.Parse (level.Element ("MaxBubbles").Value),
                MaxVisibleBubbles = int.Parse (level.Element ("MaxVisibleBubbles").Value),
                StartingPointValue = int.Parse (level.Element ("StartingPointValue").Value),
                MaxLevelTime = int.Parse (level.Element ("MaxLevelTime").Value),
                LevelPassScore = int.Parse (level.Element ("LevelPassScore").Value),
                TapsToPopStandard = int.Parse (level.Element ("TapsToPopStandard").Value),
                InitialVisibleBubbles = int.Parse (level.Element ("InitialVisibleBubbles").Value),
                LevelDescription = level.Element ("LevelDescription").Value,
                SeqLinear = (bool)level.Element ("SeqLinear"),
                SeqEven = (bool)level.Element ("SeqEven"),
                SeqOdd = (bool)level.Element ("SeqOdd"),
                SeqTriangular = (bool)level.Element ("SeqTriangular"),
                SeqSquare = (bool)level.Element ("SeqSquare"),
                SeqLazy = (bool)level.Element ("SeqLazy"),
                SeqFibonacci = (bool)level.Element ("SeqFibonacci"),
                SeqPrime = (bool)level.Element ("SeqPrime"),
                SeqDouble = (bool)level.Element ("SeqDouble"),
                SeqTriple = (bool)level.Element ("SeqTriple"),
                SeqPi = (bool)level.Element ("SeqPi"),
                SeqRecaman = (bool)level.Element ("SeqRecaman")
            }).ToList ();

        return lvl;
    }

My current data structure for the level data looks like this:

public class Level
{
    public int LevelNum { get; set; }
    public string LevelName { get; set; }
    public int MaxBubbles { get; set; }
    public int MaxVisibleBubbles { get; set; }
    public int StartingPointValue { get; set; }
    public int MaxLevelTime { get; set; }
    public int LevelPassScore { get; set; }
    public int TapsToPopStandard { get; set; }
    public int InitialVisibleBubbles { get; set; }
    public string LevelDescription { get; set; }
    public bool SeqLinear { get; set; }
    public bool SeqEven { get; set; }
    public bool SeqOdd { get; set; }
    public bool SeqTriangular { get; set; }
    public bool SeqSquare { get; set; }
    public bool SeqLazy { get; set; }
    public bool SeqFibonacci { get; set; }
    public bool SeqPrime { get; set; }
    public bool SeqDouble { get; set; }
    public bool SeqTriple { get; set; }
    public bool SeqPi { get; set; }
    public bool SeqRecaman { get; set; }

    public Level ()
    {

    }
}

I'm thinking that there is a better way to handle the list of boolean flag properties. Rather than have them all listed and set each one individually, it would probably be better to store these in a list or dictionary.

My problem is I don't know how I would deserialize the XML data to do that. I'm already creating a list of level objects, but I don't know how I would create the 'list within a list' the right way.

Is is possible to create another list with the list I'm already creating using nested linq statements or is there another way I should be doing this?

JMathes
  • 23
  • 6
  • check this: http://stackoverflow.com/questions/3187444/convert-xml-string-to-object – Ammar Hamidou Dec 07 '15 at 19:14
  • 4
    I'd like to say that you should probably be using the [built-in XML (de)serialization features](https://msdn.microsoft.com/en-us/library/182eeyhh%28v=vs.110%29.aspx) instead of rolling your own. – Cᴏʀʏ Dec 07 '15 at 19:15
  • Which of the above can or are you able to change? The XML, the class structure? If you follow what @Cᴏʀʏ says and use .Nets built in serialization, you are going to make your life a whole lot easier. – gmiley Dec 07 '15 at 19:21
  • 2
    Be careful with `int.Parse` -- it's locale-specific. Better to use methods from the [`XmlConvert`](https://msdn.microsoft.com/en-us/library/System.Xml.XmlConvert%28v=vs.110%29.aspx) class - or one of the [many explicit conversion operators](https://msdn.microsoft.com/en-us/library/system.xml.linq.xelement_operators%28v=vs.110%29.aspx) on `XElement`. Or just use [`XmlSerializer`](https://msdn.microsoft.com/en-us/library/182eeyhh%28v=vs.110%29.aspx) as Cᴏʀʏ recommended. – dbc Dec 07 '15 at 19:59
  • I am creating my level data in an excel spreadsheet, exporting to XML, and then reading it in my game app. The data isn't being generated in code and serialized. It will only be deserialized. – JMathes Dec 07 '15 at 21:00

3 Answers3

1

One simple way to do this without using a list of flags (whether or not a set of flags as opposed to strongly-typed properties suits you is up to you!) is to modify the XML a little bit:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<data-set xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <record>
        <LevelNum>0</LevelNum>
        <LevelName>Level 0</LevelName>
        <MaxBubbles>40</MaxBubbles>
        <MaxVisibleBubbles>30</MaxVisibleBubbles>
        <StartingPointValue>11</StartingPointValue>
        <MaxLevelTime>78</MaxLevelTime>
        <LevelPassScore>3000</LevelPassScore>
        <TapsToPopStandard>1</TapsToPopStandard>
        <InitialVisibleBubbles>9</InitialVisibleBubbles>
        <LevelDescription>Level 80</LevelDescription>
        <LevelSeq>
            <SeqLinear>1</SeqLinear>
            <SeqEven>0</SeqEven>
            <SeqOdd>0</SeqOdd>
            <SeqTriangular>0</SeqTriangular>
            <SeqSquare>0</SeqSquare>
            <SeqLazy>0</SeqLazy>
            <SeqFibonacci>0</SeqFibonacci>
            <SeqPrime>0</SeqPrime>
            <SeqDouble>0</SeqDouble>
            <SeqTriple>0</SeqTriple>
            <SeqPi>0</SeqPi>
            <SeqRecaman>0</SeqRecaman>
        </LevelSeq>
    </record>
</data-set>

And then modify your classes a little bit, to mirror the new structure and support use with XmlSerializer:

//Avoids missing xmlns errors with XmlSerializer
[Serializable, XmlRoot("record")]
public class Level
{
    public int LevelNum { get; set; }
    public string LevelName { get; set; }
    public int MaxBubbles { get; set; }
    public int MaxVisibleBubbles { get; set; }
    public int StartingPointValue { get; set; }
    public int MaxLevelTime { get; set; }
    public int LevelPassScore { get; set; }
    public int TapsToPopStandard { get; set; }
    public int InitialVisibleBubbles { get; set; }
    public string LevelDescription { get; set; }

    //gives a hint to the serialiser that the element <LevelSeq> should be used.
    [XmlElement(ElementName="LevelSeq")]
    public LevelSeq Seq {get;set;}

    public Level ()
    {
          Seq=new LevelSeq();
    }
}

public class LevelSeq
{
    public bool SeqLinear { get; set; }
    public bool SeqEven { get; set; }
    public bool SeqOdd { get; set; }
    public bool SeqTriangular { get; set; }
    public bool SeqSquare { get; set; }
    public bool SeqLazy { get; set; }
    public bool SeqFibonacci { get; set; }
    public bool SeqPrime { get; set; }
    public bool SeqDouble { get; set; }
    public bool SeqTriple { get; set; }
    public bool SeqPi { get; set; }
    public bool SeqRecaman { get; set; }
}

And then use the serialiser directly:

void Main()
    {
        var levelInfo = XDocument.Load(@"C:\test\data.xml");

        var serialiser = new XmlSerializer(typeof(Level));
        foreach (var level in levelInfo.Root.Descendants("record"))
        {
           Level newLevel = null;
          using (var reader = level.CreateReader())
          {
            try
            {
                newLevel = serialiser.Deserialize(reader) as Level;
            }
            catch(Exception)
            {
                //something went wrong with de-serialisation!
            }
            Console.WriteLine("Level.LevelNum={0}, evel.LevelSeq.SeqLinear=",
                             newLevel.LevelNum,newLevel.Seq.SeqLinear);     
         }
       }
    }

It admittedly isn't the fanciest but it's simple and pretty fast; and one advantage is that you can also use the same concept to easily write out your level data to disk.

Stephen Byrne
  • 7,400
  • 1
  • 31
  • 51
  • Is it still recommended to prefix my Level class with [Serializable, XmlRoot("record")] if I'm never going to serialize the class, just deserialize it? – JMathes Dec 07 '15 at 22:07
  • @JMathes - yes otherwise you will get an XmlSerializer Exception complaining that record xmlns='' was not expected. It's annoying and this is the simplest (but not the only) way to fix it :) – Stephen Byrne Dec 07 '15 at 22:08
0

I would suggest that you try using the XmlSerializer It can Serialize/Deserialize directly from XML-file to object for you.

Then you can move directly from an XML-doc to objects rather than parsing in every variable of data:

using (Stream stream = File.OpenRead(path))
        {
            XmlSerializer serializer = new XmlSerializer(typeof(T));
            T deSerializedType = (T)serializer.Deserialize(stream);
            return deSerializedType;
        }

This solution will work with classes containing other classes or lists of other objects, as long as they're all exposed as public properties on the containing class.

0

You may be able to adapt the following to your situation. Since we don't have your setting values code, I cannot presume to know how you are calling your method (whatever) that sets the xml.

public IList<bool> Flags
{
    get
    {
        try
        {
            return self.Element("Flags")
                .Elements()
                .Select(x => (bool)x)
                .ToList();
        }
        catch
        {
            return new bool[] { }.ToList();
        }
    }
    set
    {
        XElement flags = self.GetElement("Flags");
        flags.RemoveAll();
        flags.Add(value.Select(b => new XElement("flag", b)));
    }
}

I use the GetElement extension method from this public xml library which is similar to an Element() call, but creates the element if it doesn't exist.

self is like your level variable, except I make it a part of the Level object. So your code might look like:

public class Level
{
    XElement self;
    public int LevelNum { get; set; }
    public string LevelName { get; set; }
    public int MaxBubbles { get; set; }
    public int MaxVisibleBubbles { get; set; }
    public int StartingPointValue { get; set; }
    public int MaxLevelTime { get; set; }
    public int LevelPassScore { get; set; }
    public int TapsToPopStandard { get; set; }
    public int InitialVisibleBubbles { get; set; }
    public string LevelDescription { get; set; }

    public IList<bool> Flags { the code from above here }

    public Level (XElement e)
    {
        self = e;
    }
}
Chuck Savage
  • 11,775
  • 6
  • 49
  • 69
  • I'm not setting the values anywhere in code. I am creating my level data in excel and exporting to the xml file. Then, in the game I'm deserializing the xml data and using my list of levels as the player progresses through the game. Depending on the level, the user has access to various "Sequences". – JMathes Dec 07 '15 at 22:16
  • I'm a little confused with the `self` property. How would I create an instance of class Level? At the moment, I'm doing it like this: `List lvl = (from level in levelInfo.Root.Descendants ("record") select new Level { LevelNum = int.Parse (level.Element ("LevelNum").Value), ...` – JMathes Dec 08 '15 at 05:18
  • @JMathes `select new Level(level) { ... }` – Chuck Savage Dec 08 '15 at 21:05