Dynamic typing with C#4.0 part 2 – A Dynamic XML Document


Here’s a great example of where dynamic typing makes your code much more readable and logical: parsing an xml document. It turns out that someone has already done this – and I nicked a bit of his code (basically the implementation of IEnumerable) – but it’s funny how similar both of our code was before I found it (link is at end of this post).

Here’s an example XML document:

<catalog>

    <book id="bk101">

        <author>Gambardella, Matthew</author>

        <title>XML Developer's Guide</title>

        <genre>Computer</genre>

        <price>44.95</price>

        <publish_date>2000-10-01</publish_date>

        <description>

            An in-depth look at creating applications

            with XML.

        </description>

    </book>

    <!-- etc. etc. -->

</catalog>

Now, to parse that document, you could currently use either good old XmlDocument (and / or XmlReader), or the more modern XDocument in Linq to Xml. However, both of them and still inherently weakly typed, using strings passed into methods such as “Attribute (“MyAttr”) or Element (“MyElement”). I did see an alpha demo of a strongly-typed version of a Xml Document reader a while ago but haven’t heard anything of that recently. I recall seeing that VB9 also has some good XML support, but I’m exclusively a C# coder so let’s concentrate on that 😉

Anyway. So let’s see if we can make an XML document class in C#4 which will let us read the above XML document like this:

   1: // Create our dynamic XML document based off a Linq-to-Xml XElement.

   2: var xDocument = XDocument.Load ("Example.xml");

   3: dynamic dynamicXmlDocument = new DynamicXmlDocument (xDocument.Root);

   4:  

   5: // Get the first five books

   6: var books = dynamicXmlDocument.book as IEnumerable<DynamicXmlDocument>;

   7: foreach (dynamic book in books.Take (5))

   8: {

   9:     // Print out details on each book

  10:     System.Console.WriteLine ("Id: {0}, Title: {1}, Price: {2}",

  11:         book.id,            // Attribute access

  12:         book.title.Value,    // Element access

  13:         book.price.Value);  // Element access

  14: }

  15:  

  16: // Get a specific book

  17: var specificBook = dynamicXmlDocument.book["bk107"];

  18: System.Console.WriteLine ("Id: {0}, Title: {1}", specificBook.id, specificBook.title.Value); 

  19:  

  20: System.Console.Read ();

This actually wasn’t that difficult to write! Again, inheriting from DynamicObject and overriding a couple of methods, I was able to get this done in about three quarters of an hour.

First thing is the class hierarchy, our constructors and single member field:

   1: class DynamicXmlDocument : DynamicObject, IEnumerable, IEnumerable <DynamicXmlDocument>

   2: {

   3:     IEnumerable <XElement> elements;

   4:  

   5:     /// <summary>

   6:     /// Creates a new instance of the XML Dynamic Document based on a single XElement.

   7:     /// </summary>

   8:     /// <param name="element"></param>

   9:     public DynamicXmlDocument (XElement element)

  10:     {

  11:         elements = new List<XElement> { element };

  12:     }

  13:  

  14:     /// <summary>

  15:     /// Creates a new instance of the XML Dynamic Document based on a sequence of XElements.

  16:     /// </summary>

  17:     /// <param name="elements"></param>

  18:     public DynamicXmlDocument (IEnumerable <XElement> elements)

  19:     {

  20:         this.elements = new List<XElement> (elements);

  21:     }


Nothing too scary there, but notice how we implement IEnumerable and IEnumerable<T> so that we can do the foreach and .Take (5) in the original example.
 
The next bit is the first DynamicObject method we override, which gets called every time you make a property accessor request e.g. myDynamicDocument.book. The method looks for all sub-elements that match the parameter name, and if it finds at least one, returns a new dynamic xml document (dxd) based on them. If not, it looks for any attributes in the first element of the current dxd and returns the value if it finds that. Otherwise it returns null.
 
   1: /// <summary>

   2: /// Gets either all children the XML Dynamic Documents (i.e. elements), or the current element's attribute.

   3: /// </summary>

   4: /// <param name="binder">Specifies the keyword to search on.</param>

   5: /// <param name="result">The resultant object.</param>

   6: /// <returns>True is a match was made.</returns>

   7: public override bool TryGetMember (System.Dynamic.GetMemberBinder binder, out object result)

   8: {

   9:     var elementNameToSearchFor = binder.Name;

  10:     var matchingChildElements = elements.Elements (elementNameToSearchFor);

  11:     if (matchingChildElements.Any())

  12:     {

  13:         result = new DynamicXmlDocument (matchingChildElements);

  14:         return true;

  15:     }

  16:  

  17:     var attributeNameToSearchFor = binder.Name;

  18:     var attribute = elements.First ().Attribute (attributeNameToSearchFor);

  19:     if (attribute != null)

  20:     {

  21:         result = attribute.Value;

  22:         return true;

  23:     }

  24:  

  25:     result = null;

  26:     return false;

  27: }

 

The next question is how to get that array notation e.g. book[“123”] as per the example? That’ll be answered in the next post!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s