Parsing XML files with Objective-C

Perhaps the simplest app to create is a blog feed reader. Typically you will not find a native mobile app that’s there only to display a feed. Normally, this is a task best left for a website or a web-based application.

Starting with the basics, RSS feeds are typically XML, which uses special tags to tell the page what type of information is being displayed. For example, instead of in text displaying “Author: Joe Shmoe”, the code would look more like

<author>Joe Shmoe</author>

The first format is more friendly to someone viewing the XML file in the browser and the other is more friendly to search engines and browsers. The other advantage to using XML over plain text is that it is easy to break it down into the individual parts rather than only looking at text to split the file apart. We are going to be looking at “Books.xml.”

How do we do this in our apps then? Let’s start up XCode and get to work. First, lets create a new project, I named mine RSSFeed –Note I am running XCode 4.2  on Mac OSX Lion which is the latest version. So it may look slightly different for you – When setting up the project, select “Single View Application” as the project type and make sure Automatic Reference Counting (ARC) is not checked. This will set us up with a view controller, an app delegate, and an nib file. Nib files are the visual side of the application.

Create a new project in Xcode 4

We need to create a file to hold the information of the current item we are looking at, which in this case would be a book. so we need to create a new subclass of NSObject. To do so, go to File -> New -> New File. Under iOS select the cocoa touch section then choose Objective-C class.

Add a new Objective-C class

I named mine Book. There are four items that we need to store based on the XML file: author, title, summary, and the book index. Author, title and summary are all strings, while the book index is an integer.

This is a very simple object and only consists of variables to store data, no methods are necessary outside of the dealloc function. Just like in any other application, we need to create the variables in the header file and create the properties for them. Then in the implementation file, we need to synthesize those variables and release them in the dealloc function. Here is how they should look.

 // Book.h
#import <Foundation/Foundation.h>

@interface Book : NSObject {
    NSInteger bookID;
    NSString *author;
    NSString *title;
    NSString *summary;
}

@property (nonatomic, readwrite) NSInteger bookID;
@property (nonatomic, retain) NSString *author;
@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *summary;

@end 

 

// Book.m
#import "Book.h"

@implementation Book

@synthesize author, title, summary, bookID;

- (void) dealloc
{
    [author release];
    [summary release];
    [title release];
    [super dealloc];
}

@end 

Now that we have that set up, lets create the parser file. We need to go through the same process to create a new Objective-C based class, call this one rssParser. The following code should be added to the rssParser.h file:

#import <UIKit/UIKit.h>

@class Book, AppDelegate;

@interface rssParser : NSObject <NSXMLParserDelegate>{

    Book *book;
    AppDelegate *appdelegate;

    NSMutableString *curElem;

}

@property (nonatomic, retain) Book *book;
@property (nonatomic, retain) AppDelegate *appdelegate;
@property (nonatomic, retain) NSMutableString *curElem;

- (rssParser*) initXMLParser;

@end

As you can see, this file uses the UIKit framework. Right below that you have what are called wrapper classes. When you use wrapper classes you are allowing your class to access not only methods, but variables of those classes, which in this case are Book and AppDelegate. In order to reach those variables and methods, we need to create instances of those classes. That is what Book *book; AppDelegate *appdelegate; accomplishes. Then we create a string to hold the current element. Just like before, we need to add the properties and then synthesize the variables.

I know this is a bit dry right now, but it is the basic setup to get everything working. You will also see the method declaration for the initXMLParser function which we will get into next. Now we have to connect the pieces within the rssParser class’s implementation file. Add the following headers to the top of rssParser.m to connect the files together: AppDelegate.h and Book.h. Remember that initXMLParser function we declared? Well here is the code that makes up that function.

As I’m sure you are able to tell, this method initializes all of the parts necessary to run the parser. First you initialize the parser itself, and then the AppDelegate. Let’s jump over to the AppDelegate.h and AppDelegate.m files to insert a little more code before getting a better idea of what is going on here. Starting in AppDelegate.h, add the following code:

- (rssParser *) initXMLParser
{
    [super init];

    appdelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];

    return self;
}

This should not be too different from what the file was originally created with. In reality the only lines of code that have been added to the file is the creation and property of an NSMutableArray called books. Here is the instead of recreating the whole file, only three things need to happen.

  • We need to add a reference to the rssParser.h file so we can have access to it.
  • We need to synthesize our books array
  • Connect the dots and make everything work.

Add the following to the application didFinishLaunchingWithOptions function right after the first line of code in the function.

NSURL *url = [[NSURL alloc] initWithString:@"http://sites.google.com/site/iphonesdktutorials/xml/Books.xml"];
NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithContentsOfURL:url];

rssParser *parser = [[rssParser alloc] initXMLParser];

[xmlparser setDelegate:parser];

BOOL success = [xmlparser parse];

