Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Sunday, December 26, 2010

From LINQ to XML to LINQ to Objects: Two Generic XElement Extension Methods

LINQ is one of the most amazing programming language innovations in the few recent years. Coming with .NET framework 3.5, there are a whole new stack of XML classes that help you manipulate XML document in your .NET program using LINQ approach.

In real-world programming practice, we often have the need to read from an XML file and convert to an enumerable list of strong-typed objects which you start to work with in your .NET application. But I have never come across a complete sample code that shows me how to do this properly in a simple way, so I came up with the following two extension methods that might help you if you need to do the same thing in your life.

/// <summary>
/// Convert the value of an XElement's child element to the specified generic type.
/// </summary>
/// <typeparam name="T">The target type to be used to convert the XML element value</typeparam>
/// <param name="xe">The XML element that contains child elements</param>
/// <param name="childNode">The child element that you want to parse its element value</param>
/// <returns>Converted value of type T</returns>
public static T ParseValueAs<T>(this XElement xe, XName childNode)
{
    return ParseValueAs<T>(xe, childNode, null);
}

/// <summary>
/// Convert the value of an XElement's child element (or one of its attributes) to the specified generic type.
/// </summary>
/// <typeparam name="T">The target type to be used to convert the XML element or attribute value</typeparam>
/// <param name="xe">The XML element that contains child elements</param>
/// <param name="childNode">The child element that you want to parse its element or attribute value</param>
/// <param name="attribute">If provided, the attribute value will be parsed. Otherwise, the element value will be parsed</param>
/// <returns>Converted value of type T</returns>
public static T ParseValueAs<T>(this XElement xe, XName childNode, XName attribute)
{
    if (xe == null)
        return default(T);

    XElement childElement = xe.Element(childNode);
    if (childElement == null)
        return default(T);

    bool valueIsEmpty = attribute == null
                            ? string.IsNullOrEmpty(childElement.Value)
                            : childElement.Attribute(attribute) == null || string.IsNullOrEmpty(childElement.Attribute(attribute).Value);

    if (valueIsEmpty)
        return default(T);

    string value = (attribute == null) ? childElement.Value : childElement.Attribute(attribute).Value;

    Type type = typeof(T);

    if (type == typeof(bool) || type == typeof(bool?))
    {
        switch (value.ToLower())
        {
            // You may tweak the following options a bit based on your needs
            case "1":
            case "true":
            case "yes":
                value = "true";
                break;

            case "0":
            case "false":
            case "no":
                value = "false";
                break;

            default:
                return default(T);
        };
    }

    TypeConverter converter = TypeDescriptor.GetConverter(type);
    return (T)converter.ConvertFromString(value);
}
Given that you have an XML file, suppose it's called contacts.xml, like this:
<Contacts>
  <Contact>
    <Name>Patrick Hines</Name>
    <Phone>408-555-1234</Phone>
    <BirthDate>1990-01-01</BirthDate>
    <Address>123 Main St, San Jose, CA, 94500</Address>
  </Contact>
  <Contact>
    <Name>Patrick Hines</Name>
    <Phone>510-444-0123</Phone>
    <Address>345 Center Ave, Milpitas, CA, 94439</Address>
  </Contact>
</Contacts>
You can use the following C# snippet to convert the XML to your LINQ objects.
XDocument xml = XDocument.Load("contacts.xml");

var contacts = from item in xml.Root.Elements()
               select new
               {
                   Name = item.ParseValueAs<string>("Name"),
                   Phone = item.ParseValueAs<string>("Phone"),
                   BirthDate = item.ParseValueAs<DateTime?>("BirthDate"),
                   Address = item.ParseValueAs<string>("Address"),
               };
I used an anonymous type for simplicity, but you can always use any defined class that you may already have in your application, so you are working with the objects that you are familiar with.

As usual, here are a few notes before we conclude this blog post:
  • You should always try to use the basic CLR types as your target type, such as int, bool, string, DateTime, etc.
  • As I have mentioned, I have offered a pair of overloaded methods, they are serving for different purpose. One of them is parsing the XML element's value, another one is parsing an XML element's attribute value. With the overloaded signatures, you have the flexibility to either read the XML element or an XML element's attribute.
  • You may want to tweak a little bit about how you want to parse into boolean value depending on your XML data and business requirement.
  • I didn't include any exception handling in the code, which you may want to add, if your XML data is unpredictable.
  • It's advisable to return nullable type, so when the XML element or attribute doesn't exist, it return null as the value, which makes more sense in most cases.

[Update - Jan 6, 2010] I updated the code so that the extension methods now support XML namespace.

If you are coming Microsoft Dynamics CRM world, I will show you in my next blog post about how to use the above extension methods to properly parse your FetchXML query result. Stay tuned.

Hope this helps, cheers!

Sunday, May 02, 2010

C#: A Simple Pseudo-Serializable Generic <string, string> Dictionary

