January 7, 2015

Reading Xml Using XDocument

The ideas behind this blog are revealed here
Wanted to read some objects in using XDocument.

Found that the best was to use the explicit cast operators exposed by XDocument. These can be used to protect against attributes and elements that are missing

Also found that the XDocument.Parse() method was an excellent way to use raw XML strings to test the parsing
Consider this class:
internal class Base 
{
 public int Id { get; set; }
 public string Name { get; set; }
 public string Faction { get; set; }
}
Here is some small test XML to parse:
string rawTestXml = @"
<Bases>
  <Base ID=""1""><NAME>Freeport 2</NAME><FACTION>Zoners</FACTION></Base>
  <Base ><NAME>Pacifica</NAME></Base>
</Bases>";
Here is a method to test parsing this XML:
public void TestXDocumentParseUnprotected()
{
    FreelancerData freelancerData = new FreelancerData();

    var xmlDoc = XDocument.Parse(rawTestXml);
    var basesTableRaw = new Dictionary<int, Base>();

    foreach (var bas in xmlDoc.Descendants("Base"))
    {
        var b = new Base()
        {
            Id = Convert.ToInt32(bas.Attribute("ID").Value),
            Name = bas.Element("NAME").Value,
            Faction = bas.Element("FACTION").Value,
        };

        basesTableRaw.Add(b.Id, b);
    }

    Debug.Assert(basesTableRaw.Count == 2);

}
This will throw an exception when parsing the second "Base" element in the Xml string, as this second object is missing an "ID" attribute a NullReferenceException will be thrown by the line to extract it. If the XML data can be guaranteed then this is not a problem. A more robust way to read in the XML is to use the XElement/Xattribute explicit conversion operators:
public void TestXDocumentParseProtected()
{
  var xmlDoc = XDocument.Parse(rawTestXml);
  var basesTableRaw = new Dictionary<int, Base>();

  foreach (var bas in xmlDoc.Descendants("Base"))
  {
    var b = new Base()
    {
      Id = (int?)bas.Attribute("ID") ?? 0,  // 0 when the attribute "ID" is not present
      Name = (string)bas.Element("NAME") ?? "",  // "" when the element "NAME" is not present
      Faction = (string)bas.Element("FACTION"), // null when the element "FACTION" is not present
    };

    basesTableRaw.Add(b.Id, b);
  }

  Debug.Assert(basesTableRaw.Count == 2);
}
However this is allowing malformed objects to be allowed as input. The developer must detect these and decide what to do with them.

Note that the XDocument.Load() method can be used to load a document from a local file system file or from a Url. So this method
public void LoadBases(string rootPath)
{
  string xmlFileName = "Bases.xml";
  string basesPath = Path.Combine(rootPath, xmlFileName);
  var xmlDoc = XDocument.Load(basesPath);
  
  ...
}
can work with a local file:
LoadBases(@"X:\Backup\Documents\flasp\");
or a url:
LoadBases(@"http://www.somewebserver.com/flasp/");

No comments: