How to write an XML parser app with the SDK

One of the easiest apps to write is one that simply pulls data from the web, thanks to how Apple set up their frameworks. In this post I’m going to walk you through fetching and parsing an XML file from the web in the iPhone SDK. What we want to make is something like this:

NSURL → Parser.m → NSDictionary

Let’s start off with an example URL. For this blog post, I’m going to create an app that searches for gas stations near a zip code. I’ll be using Yahoo’s Local Search API, a very handy little framework that spits back local business results when given a zip code (or GPS coordinates, ;)).

Let’s start off by defining some variables.
NSString *appID = @"exampleKey"; NSString *query = @"gas%20station" NSString *zip = @"90210"; NSMutableArray *results = [[NSMutableArray array] retain];
So off we go, let’s create the URL for the request (as defined by Yahoo’s API). The base URL looks like this:
http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=APP_ID&query=QUERY&zip=ZIP_CODE
Let’s go ahead and fill in those parameters in our program so we can get an NSURL object. Should look like this:
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://local.yahooapis.com/LocalSearchService/V3/localSearch?appid=%@&query=%@&zip=%@", appID, query, zip]];
Now all we need is something to parse this URL with. Luckily, Apple provides a very handy class to do that called NSXMLParser. Basically what NSXMLParser does is it accepts an NSURL, downloads the XML file, and goes through it node by node, calling methods that you define at certain intervals with node information as parameters.

Now we have to decide which delegate methods we want to implement - a look at the NSXMLParser documentation shows a whole variety of delegate methods. For this application, we only need a couple - basically, we want to store every business result as a separate NSDictionary object, and in the end return an NSMutableArray of these NSDictionarys. So let’s define two more global variables, this time an NSMutableDictionary for temporarily storing the result, and two NSStrings for storing the names of a result’s children nodes as they are parsed:
NSMutableDictionary *currentResult; NSString *currentElement; NSString *lastElement;
And then let’s go ahead and implement some of those delegate methods…
/*** Called when the parser runs into an open tag (<tag>) ***/ - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { if ([elementName isEqualToString:@"Result"]) { currentResult = [NSMutableDictionary dictionary]; } else { currentElement = [elementName copy]; } } /*** This is just to resolve random HTML entities that would otherwise cause the parsing to break ***/ - (NSData *)parser:(NSXMLParser *)parser resolveExternalEntityName:(NSString *)entityName systemID:(NSString *)systemID { return [entityName dataUsingEncoding:NSASCIIStringEncoding]; } /*** Called when the parser finds text between an open and close tag. We add this to the currentResult, using the currentElement (node name) as the key ***/ - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { if (!currentElement) { NSString *last = [currentResult objectForKey:lastElement]; [currentResult setObject:[last stringByAppendingString:string] forKey:lastElement]; } else { [currentResult setObject:string forKey:currentElement]; lastElement = currentElement; currentElement = nil; } } /*** Called when the parser runs into a close tag (</tag>). If it is the Result tag that is closing, we should add the currentResult to the array, and then forget about it ***/ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if ([elementName isEqualToString:@"Result"]) { [results addObject:currentResult]; currentResult = nil; } }
Whew. So that’s our parser. Basically, we need run the NSXMLParser object (after setting this class as its delegate), and after it’s done running, like magic we have a populated NSArray. So let’s do it:
NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url]; [parser setDelegate:self]; [parser parse]; [parser autorelease];
And that’s it! We now have a NSArray 100% full of useful information. To pull data out, try something like this:
NSDictionary *gasStation = [results objectAtIndex:0]; NSLog(@”%@ at %@”, [gasStation objectForKey:@"Title"], [gasStation objectForKey:@”Address”]);
Which prints:
$ Shell at 9602 Chimney Rock Rd
Whew! It’s finished! You can take this array of results and do whatever you want with it - put it in a table, add it to a local database, throw it up on a text field - the possibilities are endless! That wasn’t so hard now, and it really makes you appreciate the beauty in the way Apple uses delegate methods to make complex tasks simple.

Also, I’ve opened the forums again as a place to share code / ask for help. Go ahead and sign up, new members are welcome.

Check back for more tutorials
- Joe

5 Responses to “How to write an XML parser app with the SDK”

  1. Skyone Says:

    Is this the uber fine method you used in the greatly-skinned PizzaNow app?

  2. bbr Says:

    isnt there a interface or something like that

  3. bbr Says:

    an NSXMLParserDelegate* interface

  4. David Says:

    Joe,

    Thanks for creating this code. I signed up for a Yahoo appID to use this code specifically. The code compiles successfully but when I check the NSLog it just prints:

    2009-03-11 16:06:20.804 Cells[18856:20b] (null) at (null)

    I haven’t changed any of your code and I was wondering if you encountered the same problem (or have any suggestions to fix it). I copied the generated URL into my browser and it brings up the proper XML file however, it seems like the parser is not saving the “Title” and “Address” into the NSMutableArray. Any suggestions? I’d really like to get this to work and I appreciate any help you can give me.

  5. Todd Says:

    Hi guys, I am a newbi to iphone app development. I have to parse a xml and retrieve its node values. Can some one post me a sample code?

    My data from web server (Response): Todd25

    My client code:

    #import “MainView.h” #import #import #import #import #import #import #import “NSDebug.h” #import “NSXMLParser.h”

    @implementation MainView - (IBAction)ShowText { //NSZombieEnabled = YES; NSArray *keys = [NSArray arrayWithObjects:@”username”, @”password”, @”preference”, @”uid”, nil]; NSArray *objects = [NSArray arrayWithObjects:@”accuser”, @”accpass”, @”abc_region”, @”100″, nil]; NSDictionary *theRequestDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; NSDictionary *otherRequest = [NSDictionary dictionaryWithDictionary:theRequestDictionary];

    NSURL *theURL = [NSURL URLWithString:@”http://localhost/ApiService/Default.aspx”]; NSMutableURLRequest *theRequest = [NSMutableURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];[theRequest setHTTPMethod:@”POST”];

    [theRequest setValue:@”text/xml” forHTTPHeaderField:@”Content-Type”]; NSString *theBodyString = @”SetAthleteBlog667AEBC232322-7623-2322-122311sampleAthlete Sample Blog Text”; NSLog(@”%@”, theBodyString); NSData *theBodyData = [theBodyString dataUsingEncoding:NSUTF8StringEncoding]; [theRequest setHTTPBody:theBodyData]; NSURLResponse *theResponse = NULL; NSError *theError = NULL; NSData *theResponseData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:&theResponse error:&theError];

    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theResponseData]; [parser setDelegate:self]; [parser parse];

    //NSMutableString *mutableddata = [[NSMutableString alloc] initWithData:theResponseData encoding:NSUTF8StringEncoding]; /*NSXMLParser *parser = [[NSXMLParser alloc] initWithData:theResponseData]; [parser setDelegate:self]; [parser parse]; [parser release];*/

    mainText.text=@”success”; }

    - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict { if (qName) { elementName = qName; } }

    @end

    Also, I have some problem in firing the didstartelement method.

    Can some one troubleshoot me. Please Its very urgent:

    Thanks in Advance!

    Todd.

Leave a Reply

*
To prove you're a person (not a spam script), type the security word shown in the picture. Click on the picture to hear an audio file of the word.
Click to hear an audio file of the anti-spam word