Power of Dynamic: Reading XML and CSV files made easy using DynamicObject in C#





5.00/5 (11 votes)
DynamicObject wrapper for XML and CSV reading
1. INTRODUCTION
In case of CSV, its a kind of text file parsing but every developer had their own approach of reading a CSV file no matter its good or bad approach.
<Customer> <Name>Mahesh Kumar</Name> </Customer>
2. PREFACE
This gave me an idea that, i can use "DynamicObject" for reading XML and CSV files. i wrote a small prototype, it really worked well.
3. CODE DOWNLOAD
4. DYNAMIC OBJECT in C#
dynamic myDynamic = "Something"; Console.WriteLine(myDynamic.GetType().FullName);// results System.String
System.Dynamic.DynamicObject expose members such as properties and methods at run time, instead of in at compile time.
To know about DynamicObject refer:
http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject(VS.100).aspx
4.1 REFLECTION Vs DYNAMIC
#region Reflection Vs Dynamic // --- Using Reflection --- object usingReflection = Activator.CreateInstance(Type.GetType("System.Text.StringBuilder")); MethodInfo ObjectMethodInfo = usingReflection.GetType().GetMethod("Append", new[] { typeof(string) }); ObjectMethodInfo.Invoke(usingReflection, new object[] { "Hello" }); Console.WriteLine( usingReflection.GetType().GetMethod("ToString", new Type[0]).Invoke(usingReflection, null) ); // --- Using Dynamic --- dynamic usingDynamic = Activator.CreateInstance(Type.GetType("System.Text.StringBuilder")); usingDynamic.Append("Hello"); Console.WriteLine(usingDynamic.ToString()); #endregion
5. PARSING XML DOCUMENT USING System.Dynamic.DynamicObject
<?xml version="1.0" encoding="utf-8" ?> <Root> <Customers> <Customer CustomerID="GREAL"> <CompanyName>Great Lakes Food Market</CompanyName> <ContactName>Howard Snyder</ContactName> <ContactTitle>Marketing Manager</ContactTitle> <Phone>(503) 555-7555</Phone> <FullAddress> <Address>2732 Baker Blvd.</Address> <City>Eugene</City> <Region>OR</Region> <PostalCode>97403</PostalCode> <Country>USA</Country> </FullAddress> </Customer> </Customers> </Root>
dynamic customerObj = new DynamicObject(strXmlContent); // Lets say, strXmlContent variable holds value of above XML content string compName = customerObj.Root.Customers.Customer.CompanyName;
5.1 DYNAMIC XML STREAM
static DynamicXmlStream Parse(string, LoadOptions): allows user to pass xml string directly and create the dynamic object
This class is extended from System.Dynamic.DynamicObject and IEnumerable<DynamicXmlStream>.
5.1.1 TryGetMember()
But the actual result of the operation must be assigned to the out parameter result.
public override bool TryGetMember(GetMemberBinder binder, out object result) { result = null; if (binder.Name == DynamicXmlStream.Value) { var items = _XmlElementCollection[0].Descendants(XName.Get("Value")); if (items == null || items.Count() == 0) { result = _XmlElementCollection[0].Value; } else { result = new DynamicXmlStream(items); } } else if (binder.Name == DynamicXmlStream.Count) { result = _XmlElementCollection.Count; } else { XAttribute xAttribute = _XmlElementCollection[0].Attribute(XName.Get(binder.Name)); if (null != xAttribute) { result = xAttribute; } else { IEnumerable<XElement> xElementItems = _XmlElementCollection[0].DescendantsAndSelf(XName.Get(binder.Name)); if (xElementItems == null || xElementItems.Count() == 0) { return false; } result = new DynamicXmlStream(xElementItems); } } return true; }
5.1.2 TrySetMember()
public override bool TrySetMember(SetMemberBinder binder, object value) { if (binder.Name == DynamicXmlStream.Value) { _XmlElementCollection[0].Value = value.ToString(); } else { XElement setNode = _XmlElementCollection[0].Element(binder.Name); if (setNode != null) { setNode.SetValue(value); } else { if (value.GetType() == typeof(DynamicXmlStream)) { _XmlElementCollection[0].Add(new XElement(binder.Name)); } else { _XmlElementCollection[0].Add(new XElement(binder.Name, value)); } } } return true; }
5.1.3 TryGetIndex()
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { result = null; if (null != indexes[0]) { if (typeof(int) == indexes[0].GetType()) { int index = (int)indexes[0]; result = new DynamicXmlStream(_XmlElementCollection[index]); return true; } else if (typeof(string) == indexes[0].GetType()) { string attributeName = (string)indexes[0]; result = _XmlElementCollection[0].Attribute(XName.Get(attributeName)).Value; return true; } } return false; }
5.1.4 TrySetIndex()
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { if (null != indexes[0]) { if (typeof(string) == indexes[0].GetType()) { _XmlElementCollection[0].SetAttributeValue((string)indexes[0], value); return true; } } return false; }
5.1.5 TryConvert()
public override bool TryConvert(ConvertBinder binder, out object result) { if (binder.Type == typeof(XElement)) { result = _XmlElementCollection[0]; } else if (binder.Type == typeof(List<XElement>) || (binder.Type.IsArray && binder.Type.GetElementType() == typeof(XElement))) { result = _XmlElementCollection; } else if (binder.Type == typeof(String)) { result = _XmlElementCollection[0].Value; } else { result = false; return false; } return true; }
5.1.6 TryInvokeMember()
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { Type xmlType = typeof(XElement); try { result = xmlType.InvokeMember( binder.Name, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, _XmlElementCollection[0], args ); return true; } catch { result = null; return false; } }
5.1.7 Other Public Methods
5.1.8 XElementExtension class
public static class XElementExtension { public static DynamicXmlStream ToDynamicXmlStream(this XElement xElement) { DynamicXmlStream dynamicXmlStream = new DynamicXmlStream(xElement); return dynamicXmlStream; } }
5.1.9 XmlNamespaceRemover class
This class helps us to remove the namespaces in XML file. For example if below is my XML file:
<x:Book> <x:Name>Steve Jobs</x:Name> </x:Book>
In this case, developer cannot retrive name of the book by: "dynXmlObj.x:Book.x:Name" (which is wrong syntax). So, this XmlNamespaceRemover will remove all namespaces. Now, developer can fetch name of the book by: "dynXmlObj.Book.Name" (which is correct syntax)
public class XmlNamespaceRemover { public XmlNamespaceRemover() { } public static XElement RemoveAllNamespaces(XDocument xDocumentSource) { Stream docStream = new MemoryStream(); xDocumentSource.Save(docStream); docStream.Position = 0; Stream outputStream = new MemoryStream(); outputStream.Position = 0; XPathDocument xPathDocument = new XPathDocument(docStream); XPathNavigator xPathNavigator = xPathDocument.CreateNavigator(); XslTransform myXslTransform; myXslTransform = new XslTransform(); XmlReader xsltReader = XmlReader.Create(Assembly.GetExecutingAssembly().GetManifestResourceStream("LearningMahesh.DynamicIOStream.Xml.StripNamespace.xslt")); myXslTransform.Load(xsltReader); XsltArgumentList xsltArgList = new XsltArgumentList(); myXslTransform.Transform(xPathNavigator, xsltArgList, outputStream); outputStream.Position = 0; XDocument finalDocument = XDocument.Load(outputStream); XElement root = finalDocument.Root; return root; }
Thanks to one of reader of this article(woutercx), who suggested this change.
6. PARSING CSV DOCUMENT USING System.Dynamic.DynamicObject
CustomerID,EmployeeID,Address GREAL,6,USA HUNGC,3,UK
6.1 DYNAMIC CSV STREAM
In DynamicCsvStream, i simply use constructor to feed header columns and List<DynamicCsvRow> that holds the value of each row in the CSV file.
6.2 DYNAMIC CSV ROW
6.2.1 TryGetMember()
public override bool TryGetMember(GetMemberBinder binder, out object result) { int indexOfColumn = _HeaderColumns.IndexOf(binder.Name); result = null; if (-1 != indexOfColumn) { result = (indexOfColumn < _RowColumns.Count) ? _RowColumns[indexOfColumn] : null; } return (indexOfColumn >= 0); }
6.2.2 TrySetMember()
public override bool TrySetMember(SetMemberBinder binder, object value) { int indexOfColumn = _HeaderColumns.IndexOf(binder.Name); if (-1 != indexOfColumn) { _RowColumns[indexOfColumn] = value.ToString(); } return (indexOfColumn >= 0); }
6.2.3 TryGetIndex()
public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) { result = null; if (null != indexes[0]) { if (typeof(int) == indexes[0].GetType()) { int index = (int)indexes[0]; result = (index <= _RowColumns.Count) ? _RowColumns[index] : null; return true; } else if (typeof(string) == indexes[0].GetType()) { string attribute = (string)indexes[0]; int index = _HeaderColumns.IndexOf(attribute); result = (-1 != index && index < _RowColumns.Count) ? _RowColumns[index] : null; return true; } } return false; }
6.2.4 TrySetIndex()
public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) { if (null != indexes[0]) { if (typeof(int) == indexes[0].GetType()) { int indexToChange = (int)indexes[0]; if (indexToChange <= _RowColumns.Count) { _RowColumns[indexToChange] = value.ToString(); } return true; } else if (typeof(string) == indexes[0].GetType()) { string attribute = (string)indexes[0]; int indexToChange = _HeaderColumns.IndexOf(attribute); if (indexToChange <= _RowColumns.Count) { _RowColumns[indexToChange] = value.ToString(); } return true; } } return false; }
6.2.5 TryConvert()
public override bool TryConvert(ConvertBinder binder, out object result) { result = null; if (binder.Type == typeof(List<string>)) { result = _RowColumns; } else if (binder.Type == typeof(string)) { result = string.Join(",", _RowColumns); } else { return false; } return true; }
6.2.6 TryInvokeMember()
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { Type stringListType = typeof(List<string>); try { result = stringListType.InvokeMember( binder.Name, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, _RowColumns, args ); return true; } catch { result = null; return false; } }
7. CLIENT PROGRAM
dynamic customerOrderXmlReader = DynamicXmlStream.Load(new FileStream("../../Input/CustomerOrder.xml", FileMode.Open)); Console.WriteLine("First Customer's Company Name = " + customerOrderXmlReader.Root.Customers.Customer[0].CompanyName.Value); Console.WriteLine("Orders Shipped to CA: "); foreach (dynamic order in (customerOrderXmlReader.Root.Orders.Order as DynamicXmlStream).AsDynamicEnumerable() .Where(ord => ord.ShipInfo.ShipRegion.Value == "CA")) { Console.WriteLine(order.ToString()); }
// --- Read XML file with namespace like <x:book> --- dynamic booksReviewXmlReader = DynamicXmlStream.Load(new FileStream("../../Input/BooksReview.xml", FileMode.Open)); Console.WriteLine("\n\nXML Namespace remover for Book review:"); Console.WriteLine("Title Name" + booksReviewXmlReader.html.head.title.Value); Console.WriteLine("Book Price" + booksReviewXmlReader.html.body.bookreview.table.tr[1].td[1].price.Value);
dynamic customerOrderCsvReader = DynamicCsvStream.Load(new StreamReader("../../Input/CustomerOrder.csv")); Console.WriteLine("All Processed Orders: "); foreach (dynamic order in customerOrderCsvReader) { Console.WriteLine("Customer ID: {0}, Ship Date: {1}, Ship Address: {2}", order.CustomerID, // Calls TryGetMember order[4], // Calls TryGetIndex (Int) order["ShipAddress"] // Calls TryGetIndex (String) ); }