Monday, August 8, 2011

Parsing XML using NSXMLParser

In this post we shall have a look at the NSXMLParser class which is used to parse the xml with the help of its delegate methods.

To study in more detail about this class you can always have a close look at the documentation where everything is explained in details about this class.

Design Phase: For this post i shall parse the xml file present online i.e. I will be parsing the rss feeds of apple, here's the view at the final output that we will have once you finish reading this post.



Step 1: Open Xcode and create a windows based application and give the project an appropriate name and after doing that add the table view controller subclass file into your project.






After adding the subclass file add one more file which will be the subclass of the NSObject class the reason why we are adding this file is because in future if it is required to parse one or more nodes from the xml file then in that case we can just create new properties and store the data in these properties which later can be assigned to a mutable array (You will understand this part as we proceed with the tutorial) also the properties that we create in this file act as a temporary storage of the node that we parse.



Once you are finish adding the NSObject subclass declare the properties for xmlLink and xmlTitle which will be of NSString type and don't forget to synthesize them.

Here's a view at the .h file of the NSObject subclass, name of my NSObject subclass file is XMLStringFile you may give any name of your choice


#import <Foundation/Foundation.h>


@interface XMLStringFile : NSObject 
{
NSString *xmlLink,*xmlTitle;
}
@property (nonatomic,retain) NSString *xmlLink,*xmlTitle;
@end



and here's the view at the .m part of the NSObject subclass


#import "XMLStringFile.h"


@implementation XMLStringFile
@synthesize xmlLink,xmlTitle;
@end



Step 2: Now select the tableviewController subclass file and add the following code.


#import <UIKit/UIKit.h>
#import "XMLStringFile.h"
@interface MainRssTableViewController : UITableViewController <NSXMLParserDelegate>
{
//mutable array to store the data from the rss feeds and then display it inside the table using the tables delegate and datasource method.
NSMutableArray *rssOutputData_MutableArray;
//to store the data from the xml node
NSMutableString *nodecontent;
//declaring the object of NSXMLParser which will be used for parsing
NSXMLParser *xmlParserObject;
//declaring the object of the NSObject subclass file
XMLStringFile *XMLStringFileObject;
}
@end



maximum part of the code is explained via comments.

After declaring all the variables its time to allocate memory for the variables that you declared



// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad {

    [super viewDidLoad];
    
    rssOutputData = [[NSMutableArray alloc]init];
    
    //declare the object of allocated variable
    NSData *xmlData=[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:@"http://images.apple.com/main/rss/hotnews/hotnews.rss"]];
    
    //allocate memory for parser as well as 
    xmlParserObject =[[NSXMLParser alloc]initWithData:xmlData];
    [xmlParserObject setDelegate:self];
    
    //asking the xmlparser object to beggin with its parsing
    [xmlParserObject parse];
    
    //releasing the object of NSData as a part of memory management
    [xmlData release];
}



Step 3: Now its time to add the NSXMLParser delegate methods, the delegate methods that i have used for the parsing are given below with their appropriate meaning

a)- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedNameattributes:(NSDictionary *)attributeDict


The above delegate method is sent by a parser object to its delegate when it encounters a start tag for a given element.


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


The above delegate method is sent by a parser object to provide its delegate with a string representing all or part of the characters of the current element.


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


The above delegate method is sent by a parser object to its delegate when it encounters an end tag for a specific element.


To know more delegate method of the NSXMLParser have a look at this link.

Step 4: Implementing the delegate method






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

