Fast XML parsing in iOS / Objective-C with TBXMLEx

Have you ever wondered which is the best way to parse a XML file in Objective-C / iOS? There are certainly a lot of options available to those who know where to lot at, but many of them are just awful to use: hard to configure, cryptic API, inexistent documentation and so forth.

Ray Wenderlich once wrote a post called “How To Choose The Best XML Parser for Your iPhone Project“, which greatly covers many XML parsers available to iOS, and was there that I discovered TBXML, which is by far one of the best parsers available to iOS. While TBXML is great, it’s “non-OO” API and some intolerance to bad formed XML had me wonder if there were anything I could do to make it better.

With that motivation, I created an extension to TBXML called “TBXMLEx” (TBXML with Extensions) which adds some syntax sugar on top of the original library and better handle bad XML files, all of that with a more OO-friendly interface.

The design goals for TBXMLEx are (taken for the original TBXML goals):

  • XML files conforming to the W3C XML spec 1.0 should be passable
  • XML parsing should incur the fewest possible resources
  • XML parsing should be achieved in the shortest possible time
  • It shall be easy to write programs that utilise TBXML
How to use it
Below is a quick example of how to use TBXMLEx:
#include "TBXMLEx.h"

NSString *xml = @"<files> \
    <file timestamp='1234567890' size='123' createdAt='01/01/20011'>file1.jpg</file> \
    <file timestamp='1234567890' size='8934'> \
        <name>file2.jpg</name> \
        <attributes> \
            <createdAt>01/01/2011 13:45:56</createdAt> \
            <owner>john</owner> \
        </attributes> \
    </file> \
</files>";

TBXMLEx *xml = [TBXMLEx parserWithXML:xml];

// "files" is the rootElement
if (xml.rootElement) {
    TBXMLElementEx *fileNode = 1;

    while ([fileNode next]) {
      // You can access the attributes through a dictionary
        NSDictionary *allAttributes = fileNode.attributes;
        NSLog(@"Timestamp: %@", [allAttributes objectForKey:@"timestamp"]);

        // Or you can have direct access to any specific attribute
        NSLog(@"Size: %d", [fileNode intAttribute:@"size"]);

        NSObject *createdAt = [fileNode attribute:@"createdAt"];
        NSString *filename = fileNode.value; // or fileNode.text

        if (!createdAt || !filename) {
            // Look for the properties someplace else
            TBXMLElementEx *nameNode = [fileNode child:@"name"];

            if (nameNode) {
                filename = nameNode.value;
            }

            TBXMLElementEx *attributesNode = [fileNode child:@"attributes"];

            // If an attribute does not exist it will simply return "nil", but nevertheless it's
            // always good check if it exists if you really need it
            if (attributesNode) {
                NSLog(@"Created at: %@", [attributesNode child:@"createdAt"].value); // Will not crash if attribute is nil
                NSLog(@"Owner: %@", [attributesNode child:@"owner"].value);
            }
        }

        NSLog(@"Filename: %@", filename);
    }
}

You can find more information about the project, as well the source code, at https://github.com/rafaelsteil/tbxmlex