How to use nsxmlparser in Iphone SDk?
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.
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
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
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.
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
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
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];
}