Today I was trying to find an easy way to convert XML string to a C# generic <string, string> dictionary and the other way around. I wasn't able to find anything easy and simple enough, so I decided to write my own code to do the job.

I knew the first option would be XML Serializer, but there are a couple of issues that I don’t appreciate using it. First, I want to keep the xml file really simple, I don't like a full-blown XML file, as the more complex the XML file is, the easier people make mistakes when making changes to the XML file. Second, I want to be able to customize the dictionary root node's name, item node's name, and also key/value attribute's name, which doesn't seem to be viable using XML serializer. Lastly, C# generic dictionary is not serializable out-of-box, so it has to be custom serialization code like this one.

To be more specific, what I want is actually very simple, I want my code to convert the following XML string to a C# generic dictionary, and other way around the other time.
<settings>
  <setting key="setting1" value="value1" />
  <setting key="setting2" value="value2" />
</settings>
Here is the class that I implemented based on C# Dictionary class.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

public class SimpleDictionary : Dictionary<string, string>
{
    /**
     * A Simple pseudo-serializable generic <string, string> dictionary
     * @author Daniel Cai, http://danielcai.blogspot.com/
     */
    private readonly string xsdMarkup = @"
<xs:schema id='dictionary' xmlns='' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  <xs:element name='{0}'>
    <xs:complexType>
      <xs:choice minOccurs='0' maxOccurs='unbounded'>
        <xs:element name='{1}'>
          <xs:complexType>
            <xs:attribute name='{2}' type='xs:string' />
            <xs:attribute name='{3}' type='xs:string' />
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>";

    private string _rootNodeName;
    private string _itemNodeName;
    private string _keyAttributeName;
    private string _valueAttributeName;

    public SimpleDictionary()
        : this("dictionary", "item", "key", "value")
    {

    }

    public SimpleDictionary(string rootNodeName, string itemNodeName, string keyAttributeName, string valueAttributeName)
    {
        _rootNodeName = rootNodeName;
        _itemNodeName = itemNodeName;
        _keyAttributeName = keyAttributeName;
        _valueAttributeName = valueAttributeName;
        xsdMarkup = string.Format(xsdMarkup, rootNodeName, itemNodeName, keyAttributeName, valueAttributeName);
    }

    public void FromXml(string xml)
    {
        Clear();

        XDocument xdoc = XDocument.Parse(xml);

        ValidateXml(xdoc);

        var dictionaryItemQuery = from element in xdoc.Root.Elements()
                       where
                           element.Name == _itemNodeName &&
                           element.Attributes().Count() == 2 &&
                           element.FirstAttribute.Name == _keyAttributeName &&
                           element.LastAttribute.Name == _valueAttributeName

                       select element;

        foreach (XElement keyValuePair in dictionaryItemQuery)
        {
            Add(keyValuePair.Attribute(_keyAttributeName).Value,
                keyValuePair.Attribute(_valueAttributeName).Value);
        }
    }

    public string ToXml()
    {
        XElement xElement = new XElement(_rootNodeName,
                                         from key in this.Keys
                                         select new XElement(_itemNodeName,
                                                             new XAttribute(_keyAttributeName, key),
                                                             new XAttribute(_valueAttributeName, this[key]))
            );
        return xElement.ToString();
    }

    private void ValidateXml(XDocument xdoc)
    {
        bool isValid = true;
        string errorMessage = string.Empty;

        XmlSchemaSet schemas = new XmlSchemaSet();
        schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup)));

        xdoc.Validate(schemas, (sender, e) =>
        {
            errorMessage = string.Format("Validation error: {0}", e.Message);
            isValid = false;
        }, true);

        if (!isValid)
        {
            throw new XmlSchemaValidationException(errorMessage);
        }
    }
}
To convert a XML string into a generic <string, string> dictionary, you can write your code as below:
SimpleDictionary simpleDictionary = new SimpleDictionary("settings", "setting", "key", "value");
simpleDictionary.FromXml(@"
<settings>
  <setting key='key1' value='value1' />
  <setting key='key2' value='value2' />
</settings>");

// Your dictionary is now ready for use. 
To convert a <string, string> dictionary into XML string, the code should be something like this:
SimpleDictionary simpleDictionary = new SimpleDictionary();
simpleDictionary.Add("key1", "value1");
simpleDictionary.Add("key2", "value2");

string xml = simpleDictionary.ToXml();
Console.WriteLine(xml);

/* Output:
<dictionary>
  <item key='key1' value='value1' />
  <item key='key2' value='value2' />
</dictionary>"
*/
It's worth noting that the class has two constructors, the default one will use a default set of names (dictionary as the root node name, item as the dictionary item node name, key as the item's key attribute name, and value as the item's value attribute name). If you want the nodes and attributes to be called differently, you may call the other constructor by providing specific names.

Hope this helps.