Sunday, November 14, 2010

How to parse xml using Objective C



There will always be a need to parse xml, especially if you are dealing with web services. In this post i will show how to use NSXMLParser to parse the xml you get back from Amazon Web Service when you attach a device to a running EC2 instance.
Here is what the xml response looks like:
<AttachVolumeResponse xmlns="http://ec2.amazonaws.com/doc/2010-08-31/">
  <volumeId>vol-4d826724</volumeId>
  <instanceId>i-6058a509</instanceId>
  <device>/dev/sdh</device>
  <status>attaching</status>
  <attachTime>2008-05-07T11:51:50.000Z</attachTime>
</AttachVolumeResponse>

To do the parsing , we need to use NSXMLParserDelegate protocol and we will implement three of its methods:
  • – parser:didStartElement:namespaceURI:qualifiedName:attributes:
  • – parser:didEndElement:namespaceURI:qualifiedName:
  • – parser:foundCharacters:
Here is what the header file looks like:

@interface AttachVolumeParser : NSObject {
    NSMutableArray *items;
    AttachVolumeInfo *volumeInfo;
    NSString *keyInProgress;
    NSMutableString *textInProgress;
bool isItemInProgress;
}

@property(nonatomic, assign) bool isItemInProgress;

- (BOOL)parseData:(NSData *)d;
- (NSArray *)items;
@end

items is the array that will hold the objects resulting from parsing the xml.
you need to create an object that corresponds to the xml above and that i refer to in above code snippet as AttachVolumeInfo.

And the implementation looks something like this:
#import "AttachVolumeParser.h"


static NSSet *interestingKeys;

@implementation AttachVolumeParser
@synthesize isItemInProgress;

+ (void)initialize
{
    if (!interestingKeys) {
        interestingKeys = [[NSSet alloc] initWithObjects:@"volumeId", @"instanceId", @"device", @"status", @"attachTime", nil];
    }
}

- (void)dealloc
{
    [items release];
    [super dealloc];
}

- (BOOL)parseData:(NSData *)d
{
    [items release];
    items = [[NSMutableArray alloc] init];
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:d];
    [parser setDelegate:self];
    [parser parse];
    [parser release];
    return YES;
}

- (NSArray *)items
{
    return items;
}

#pragma mark Delegate calls

- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qName
    attributes:(NSDictionary *)attributeDict
{
    if ([elementName isEqual:@"AttachVolumeResponse"]) {
isItemInProgress = true;
volumeInfo = [[AttachVolumeInfo alloc] init];
        return;
    }

    if ([interestingKeys containsObject:elementName]) {
        keyInProgress = [elementName copy];
        textInProgress = [[NSMutableString alloc] init];
    }
}

- (void)parser:(NSXMLParser *)parser
 didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI
 qualifiedName:(NSString *)qName
{
    if ([elementName isEqual:@"AttachVolumeResponse"]) {
[items addObject:volumeInfo];
[volumeInfo release];
volumeInfo = nil;
isItemInProgress = false;
        return;
    }
    // Is the current key complete?
    if ([elementName isEqual:keyInProgress]) {
        if([elementName isEqual:@"volumeId"]) {
            [volumeInfo setVolumeId:textInProgress];
        } else if([elementName isEqual:@"instanceId"]) {
            [volumeInfo setInstanceId:textInProgress];
        } else if([elementName isEqual:@"device"]) {
            [volumeInfo setDevice:textInProgress];
        }  else if([elementName isEqual:@"status"]) {
            [volumeInfo setStatus:textInProgress];
        } else if([elementName isEqual:@"attachTime"]) {
            [volumeInfo setAttachTime:textInProgress];
        }
        // Clear the text and key
        [textInProgress release];
        textInProgress = nil;
        [keyInProgress release];
        keyInProgress = nil;
    }
}

// This method can get called multiple times for the
// text in a single element
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string
{
    [textInProgress appendString:string];
}

@end


What i like about this method is that it is fast due to its event-based nature, but on the other hand i need to write a new parser every time i have a new xml schema. 
I have already tried to use ObjectiveResource to parse amazon xml schemas but it was a little bit challenging, hopefully i will find some time soon to grab the source code and modify it to do that.

If you have done any amazon xml parsing using a better method, you are welcome to share.

No comments: