Search code examples
c#xmlxpathdictionarycode-duplication

Reduce Code Duplication when Searching for values in XML with XPath


I am writing some code to parse an xml file of the following format (truncated for simplicity)

<?xml version="1.0" encoding="UTF-8"?>
<ship name="Foo">
 <base_type>Foo</base_type>
 <GFX>fooGFX</GFX>
 ....
</ship>

I am using a dictionary consisting of key pairs of parameter and and xpath to value, and querying this to load all the values into various variables. However, I am noticing that there will be a massive amount of code duplication. In fact, for every value I wish to retrieve, I will have to write another line of almost identical looking code. Here is my code:

class Ship
{
    public Ship()
    {
        paths = new Dictionary<string, string>();
        doc = new XmlDocument();
        //Define path to various elements
        paths.Add("name", "/ship/@name");
        paths.Add("base_type", "/ship/base_type");
        paths.Add("GFX", "/ship/GFX");
    }
    public void LoadFile(string filename)
    {// Loads the file and grabs the parameters
        doc.Load(filename);
        Name = doc.SelectSingleNode(paths["name"]).Value;
        Base_type = doc.SelectSingleNode(paths["base_type"]).Value;
        GFX = doc.SelectSingleNode(paths["GFX"]).Value;
    }

    public Dictionary<string, string> paths; //The XPaths to the various elements, define them in constructor
    public XmlDocument doc;
    public string Name;
    public string Base_type;
    public string GFX;
}

Notice the duplication here:

variable = doc.SelectSingleNode(paths["variable_name"]).value. 

There are going to be many more variables, so this section will be massive.

Is there anyway to simplify this? If this was C++, I would probably try pointers, but I know they are not recommended for use in C#, so is there a similar way?

I would be looking for something that I could give a list of variable names and xpaths, and have the code pull out all the values and load them in the variable in some kind of loop or something. I want to use XPaths because I expect that the format of this file may change periodically.

Any ideas?

Thanks in advance.

EDIT: I would also like to be able to modify this data and save it back. I am not adverse to saving an entire new tree if necessary, but it would be nice to modify the data in place if possible. I don't need a solution for the modification, but I just need to have this option open.


Solution

  • One good way to achieve the population of the object is by using XmlSerializer.Deserialize().

    Something like this:

    namespace TestSerialization
    {
        using System;
        using System.IO;
        using System.Xml;
        using System.Xml.Serialization;
    
        public class TestSerialization
        {
            static void Main(string[] args)
            {
                string theXml =
    @"<ship name='Foo'>
     <base_type>Foo</base_type>
     <GFX>fooGFX</GFX>
    </ship>";
                Ship s = Ship.Create(theXml);
    
                // Write out the properties of the object.
                Console.Write(s.Name + "\t" + s.GFX);
            }
        }
    
        [XmlRoot("ship")]
        public class Ship
        {
            public Ship() { }
    
            public static Ship Create(string xmlText)
            {
                // Create an instance of the XmlSerializer specifying type.
                XmlSerializer serializer = new XmlSerializer(typeof(Ship));
    
                StringReader sr = new StringReader(xmlText);
    
                XmlReader xreader = new XmlTextReader(sr);
    
                // Use the Deserialize method to restore the object's state.
                return (Ship)serializer.Deserialize(xreader);
            }
    
            [XmlAttribute("name")]
            public string Name;
    
            [XmlElement("base_type")]
            public string Base_type;
    
            public string GFX;
        }
    }
    

    UPDATE: The OP has added an additional question:

    I would also like to be able to modify this data and save it back. I am not adverse to saving an entire new tree if necessary

    Just use the XmlSerializer.Serialize() method.

    Here is a typical example of using it:

      // Create an XmlTextWriter using a FileStream.
      Stream fs = new FileStream(filename, FileMode.Create);
      XmlWriter writer = 
      new XmlTextWriter(fs, Encoding.Unicode);
      // Serialize using the XmlTextWriter.
      serializer.Serialize(writer, yourObject);
      writer.Close();