Dynamic XML Reader with C# and .Net 4.0

Along with the new features of .Net 4.0 (including the long awaited Tuple classes, BigInteger, extra Code Access Security stuff, and a brilliant new feature Code Contracts) is the dynamic keyword. This keyword is designed to allow better COM interoperability. Although it probably won’t make an appearance in everyday programming, it will help with interop with dynamic languages such as Python and Ruby (or Iron Python & Iron Ruby).

An example of its usage can be show in processing an XML document.

Consider this XML document:

<order orderId="123">

  <customer>

    <name>Wile E Coyote</name>

    <address>The Desert</address>

  </customer>

  <orderItem>

    <product>Rocket Powered Rollerskates</product>

    <quantity>1</quantity>

    <supplier>Acme Inc</supplier>

  </orderItem>

</order>

We could set up an XmlDocument, or XDocument via LINQ, to read its contents. This could cause some complex constructs within your code, and therefore make it less readable. Optionally we could ask to get the customer’s name, say customer.name, treating the XML document’s structure as a strongly typed object graph.

We can achieve this using a simple class extending from the new DynamicObject type. This is a new type within .Net 4.0, in System.Dynamic, which allows the TryGetMember methods to override and TryInvoke methods for method invocation. This allows the dynamic creation of a meta-class at runtime based on data provided. All you need to specify is how to handle the method calls.

So a basic Dynamic XML reader would look like this:

class DynamicXmlParser : DynamicObject

{

    XElement element;

    public DynamicXmlParser(string filename)

    {

        element = XElement.Load(filename);

    }

    private DynamicXmlParser(XElement el)

    {

        element = el;

    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)

    {

        if (element == null)

        {

            result = null;

            return false;

        }

        XElement sub = element.Element(binder.Name);

        if (sub == null)

        {

            result = null;

            return false;

        }

        else

        {

            result = new DynamicXmlParser(sub);

            return true;

        }

    }

    public override string ToString()

    {

        if (element != null)

        {

            return element.Value;

        }

        else

        {

            return string.Empty;

        }

    }

    public string this[string attr]

    {

        get

        {

            if (element == null)

            {

                return string.Empty;

            }

            return element.Attribute(attr).Value;

        }

    }

}

You will notice that we are still loading the XML document under the covers as an XElement. The most interesting point to note here is the override method:

public override bool TryGetMember(GetMemberBinder binder, out object result)

This override firstly looks to see if an XML document has been loaded. Secondly, its looks at the binder Name property (the binder being an object passed in, which would be the invoked member’s name) and if found, returns a new instance of the class pointing at the element requested.

The indexer property is defined by:

public string this[string attr]

This allows an index style access to attributes on the xml element.

We can now write statements such as:

dynamic parser = new DynamicXmlParser(@".\order.xml");

Console.WriteLine(parser.customer.name);

This will print out the customer’s name to the console.

Using the indexer we can write statements such as:

dynamic parser = new DynamicXmlParser(@".\order.xml");

Console.WriteLine(parser["orderId"]);

This will print out the orderId attribute on the order.

This can easily be extended to work with CSV files:

class DynamicCSV : DynamicObject

{

    List<string> columns;

    StreamReader sr;

    string[] currentLine;

    public DynamicCSV(string file)

    {

        sr = new StreamReader(file);

        string columnLine = sr.ReadLine();

        columns = columnLine.Split(',').ToList<string>();

    }

    public bool Read()

    {

        if (sr.EndOfStream)

        {

            return false;

        }

        else

        {

            currentLine = sr.ReadLine().Split(',');

            return true;

        }

    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)

    {

        int index = columns.FindIndex(col => col == binder.Name);

        if (index == -1)

        {

            result = null;

            return false;

        }

        result = this.currentLine[index];

        return true;

    }

}

This code snippet allows the access of CSV files with a named header (this parser assumes the first line of the CSV is the name of the column).

 

Written by Dave Thompson.