iPhone SDK Articles

Friday, December 19, 2008

Parsing XML Files


Reading XML files is one of the common tasks we perform in our application, In this tutorial let's see how we can use NSXMLParser to parse XML in our iPhone app.
Introduction
NSXMLParser is a forward only reader or an event driven parser. What it means is, an event is raised whenever the parser comes across a start of an element, value, CDATA and so on. The delegate of NSXMLParser can then implement these events to capture XML data. Some of the events are raised multiple times like the start of an element, value of an element and so on. Since NSXMLParser is known as an event driven parser, we can only read data at the present node and cannot go back. The iPhone only supports NSXMLParser and not NSXMLDocument, which loads the whole XML tree in memory.

Books Application
To understand how to use an instance of NSXMLParser, let's create a simple navigation based application where we will list the title of the book in the table view and upon selecting a title, display the detail information in a detail view. Click here to see the sample XML file, used in this application.

Create a new application in XCode by selecting Navigation-Based Application, I have named my app XML. Since the NSXMLParser is a forward only parser or an event driven parser, we need to store the data locally, which can be used later. To store this data, we will create a class which replicates the elements and attributes in the XML file. An instance of this class represents one single Book element in the XML file. I have named this class "Book" and its source code is listed below

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

@interface Book : NSObject {

NSInteger bookID;
NSString *title; //Same name as the Entity Name.
NSString *author; //Same name as the Entity Name.
NSString *summary; //Same name as the Entity Name.

}

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

@end

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

@implementation Book

@synthesize title, author, summary, bookID;

- (void) dealloc {

[summary release];
[author release];
[title release];
[super dealloc];
}

@end

Notice that the name of the property is the same as the element name in the XML file. Since the XML file has n number of Book elements, we need an array to hold all the books we read, so we declare an array in the application delegate and this is how the source code changes

//XMLAppDelegate.h
#import <UIKit/UIKit.h>

@interface XMLAppDelegate : NSObject <UIApplicationDelegate> {

UIWindow *window;
UINavigationController *navigationController;

NSMutableArray *books;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@property (nonatomic, retain) NSMutableArray *books;

@end

The Delegate
To keep the source code clean, we will also declare a delegate, which will be used by the instance of NSXMLParser and this how its source code looks like

//XMLParser.h
#import <UIKit/UIKit.h>

@class XMLAppDelegate, Book;

@interface XMLParser : NSObject {

NSMutableString *currentElementValue;

XMLAppDelegate *appDelegate;
Book *aBook;
}

- (XMLParser *) initXMLParser;

@end

Let's look at how the variables will be used. currentElementValue holds the current element value, appDelegate so we can access the array which holds the list of books and finally a reference to the Book class itself. Notice that we do not keep track of the current element name being processed, because the event will tell us that. Finally, we have a constructor called initXMLParser and let's see what it does

//XMLParser.m
- (XMLParser *) initXMLParser {

[super init];

appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate];

return self;
}

Very simple, gets a reference to the application delegate and returns itself.

Parsing the XML File
Now that we have everything set up, let's look at the code to read the XML file

//XMLAppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {


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

//Initialize the delegate.
XMLParser *parser = [[XMLParser alloc] initXMLParser];

//Set delegate
[xmlParser setDelegate:parser];

//Start parsing the XML file.
BOOL success = [xmlParser parse];

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

// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

The code is very simple, we create an instance of NSURL, create an instance of NSXMLParser, initialize the delegate, assign the delegate and start parsing by passing the parse message. It returns YES, if the parsing is successful, NO if there is an error or if the operation is aborted.

Parsing the start of an element

The delegate of the parser does not have to implement all the methods that it raises, so we can pick and choose which events we care about. If we do not want to handle the event when the parser starts reading the document, we can choose to ignore it by not implementing it. We will only implement three methods which is called when the parser encounters the start of an element, end of an element or value of an element.

Let's look at parser:didStartElement:namespaceURI:qualifiedName:attributes method which is called when the parser encounters the start of an element.

//XMLParser.m
- (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.
aBook = [[Book alloc] init];

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

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

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

From the above code we first initialize the array when it encounters the "Books" element, which can also be done in parserDidStartDocument method. If the element is "Book" then we initialize the local book object and read the attribute of the present XML book element from the attribute dictionary object.

Parsing an element's value
Now that we have a local book object representing the current book element in the XML tree, the next thing to do is to populate the local object with the XML data. The parser now moves to the title element and the same method is called again, but this time we do not do anything. Parser then moves to the element value and it sends parser:foundCharacters event to the delegate, let's see how the code look like

//XMLParser.m
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {

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

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

}

The code is very easy to read, if the mutable string is nil then we initialize it with the string parameter. If the currentElementValue is not nil then we simply append the data to the existing string value.

Parsing the end of an element
The parser now moves to the end of the element and hence parser:didEndElement:namespaceURI:qualifiedName is sent to the delegate. This is where we set the currentElementValue to the correct property of the local book object and set the currentElementValue to nil. This is how the code looks like

//XMLParser.m
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {

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

//There is nothing to do if we encounter the Books element here.
//If we encounter the Book element howevere, we want to add the book object to the array
// and release the object.
if([elementName isEqualToString:@"Book"]) {
[appDelegate.books addObject:aBook];

[aBook release];
aBook = nil;
}
else
[aBook setValue:currentElementValue forKey:elementName];

[currentElementValue release];
currentElementValue = nil;
}

If the element it encounters is "Books" then there is nothing to do as we are almost done reading the file. If the element name is "Book" then we add the book object to the array and set the local book object to nil and release its memory, so it can be used again. If the end element is not "Books" or "Book" then it must be one of the sub element of "book" and we set the currentElementValue to the current book property using setValue:forKey. We can do this, because the properties declared in the book is the same as the XML element names.

The cycle starts again by initializing the book object and reading the attribute, reading the children elements and setting its value to the local object and finally adding the object to the array. The parser calls the three functions again and again as long as it does not encounters eof.

Complete listing of XMLParser.m file

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

@implementation XMLParser

- (XMLParser *) initXMLParser {

[super init];

appDelegate = (XMLAppDelegate *)[[UIApplication sharedApplication] delegate];

return self;
}

- (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.
aBook = [[Book alloc] init];

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

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

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

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

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

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

}

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

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

//There is nothing to do if we encounter the Books element here.
//If we encounter the Book element howevere, we want to add the book object to the array
// and release the object.
if([elementName isEqualToString:@"Book"]) {
[appDelegate.books addObject:aBook];

[aBook release];
aBook = nil;
}
else
[aBook setValue:currentElementValue forKey:elementName];

[currentElementValue release];
currentElementValue = nil;
}

- (void) dealloc {

[aBook release];
[currentElementValue release];
[super dealloc];
}

@end

This is how the data looks like in the table view and the detail view controller.

The data is then shown in a UITableView with a detail view, the complete code is not shown here but you can download the source code and to get a better understanding UITableView, you can follow my suggested readings below.

Summary
Reading XML files is very easy and it can be done with only three methods as seen above. I hope this tutorial has got you started in reading XML files.

Happy Programming,
iPhone SDK Articles



Attachments
Suggested Readings

56 comments:

Krzysiek said...

Thanks for your tutorials. It's pretty much work and You, Jai are doing it for free. Do you have anything in appstore?

And please: I guess most of ass want to learn it. Show as how to implement some animations/transitions between views like page flip and others. And maybe some kind of blue pop-up msgboxes.

Thanks again
Chris [Poland]

Niranjan Singh said...

I downloaded the sample code.Build failed with error "error:UIKit/UIKit.h:No such file or directory"

I have Mac OS X 10.4

What needs to done to build it....

Thanks in advance,Niranja

Anonymous said...