if(success){
	NSLog(@"No Errors");
}
else{
	NSLog(@"Error Error Error!!!");
}

Time to break the code apart. Any time you work with a file within Objective-C you have to reference using a variable of NSUrl data type. Since we are using a file that is not contained within the app, we can initialize that URL using a string. Now we need to create a parser, but note: we are not using the rssParser class yet. Objective C has a built in XML parsing class called NSXMLParser. We are able to use the URL we created to pass the file into the parser to eventually be processed by the application.

In order to move any farther we need to initialize our rssParser class using the initXMLParser function that we created earlier. Also, remember in rssParser.h we had to declare it as an XMLParserDelegate, here is where we apply that to the XML Parser.

The purpose of the delegate is to override basic functionality to give custom processing to the parser, but we will get into that in a few minutes. The last couple lines of code here check to see if the parsing was successful and adds a message to the log within XCode as proof. Note that we use the xmlparser variable to call the parse function. Normally this is run by basic functionality, but we want to customize it. So now we need to jump back into rssParser.m and add the functions to parse the information. This delegate will override three methods that come from the XMLParser class: didStartElement, foundCharacters, and didEndElement.

-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
    if([elementName isEqualToString:@"Books"]) {
        //Initialize the array.
        appdelegate.books = [[NSMutableArray alloc] init];
    }
    else if([elementName isEqualToString:@"Book"]) {

        //Initialize the book.
        book = [[Book alloc] init];

        //Extract the attribute here.
        book.bookID = [[attributeDict objectForKey:@"id"] integerValue];

        NSLog(@"Reading id value :%i", book.bookID);
    }

    NSLog(@"Processing Element: %@", elementName);
}

This function handles what to do when an element is found. In the XML file we are referencing, “Books” is the name of the feed wrapper. So if this is the element that has been found, then it is the start of the file so we initialize the books array we created within the AppDelegate. “Book” (non-plural) is the declares an individual item within the file. Now is when we need to store the data locally within the Book object. Also note that this process records the start of the parsing process within the log file in XCode. Now we move on to the foundCharacters method.

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

    if(!curElem)
        curElem = [[NSMutableString alloc] initWithString:string];
    else
        [curElem appendString:string];

    NSLog(@"Processing Value: %@", curElem);

}

This function is simply used to store the value of the elements as they are bing processed and write them to the log. And for the end of the element:

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
  namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

    if([elementName isEqualToString:@"Books"])
        return;

    if([elementName isEqualToString:@"Book"]) {
        [appdelegate.books addObject:book];

        [book release];
        book = nil;
    }
    else
        [book setValue:curElem forKey:elementName];

    [curElem release];
    curElem = nil;
}

This function does a couple things. First it checks for the wrapper element again. This time, if it is found, it ends the parsing process. If the parser finds the book element again, it takes all of the information found in that element and adds it to the array of books, and then clears the value stored in curElem, to be used by the next element in line. Lets re-cap what this application does from the view of the application process. First, when the application loads, we declare where the XML file is located, create an instance of the XML parser, and push the XML file into the parser.

We wanted to override the normal parsing functionality, so we set the delegate to our rssParser delegate that we created. Within that delegate we override the three functions for processing an XML file: didStartElement, foundCharacters and didEndElement. Within these functions we reference our Book object to temporarily store whatever information is found, write the process to the output log, and when the element has been fully parsed, add the collection of data to an array that could be used later.

Go ahead and run your code now and open the output log in XCode to see whether or not your code has worked successfully. At the end of those logs you should see “No Error” in bold text and you will also see all of the elements that have been processed from the xml file. From this point there are many ways to display your XML feed, so let your creativity run wild!

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Kirk Kennedy

    Tyler, is there a place where I can look at or download the code in its entirety? I’m having trouble following your text and code examples. I think there may be a section of code missing (referencing the creation of the NSMutableArray books). I’m new to objectiveC and I can’t yet fill in all the blanks.
    Thanks,
    Kirk

  • Shadow Caster

    This is the first time I’ve ever seen Objective C code… man is it an ugly language!

  • Christine

    I am having a compiling error in your last to 3rd code snippet.
    Property ‘books’ cannot be found in forward class object (in following line:)

    appDelegate.books = [[NSMutableArray alloc] init];

    it seems to be books was not part of XMLParserAppDelegate class..

    did i miss something here?
    thanks!

    • Christine

      To answer my own questions:
      I added in XMLParserAppDelegate.h
      NSMutableArray *books;

      @Property(nonatomic, retain)NSMutableArray *books;

      and in XMLParserAppDelegate.m

      @synthsize books;

      it seems to have solved the issue.
      thanks.

  • mondousage
  • Jairo

    Hi,

    If you are spanish people, there is another tutorial for do this task:
    http://www.apprendemos.com/tutoriales/ios/parser-xml-recorrer-iphone

    I hope it help you