iPhone - Parse XML to custom objects
The following example is based on AmazonParser by Aaron Hillegass and the SeismicXML demo from Apple iPhone Dev Center.
Aaron's code works well in his example, but if the application needs to parse different XMLs, we may end up re-writing similar code over and over. To make Aaron's code more generic so it can be reused, I have made a few changes.
Let say that we want to process the contacts.xml and turn it into Contact objects on the client side.
<contacts>
<Contact>
<firstname>Joe</firstname>
<lastname>Smith</lastname>
<phone>321-123-4567</phone>
</Contact>
<!-- ... -->
</contacts>
#import <UIKit/UIKit.h>
@interface Contact : NSObject {
NSString *firstname;
NSString *lastname;
NSString *phone;
}
@property (nonatomic, retain) NSString *firstname;
@property (nonatomic, retain) NSString *lastname;
@property (nonatomic, retain) NSString *phone;
@end
#import "Contact.h"
@implementation Contact
@synthesize firstname;
@synthesize lastname;
@synthesize phone;
@end
In the XMLToObjectParser, I have used NSObject to represent any class and to create an instance of that class at runtime using NSClassFromString method. Notice that I have made the tag names the same as the corresponding variable names to take advantage of key-value coding.
#import <UIKit/UIKit.h>
@interface XMLToObjectParser : NSObject {
NSString *className;
NSMutableArray *items;
NSObject *item; // stands for any class NSString *currentNodeName;
NSMutableString *currentNodeContent;
}
- (NSArray *)items;
- (id)parseXMLAtURL:(NSURL *)url
toObject:(NSString *)aClassName
parseError:(NSError **)error;
@end
#import "XMLToObjectParser.h"
@implementation XMLToObjectParser
- (NSArray *)items
{
return items;
}
- (id)parseXMLAtURL:(NSURL *)url
toObject:(NSString *)aClassName
parseError:(NSError **)error
{
[items release];
items = [[NSMutableArray alloc] init];
className = aClassName;
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[parser setDelegate:self];
[parser parse];
if([parser parserError] && error) {
*error = [parser parserError];
}
[parser release];
return self;
}
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
NSLog(@"Open tag: %@", elementName);
if([elementName isEqualToString:className]) {
// create an instance of a class on run-time item = [[NSClassFromString(className) alloc] init];
}
else {
currentNodeName = [elementName copy];
currentNodeContent = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
NSLog(@"Close tag: %@", elementName);
if([elementName isEqualToString:className]) {
[items addObject:item];
[item release];
item = nil;
}
else if([elementName isEqualToString:currentNodeName]) {
// use key-value coding [item setValue:currentNodeContent forKey:elementName];
[currentNodeContent release];
currentNodeContent = nil;
[currentNodeName release];
currentNodeName = nil;
}
}
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string
{
[currentNodeContent appendString:string];
}
- (void)dealloc
{
[items release];
[super dealloc];
}
@end
To drive the parser, we type
NSURL *url = [NSURL URLWithString: @"http://localhost/contacts.xml"];
XMLToObjectParser *myParser = [[XMLToObjectParser alloc] parseXMLAtURL:url toObject:@"Contact" parseError:nil];
// do something with the contact array...
Some final notes, the sample code works fine with XML that has simple structure like that of contacts.xml. For complex structure like earthquake data http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml or if you need more control, you may be better off using TouchXML. Also, the code doesn't handle potential errors properly. I will leave that to you. :-)

Thanks
Again - as a NooB to all of this, does this code stand alone or is it a add in to the mentioned Seismic XML code? Slightly confused.
Cheers GH
Yes, the sample code is independent of the SeismicXML demo. In fact, simply copy XMLToObjectParser.h and XMLToObjectParser.m to your project and you are ready to go. The last code snip shows a sample usage.
Andrew
I have a question that I cant seem to figoure out myself.
What if Joe Smith has several phone numbers ?
Now only the last number will be added to the object :(
You can handle attributeDict using a for loop:
for (id key in attributeDict)
NSLog(@"key: %@, value: %@", key, [attributeDict objectForKey:key]);
So, for <contact name="John Doe">, you should see the following in the console
Open tag: contact
key: name, value: John Doe
Andrew
To get back Contact objects, do
for(int i = 0; i < [[myParser items] count]; i++)
NSLog(@"item: %@", ((Contact *)[[myParser items] objectAtIndex:i]));
Andrew
You need to modify the Contact object a bit to handle multiple phone numbers. Instead of using NSString to store a phone number, use a NSMutableArray object. In the setter function for phone, simply add the given phone number to the array.
Modified Contact
//Contact.h
#import <UIKit/UIKit.h>
@interface Contact : NSObject {
NSString *firstname;
NSString *lastname;
NSMutableArray *phone;
}
@property (nonatomic, retain) NSString *firstname;
@property (nonatomic, retain) NSString *lastname;
- (void)setPhone:(NSString *)value;
- (NSMutableArray *)phone;
@end
//Contact.m
#import "Contact.h"
@implementation Contact
@synthesize firstname;
@synthesize lastname;
- (void)init {
[super init];
phone = [[NSMutableArray alloc] init];
}
- (void)setPhone:(NSString *)x {
[phone addObject:x];
}
- (NSMutableArray *)phone {
return phone;
}
@end
Andrew
<kingtag>
<queen>teej</queen>
<slave>mootal</slave>
<parent>
<desk>
<pencil>kavs</pencil>
<rubber>nataraj</rubber>
</desk>
<desk>
<pencil>kavs1</pencil>
<rubber>nataraj1</rubber>
</desk>
</parent>
</kingtag>
how do i go about parsing it
is it possible to do a check if the key exists?
and if not .. he just skips it because now i get an error
i have a webservice XML i want to put in an object but just certain parts of the xml not the whole XML.