I love your tutorials. Can you make another one using SQLite with a Picker that holds data. Thanks, keep up the good work.

Trond Klakken said...

Just love your tutorials! Making it really easy jumping from the pc platform to iPhone/mobile obj-c programmering :)
Thank you so much!

rexmont said...

Great tutorial!

I want to ask another thing:

How can I add a picture box into the detail page? Simply I want to display books' pictures in the detail page...

Thanks again!

iPhone SDK Articles said...

Yes, I' am planning to write one soon.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

Hi Chris,

Glad you enjoyed the tutorials. I' am working on some tutorials and will put up on the site soon.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Trond Glad you enjoyed the tutorials.

Happy Programming,
iPhone SDK Articles

Katana said...

Thanks for the great tutorials! Very easy to catch on...

A quick question, is it possible like rexmont said to add a UIImageView to the detail page? and is it possible to make a row in the detail view to also carry a link, so when a user taps it, it opens a url like a button.

Katana said...

would it also be possible to create a search bar so a user could search for a certain book?

Pietje321 said...

Hi there.

I`m currently trying to get the xml appi to work. If i use the normal xml tags structure everything works fine. But an other source xml has a slightly different structure. Like this:

(begintag)Books(endtag)
(begintag)Title= "Lorem"(/endtag)
(begintag)Author= "John Smith"(/endtag)
(begintag/)Books(endtag)

Could`t post the real xml with real begin and end tag so i wrote i down like this.


