Stream Processing XML in IronPython

When it comes to processing XML, there are two basic approaches – load it all into memory at once or process it a node at a time. In the .NET world where I have spent most of the past ten years, those two models are represented by XmlDocument and XmlReader. There are alternatives to XmlDocument, such as XDocument and XPathDocument, but you get the idea.

Out in non-MSFT land, the same two basic models exist, however the de facto standard for stream based processing is SAX, the Simple API for XML. SAX is supported by many languages, including Python.

Personally, I’ve never been a fan of SAX’s event-driven approach. Pushing events makes total sense for a human driven UI, but I never understood why anyone thought that was a good idea for stream processing XML. I like XmlReader’s pull model much better. When you’re ready for the next node, just call Read() – no mucking about setting content handlers or handling node processing events.

Luckily, the Python standard library supports both approaches. It provides both a SAX based parser as well as a pull based parser called pulldom. Pulldom doc’s are fairly sparse, but Paul Prescod wrote a nice introduction. Here’s an example from Paul’s site (slightly modified):

from xml.dom import pulldom
nodes = pulldom.parse( "file.xml" )  
for (event,node) in nodes:  
    if event=="START_ELEMENT" and node.tagName=="table":  
        nodes.expandNode( node )

Actually, I like this better than XmlReader, since it provides the nodes in a list-like construct that appeals to the functional programmer in me. I’d like it even more if Python had a native pattern matching syntax – you know, like F# – but you can get similar results by chaining together conditionals with elif.

However, IronPython doesn’t support any of the XML parsing modules from Python’s standard library. They’re all based on a C-based python module called pyexpat which IronPython can’t load. 1 I wanted a pulldom type model, so I decided to wrap XmlReader to provide a similar API and lets me write code like this:

import ipypulldom  
nodes = ipypulldom.parse( "sample.xml" )
for node in nodes:
  if node.nodeType==XmlNodeType.Element:
    print node.xname

There are a few differences from pulldom, but it’s basically the same model. I’m using the native .NET type XmlNodeType rather than a string to indicate the node type. Furthermore, I made the node type a property of the node, rather than a separate variable. I also didn’t implement expandNode, though doing so would be a fairly straightforward combination of XmlReader.ReadSubtree and XmlDocument.Load.

I stuck the code for ipypulldom up in a new folder on my Skydrive: IronPython Stuff. It’s fairly short – only about 45 lines of code. Feel free to use it if you need it.


  1. The FePy project has a .NET port of pyexpat as part of their distribution, so I assume that lets you use the standard pulldom implementation in IPy. FePy looks really cool but I haven’t had time to dig into it yet.

Rare Insight Into the IPy Team

An email conversation between two members of the IPy team that you, dear reader, might find amusing.

From:Srivatsn Narayanan
Sent: Friday, May 02, 2008 4:00 PM
Subject: You dont need pinky fingers? 😄

http://twitter.com/DevHawk/statuses/802239164


From:Dino Viehland
Sent: Friday, May 02, 2008 11:30 PM

Wow, am I really that loud? 😄

This is a response to the idea that no design is complete until you can’t remove anything else without sacrificing the overall goals of the end result.  Is the pinky really necessary?  We can “cut that feature”, right? 😄


From: Srivatsn Narayanan
Sent: Friday, May 02, 2008 11:49 PM

If you cut off the pinkie, then the ring finger becomes the pinkie and so iteratively you will have to lose all of them 😃. If I remember right in Europe, you hold up the pinkie to order beer. So it’s quite important 😄


From: Dino Viehland
Sent: Friday, May 02, 2008 11:59 PM

Well I find it hard to argue with ordering beer, but I doubt the pinkie’s original use was to order beer.  Therefore I’d describe this as an abuse of an existing API that might be better served by a new API specifically designed for this purpose.  But I question even if a new API is necessary.

Drinking beer by definition involves a container in which the beer is consumed from.  Therefore the container it’s self (or the lack thereof) might be able to serve the dual role for both consuming the beer and indicating the need for a refill.  For example when drinking sake I’ve been told it is customary to tip the bottle on its side to indicate the desire for a refill.  Are we really to expect that the Human API exposes every need in a direct fashion?  For example should I have another appendage for “I need more bread sticks” and yet another for “Can I get a refill or on the water?”.  I’d propose the “2 creams no sugar” appendage because that’s the way I take my coffee.

On the other hand the iterative problem does seem to be more relevant – but it presupposes a purpose for the pinky in the first place!  But alas, I admit I’ve just returned from drinking beer, so this may not be the best argument 😃.


From: Srivatsn Narayanan
Sent: Saturday, May 03, 2008 12:21 AM

The pinkie is like a reserved field/custom field in a API. Every user community can come up with its own use for it but they have to devise the protocol themselves. Apparently different civilizations have. Wikipedia as usual has a list of those. So do you get your “2 creams no sugar” appendage – sure if u can agree on the semantics with the waiter. Seems like a PM call to me 😄

PS: What beer did u have? I should try that – seems effective 😄


From: Dino Viehland
Sent: Saturday, May 03, 2008 12:26 AM

I hadn’t quite considered it like this but I’d propose an alternate encoding – binary.  With 4 fingers one can communicate up to 16 different which seems to cover most of the listed issues.  Yes 5 gives you up to 32 messages but 16 should be enough for everyone 😃.

And they were various Hale’s Ales fresh from the brewery.


From: Srivatsn Narayanan
Sent: Saturday, May 03, 2008 12:31 AM

I wouldn’t want to hold up 0010 really (although it won’t be the middle finger anymore) 😄


From: Dino Viehland
Sent: Saturday, May 03, 2008 12:40 AM

Ahh, but let’s just call that one reserved – that leaves the end-user with 15 other messages!  Personally I’d suggest we reserve the entire bit – that way we could use other codes like 1010 for future expansion of various standard messages.  Urination seems popular on the pinky list so maybe we’d want to standardize that in the future – we of course need to look at uses of the other fingers before we come to this conclusion.  Users are now left with 8 user-defined messages but that seems plenty.  Also they can then use 1000 or 0001 (depending their endianness, a.k.a. left-handed or right-handed) for the now deprecated pinky-messages.


From: Srivatsn Narayanan
Sent: Saturday, May 03, 2008 12:55 AM

1000, 1100, 1110 and 1111 are actually used for counting numbers 1,2,3,4 😄 They are too obvious to be non-standardized. So they are out as well.

That cuts the user messages to 5 (third bit is reserved anyway). Some of the ones left are hard to make as well – try 0101 😄