Using the TBXML Library

An increasingly common activity in iOS apps is retrieving data from web services. This is particularly true when the app is really an extension of the functionality offered by a web-based application. One challenge in doing this is that some key classes often used in Mac OS X applications are not available on the iOS platform: specifically, NSXMLDocument. This turns out to be a blessing in disguise because NSXMLDocument is not very performant. Instead, I use TBXML, an excellent lightweight XML parser.

Installing TBXML

There are two steps in installing TBXML. First, you need to download two classes from the TBXML web site: NSDataAdditions and TBXML, and add those classes to your project in Xcode. Next, you need to link the zlib C library as a binary (see the "Add a Framework" section of Add Frameworks in Xcode 4 for instructions on how to link a library).

Working with TBXML

TBXML is designed to work with well-formed XML. It does not have the validation tools of some other third-party XML parsers, but it is extremely fast (an important feature for iOS libraries). Consider the following sample XML (being retrieved from my development web and database server via web services):

Shortened XML Result from http://dev.istarel.com:8080/collection?id=1

[xml]

<?xml version="1.0" encoding="UTF-8"

<collection>

<version>1308399860</version>

<specimen id="1">

<label>0001</label>

<name>Magnetite</name>

<site></site>

<locality>Mexico</locality>

<appearance>plate of black octahedral magnetite crystals (to 10 mm wide), many twinned and intergrown, on massive magnetite matrix; reddish-brown rust coats parts of most of crystal faces</appearance>

<photograph>owner/000001/gallery/full/full_000001.jpg</photograph>

</specimen>

<specimen id="2">

<label>0002</label>

<name>Natrolite</name>

<site></site>

<locality>Nova Scotia, Canada</locality>

<appearance>hairlike spray of white needles (to 9 mm long) on mesolite ball on pale greenish-white rhyolitic matrix</appearance>

<photograph>owner/000001/gallery/full/full_000003.jpg</photograph>

</specimen>

<specimen id="3">

<label>0003</label>

<name>Prehnite</name>

<site></site>

<locality>New Jersey</locality>

<appearance>glittering translucent pale green balls of prehnite (to 1.1 cm radius)</appearance>

<photograph>owner/000001/gallery/full/full_000005.jpg</photograph>

</specimen>

</collection>

[/xml]

The goal is to parse the XML in order to create a collection of Specimen objects.

@interface Specimen : NSObject
{
        NSUInteger specimenID;
        NSString *label;
        NSString *name;
        NSString *site;
        NSString *locality;
        NSString *appearance;

        NSString *photograph;
}

@property (nonatomic) NSUInteger specimenID;
@property (nonatomic, copy) NSString *label;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *site;
@property (nonatomic, copy) NSString *locality;
@property (nonatomic, copy) NSString *appearance;
@property (nonatomic, copy) NSString *photograph;

@end

To parse the XML using TBXML, I might have a class implementation with an init method that calls an initSpecimens method (my actual implementation is quite different, but this serves to illustrate how TBXML works).

Listing: SpecimenManager.h

#import

@interface SpecimenManager
{
    NSUInteger version;
    NSMutableArray *specimens;
}

- (void)initSpecimens;

@end

Listing: SpecimenManager.m

#import &quot;SpecimenManager.h&quot;
#import &quot;Specimen.h&quot;

@implementation SpecimenManager

- (id)init
{
    [super init];
    [self initSpecimens];
    return self;
}

- (void)initSpecimens
{
    // Instatiate the collection ivar
    specimens = [[NSMutableArray alloc] init];

    // Retrieve the XML via the web service
    NSURL *url = [NSURL URLWithString:@&;quot;http://dev.istarel.com:8080/collection?id=1&quot;];
    NSData *data = [NSData dataWithContentsOfURL:url];

    // Instantiate the TBXML tree with data retrieved from the web service
    TBXML *tbxml = [[TBXML tbxmlWithXMLData:data] retain];
    TBXMLElement *root = [tbxml rootXMLElement];

    // First child is the version
    TBXMLElement *node = root->;firstChild;
    version = [[TBXML textForElement:node] integerValue];

    Specimen *specimen;

    node = node->;nextSibling;
    while (node)
    {
        specimen = [[Specimen alloc] init];

        // Determine the specimenID for the current specimen
        NSString *specimenID = [TBXML valueOfAttributeNamed:@&;quot;id&;quot; forElement:node];
        [specimen setSpecimenID:(NSUInteger)[specimenID integerValue]];

        // Retrieve the label of the specimen
        TBXMLElement *label = [TBXML childElementNamed:@&;quot;label&;quot; parentElement:node];
        [specimen setLabel:[TBXML textForElement:label]];

        // Retrieve the name of the specimen
        TBXMLElement *name = [TBXML childElementNamed:@&;quot;name&;quot; parentElement:node];
        [specimen setName:[TBXML textForElement:name]];

        // Retrieve the site of the specimen
        TBXMLElement *site = [TBXML childElementNamed:@&;quot;site&;quot; parentElement:node];
        [specimen setSite:[TBXML textForElement:site]];

        // Retrieve the locality of the specimen
        TBXMLElement *le = [TBXML childElementNamed:@&;quot;locality&;quot; parentElement:node];
        [specimen setLocality:[TBXML textForElement:le]];

        // Retrieve the appearance of the specimen
        TBXMLElement *ae = [TBXML childElementNamed:@&;quot;appearance&;quot; parentElement:node];
        [specimen setAppearance:ae]];

        // Retrieve the URL to the specimen's primary photograph
        TBXMLElement *pe = [TBXML childElementNamed:@&;quot;photograph&;quot; parentElement:node];
        NSString *photograph = [TBXML textForElement:pe];
        NSString *path = [NSString stringWithFormat:@&;quot;%@%@&;quot;, HTTP_HOST, photograph];
        [specimen setPhotograph:path];

        [specimens addObject:specimen];

        [specimen release];

        node = node->;nextSibling;
    }

    [tbxml release];
}

@end