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:
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:
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;
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:
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:
<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:
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!
private static void ImportEmployee(Element element)
{
Company c = (Company) element.FindAncestorByElementName("Company").Peer;
if (c == null)
throw new Exception("Employee must belong to a Company");
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