I can`t get it to work with this structure. Could someone please give me some directions on how i could it to work . Thanks very much in advance

iPhone SDK Articles said...

@Katana I have a tutorial on searching UITableView here http://www.iphonesdkarticles.com/2009/01/uitableview-searching-table-view.html

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Katana I think this tutorial will answer your question

http://www.iphonesdkarticles.com/2009/01/uitableview-loading-detail-view.html

The idea is to send something from one object to another object using parameters.

Please let me know if I can be of any more help.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

Pietje321 Is the XML valid?

Is it possible to prvide the XML source so I can test it out?

Happy Programming,
iPhone SDK Articles

vii said...

Is it possible to bundle the XML file with the app and read it locally instead of reading it in from a URL?

Thanks.

Rick said...

For some reason i get a lot of white spaces in mijn strings like this:

2009-02-25 00:04:37.726 XML[21466:20b] Processing Value:

Leader voor het SBS 6 programma 'Lachen het jaar in'


Any idea why?
Thanks for this tutorial!

Rick said...

I noticed that when I put all my XML code on one line it works.. does someone know why? :)

Reetu Raj said...

Thank you sooooo much !!!!! for this code !!
you rock !!

iPhone SDK Articles said...

@Rick

I think you created the XML file with line breaks in them.

iPhone SDK Articles said...

@Reetu

Glad you it helped you

Happy Programming,
iPhone SDK Articles

Däjn said...

Is is possible to use a local .xml file in this appliction?
I downloaded the code ad tried to write in the path to my own .xml file but it didnt work?

Thanks :)

Ralf said...

I'm looking for the same answer as "vii" does. But i can't find any. Is it possible to use a local xml file instead of an URL.

Another question. How do you do if you want another step of categories.
Example:

Booktitle -> bookinfo -> images
tableview tableview tableview

It will be very useful :]

Thank you!

This turtorials rocks!!
//Ralf from Norway

Gadget said...

Hello!

In my XML. the element 'Book' consists of attributes only instead of more elements, how do i get the value of them to show in the DetailView???

thank you, awsome tutorial btw!

iPhone SDK Articles said...

@Gadget

In this method
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName
attributes:(NSDictionary *)attributeDict {

all the attributes for the Book element will be stored in attributeDict dictionary variable. You can get the attribute value like this

[[attributeDict objectForKey:@"id"] integerValue];

where "id" is the name of the attribute and the above line will give you the attribute value as integer.

I hope this helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Dajn @Ralf

Save the file in the resource folder and use this code

NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *DataPath = [Path stringByAppendingPathComponent:@"xmlfile.xml"];
NSData *Data = [[NSData alloc] initWithContentsOfFile:DataPath];
NSXMLParser xmlParser = [[NSXMLParser alloc] initWithData:Data];

Now that you have the NSXMLParser object, follow the same steps as this tutorial.

//Initialize the delegate.
XMLParser *parser = [[XMLParser alloc] initXMLParser];

//Set delegate
[xmlParser setDelegate:parser];

//Start parsing the XML file.
BOOL success = [xmlParser parse];

XMLPraser is a custom class created for this tutorial.

I hope this helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Ralf

are you speaking of a drill down table view? If yes, I will be publishing a new tutorial which will show you how to do it.

Happy Programming,
iPhone SDK Articles

Gad said...

Is it possible to put in a searchBar and search in the xml and then have the result in showing in the rootTableView??

That would be very nice :]

Nikeo said...

What if you want to have a searchBar in the rootView, where u can search on booktitles or something ?

iPhone SDK Articles said...

@Gad I have a tutorial on searching the table view here
http://www.iphonesdkarticles.com/2009/01/uitableview-searching-table-view.html

After you have parsed the XML file you can search the table view in the same way, as show in the tutorial above.

I hope this helps.

Happy Programming,
iPhone SDK Articles

RulleBulle said...

In my app I created a third tableView, But i can't get the xml data showing there. I used the didSelectRowAtIndexPath method in the second view just like u did on the rootView, Do you now what to do, do i have to send som kind of ID ?

thanks :]

chris said...

Awsome tutorial

How can you do the opposite, I mean convert the

NSMutableArray *books;

to xml and then convert the xml to NSData?

Thanks

cioannou said...

What happens when you want to save the contents of

NSMutableArray

into XML file?

Thanks

iPhone SDK Articles said...

@cioannou You can save the contents of an NSMutableArray like this [array writeToFile:path atomically:YES];

Hope this helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Chris You can try saving the array

[array writeToFile:path atomically:YES];

to the disk and get it as NSData like this

NSData *data = [[NSData alloc] initWithContentsOfFile:path];

Hope this helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@RulleBulle You need some kind of mechanism to pass data to the table view. If you can send me an email with some more information on how the data is designed, I will be able to help you more.

This tutorial may help you
http://www.iphonesdkarticles.com/2009/01/uitableview-loading-detail-view.html

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@Nikeo This tutorial show how to add a UISearchBar to a UITableView

http://www.iphonesdkarticles.com/2009/01/uitableview-searching-table-view.html

Hope this helps.

Happy Programming,
iPhone SDK Articles

RulleBulle said...

Ok, I did take a look at that tutorial, but i just end up with the same result. It works just great with 2 viewControllers, but it's the third one.

I did also send ju an email as you said :]

help :=)

Kiddo said...

if you just wanna add another view , say like ImageViewController.xib
And when you select a row in the first tableview a another xml is loading with images and so on.

How do I get a connection between the second third view and parsing a new xml?=) lot o questions. maybe just like RulleBulle?

thanks

Higgins said...

Hi there - I've written an XML parser based on this code, and it works! However, it looks like ampersands (& characters) are not showing up. My XML (UTF-8 encoded) has ampersands in some strings, like "Joe's Bar & Grill." What I'm getting out of the parser is just "Joe's Bar Grill."

Is an ampersand considered some special character in XML?

RolYroLLs said...

Works great! Awesome Tutorial.

As other do, I have a question. One of the elements i am passing is either a 1 or a 0. I need to know which value it is before doing something with the data. I've been searching up and down for the past 2 days now and I got no luck. I've tried to convert it to a string and compare it string1.isEqualToString(string2) with no luck.

For example purposes, with your example in mind, how can i campare the bookid to a number.

Thanks,
RolYroLLs

iPhone SDK Articles said...

@Higgins Try replacing & with & and see what happens.

Hope this helps.

Happy Programming,
iPhone SDK Articles

iPhone SDK Articles said...

@RolYroLLs are you not able to compare the umber like this if (bookid == 1) { }

If it gives you can error can you tell me what the error is?

Happy Programming,
iphone SDK Articles

iPhone SDK Articles said...

@Kiddo Clicking on a row loads the image view controller and also parses another XML file for images. Now you want know how to connect what the clicked row and the image?

I think I am confused by the question.

Are you parsing the same XML file whenever a row is clicked in the root view controller?

In your XML file you should have some kind of a foreign key with which you can find out which image to use based on the row clicked.

Hope this helps.

Happy Programming,
iPhone SDK Articles

Kiddo said...

hehe okej sry for my english :]

What i mean is that i have a 3 xml files.

The first one I parse just like you. But the xml just contains the element 'bookname' with the attribute 'id'. So you can search the bookname and then fill the tableview.

The second xml i suppose to parse when you click a row in the first tablewview. In the second xml there are more elemens like 'autour' etc. but it also contains the element 'bookname' with 'id'. But i can't get the author elements to parse and show in the second tableview.

The third xml is suppose to be like the on before. It contains the ''bookname' element with 'ID' and then more element with 'image' url.
This xml should be parsed when you click a row in the second tableView.

All this maybe seems to be a little bit confusing. The thing is that i have 3 XML files with different info but the same ID. Each XML should be parsed in a tableview.

:):):)

iPhone SDK Articles said...

@Kiddo If you do not mind emailing your code I will be happy to take a look at it.

Happy Programming,
iPhone SDK Articles

Octavian said...

Hi, Thanks for the article very useful but I have a question :

I try to save the NSMutableArray into a xml file like
You said [array writeToFile:path atomically:YES]; and didn't save a thing...

I must include or import an Framework?

Thanks You

Däjnmaster said...

Hey!

I've just tried do add another view where i want to show some pictures.

But my problem is that I can't pass the data value to the third view?

It's work just fine in the two first view, but in the third it always just takes the data from the first cell in the XML.

Do you think you can post some code or tip for me?=) Like your tutorials adder wise.

Ragnar said...

Hey!

I've created a app just like yours. But in the second View(detailView) I want to parse another XML file who will show it's data in a third View?

I tried do this myself but i wont work..

could someone help me out with this. I would be eternally grateful.

Justin said...

First, very nice tutorials u got here!

But if I don't want the summary element to display in the second View?

Instead I want to add a third View where summary could be display for it self. I've tried this many times. But I just can't send data between more then 2 Views.

Pranathi said...

Hi, I am looking for a way to create an XML file from an IPhone application. Can you give me any pointers/sample code for the same?

Thanks

Arindam Dey said...

Many many helpful document

Anonymous said...

Looks like there is a new parser on the block:
http://touchtank.wordpress.com/element-parser/

Dean said...

Hey, great tutorial. My question is how you could adapt this tutorial to handle more than one XML file, with different tags to run like this tutorial.

Anonymous said...

Hi (sorry i cant speak engels)

How i give more information by summary the mac is 32 element than you see '...' how i make that box bigger for more information?

Mitul Gogoi said...

This tutorial really helped me to learn xml parsing.
Suppose, there's also an image along with title,author,summary in the xml.How can i display an image from web in a cell of Table in Detailed View?Do I need to have a url-link of the image in a tag(e.g. start-tag http://...end-tag) in the xml and again load the image from the web by a http request and display it in ImageView in a cell of the Group Table?or, there's a different approach to load an image in detailed view through xml?

Any help is greatly appreciated.

Mitul Gogoi said...

Thank you for this great tutorial;this really helped me to learn xml parsing.
Suppose there's an image along with title,author,summary defined in the xml.How can you display the image in the detailed view in the group table?
Do I need to have a url link in a tag in xml and have a http request and load the image in a ImageView in a cell in the group table?

Any help is greatly appreciated.