{
//if the element name is item then only i am assigning memory to the NSObject subclass
if ([elementName isEqualToString:@"item"]) 
{
XMLStringFileObject = [[XMLStringFile alloc]init];
}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
//what ever data i am getting from the node i am appending it to the nodecontent variable after i trim that data for white space or new line character
[nodecontent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
//i am saving the data inside the properties of the XMLStringFileObject from the nodecontent variable
if ([elementName isEqualToString:@"title"]) 
{
XMLStringFileObject.xmlTitle = nodecontent;
}
else if ([elementName isEqualToString:@"link"]) 
{
XMLStringFileObject.xmlLink = nodecontent;
}
//finally in the end when the item tag is encountered i am adding the data inside the mutable array 
if([elementName isEqualToString:@"item"])
{
[rssOutputData_MutableArray addObject:XMLStringFileObject];
[XMLStringFileObject release];
}
//release the data from the mutable string variable
[nodecontent release];
//reallocate the memory to get the new data from the xml file
nodecontent = [[NSMutableString alloc]init];
}





Step 5: Now finally before running this application perform some memory management stuff in the dealloc method and then inside the app delegate.m file create the object of the UITableViewController subclass and then add this class view to the window





- (void)dealloc 
{
[rssOutputData_MutableArray release];
[xmlParserObject release];
        [super dealloc];
}

Now we have to set the detailLabel and textLabel of the table view and that is done inside the cellForRowAtIndexPath delegate method of table view

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Configure the cell...
[cell.textLabel setFont: [UIFont fontWithName:@"Verdana" size:12]];
[cell.detailTextLabel setFont:[UIFont fontWithName:@"Verdana" size:12]];
cell.textLabel.text = [[rssOutputData_MutableArray objectAtIndex:indexPath.row]xmlTitle];
cell.detailTextLabel.text = [[rssOutputData_MutableArray objectAtIndex:indexPath.row]xmlLink];
    
    return cell;
}

Now after doing this run your application and you will get the output which looks like this




You may download the source code from here

i hope that this post has helped you out in understanding the concepts of xml parsing, if you have any queries related to xml parsing feel free to ask me via comments, mail or via facebook until then happy iCoding and have a great Day.

17 comments:

  1. @erhan: The datasource methods are very simple all you have to do is set the section to one and set the rows in the section as per the mutable arrays count.

    If their is still confusion then in that case let me update the above code

    ReplyDelete
  2. Nice tutorial bro... But it has got memory leaks and i'm unable to fix it....

    ReplyDelete
  3. Good job, it was very helpful for me.
    Thanks

    ReplyDelete
  4. Thanks

    My use DidLoad and change row line.

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
    return 10;
    }

    ReplyDelete
  5. Hi bilgisayarteknikservis,

    The number of rows in section should return the row count as per the array count, hardcoding the row count is ok but not a good practice.

    I have uploaded the source code you may have a look at that

    ReplyDelete
  6. hello friends,
    on this event didSelectRowAtIndexPath i want to open url in next page on web view
    how can i load that url in web view

    ReplyDelete
  7. Hi Radix,

    first sorry for my bad english.

    This is a great Tutorial and it works fine. The parsed Data are loaded into my Tableview und on Tap it open the hyperlink.

    I have one Problem. When the parsed Text had special characters like ä ü ö - as first character of an word, the output in the tableview is for example: (german) "LKW-Fahrerübersieht"... correct are
    "LKW-Fahrer übersieht"

    or: " in Hamburgsältestem" correct are " in Hamburgs ältestem "

    have you a solution for this problem?

    ReplyDelete
    Replies
    1. it happens only, when a special character is the first ore the last character of an word.

      Delete
    2. @Chrischi: See you need to understand that if the server is providing a character with ASCII value sitting on the top of it's head then we cannot do anything because its generated from the server, so server side changes needs to take place.

      2) If you want to replace those characters with proper alphabets then what i believe is that you have to undergo a logical loop to solve this kind of issue, make use of NSCharacterSet and if you have any issues then just post it as a comment.

      Delete
    3. I found the solution:

      i#ve changed: [nodecontent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];

      to

      [nodecontent appendString:[string stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]]];

      ;-)

      Delete
    4. @Chrischi81: That's Great and you just thought all of us something new, so kudos for that...

      Delete
  8. useful tutorial..what to do if I need an image to get parsed and shown alongside the title, instead of the link which is detailText?

    ReplyDelete
  9. @Bhaskar : For the image i guess you need to get the image link in the xml node something like this

    www.someDomain.com/img/ironman.png

    Parse the image and display it besides the title in your table view.

    Hope this helps.

    ReplyDelete
  10. Hi,
    How how could i add element that occur multiple times XML ... It always shows only one value the last one.

    Thanks

    ReplyDelete
  11. thank you so much.

    ReplyDelete