Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

Delegate-Based XML Processor for Configuring Objects from XML

4.38/5 (5 votes)
20 Aug 2006CPOL2 min read 1   72  
Here is a flexible, memory-efficient way of parsing an XML without dealing with the intricacies of XmlReader

Introduction

There have been times when I've needed to parse an XML file and configure some objects. XmlReader is very flexible, but too low level for the simple task at hand. The built-in serialization is a tempting option also, but not flexible enough and too intrusive. DOM is too memory intensive for large files. So after much frustration and percolation, I decided to write a simple processor class that made use of Delegates.

Note: This is a work-in-progress. It does not handle namespaces yet and probably can be improved upon in several ways. It's just a starting point, so feel free to send me improvements or suggestions.

The Problem

Suppose you want to import data from an XML file into your object model, saving each object to a database. In addition to loading data from XML, you have to set the foreign keys on each object.

Here's an example of some persistent objects:

C#
public class Company
{
    public int CompanyId;
    public string CompanyName;
    
    public void Save() { ... }
}

public class Employee
{
    public int EmployeeId;
    public int ManagerEmployeeId;
    public int CompanyId;
    public string EmployeeName;
    
    public void Save() { ...  }
}

The Solution

The minimal API I came up with consists of two classes and a delegate:

C#
public class XmlProcessor
{
    public XmlProcessor(XmlTextReader rdr);
    public void RegisterElementHandler(string elementName, ElementHandler handler);      
    public void Start();            
}
    
public class Element
{
    public string Name; 
    public IDictionary Attributes = new Hashtable();
    public Element Parent;
    public object Peer;  //arbitrary object to associate with this element
        
    public Element FindAncestorByElementName(string name);
}
        
public delegate void ElementHandler(Element element);

In order to use the XmlProcessor class, all you have to do is register some element handlers, and then invoke the Start() method, like this:

C#
XmlTextReader rdr = new XmlTextReader("../../SampleData.xml");
XmlProcessor processor = new XmlProcessor(rdr);
processor.RegisterElementHandler("Company", new ElementHandler(ImportCompany));
processor.RegisterElementHandler("Employee", new ElementHandler(ImportEmployee));
processor.Start();

Usage Details

As usual, the easiest explanation is an example.:) Here is the sample XML to give you an idea of what we're dealing with:

XML
<LoadData>
        <Company CompanyName="Microsoft">
                <Employee Name="Steve Ballmer">
                        <Employee Name="Anders Hejlsberg">
                                <Employee Name="Joe Developer"/>
                                <Employee Name="Jane Developer"/>
                        </Employee>
                        <Employee Name="Random VP"/>
                </Employee>
        </Company>
</LoadData>

The Object Model we have corresponds to two DB tables. The Employee table has two foreign keys, one to the Company, and a self-referencing FK that contains the ID of the manager.

First, we'll write the ElementHandler for the Company element. This is pretty straightforward. The only part that might look confusing is setting the Peer property of the element object. This is done so that sub-elements can get a reference of the parent Company object. Here's the code:

C#
private static void ImportCompany(Element element)
{
    Company c = new Company();
    c.CompanyName = (string) element.Attributes["CompanyName"];
    c.Save();
    element.Peer = c;
}

Finally we'll create a handler for the Employee element. This is going to be a bit trickier, but still a breeze!

C#
private static void ImportEmployee(Element element)
{
    //ensure Employee is nested in a Company Element
    Company c = (Company) element.FindAncestorByElementName("Company").Peer;
    if (c == null) 
        throw new Exception("Employee must belong to a Company");
    //create Employee and populate fields 
    Employee e = new Employee();
    e.EmployeeName = (string) element.Attributes["Name"];
    e.CompanyId = c.CompanyId;
    if (element.Parent.Name == "Employee")
    {
        Employee manager = (Employee) element.Parent.Peer;
        e.ManagerEmployeeId = manager.EmployeeId;
    }
    e.Save();
    element.Peer = e;
}

We make use of element.FindAncestorByElementName() to get the Employee's Company. To get the Employee's manager, first we check if element.Parent.Name == "Employee". If it is, we can set the ManagerEmployeeId. Again, we have to make sure to set the element.Peer to the current Employee, so that any sub-elements can access the EmployeeId of their manager.

Also note that any elements we don't care about (like the root LoadData element), we simply don't have to register a handler for.

Conclusion

That's pretty much it! You can get the actual implementation of the XmlProcessor by downloading the demo solution (for the sake of brevity). Again, the actual implementation of XmlProcessor is pretty simple and could be improved, but it works.

I hope this is helpful and you have fun using it!

History

  • 20th August, 2006: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)