iOS HTTP request and XML parsing example

NSXMLParser is a SAX parser included by default in the iOS. Its pretty straight forward to use if you are familiar with any other SAX parsers. Basically it is a event driven process where as it comes across certain parts of the XML document that it will send notification to a specific function where you can take the necessary action to save that to either memory or SQLite database. Here we get our XML data, which is basically a list of all the countries in the world grouped by the continents they are in, right from a remote server using HTTP request. We cover the following topics in this tutorial
  • Java Servlet based REST web service to get XML data from MySQL database
  • Table view to display our country data in a List
  • Navigation view to go between the country list and the detail information
  • Parsing the XML document to populate a NSMutableArray for data display
  • Go over the parser delegate methods defined in the NSXMLParserDelegate protocol and their responsibilities
iOS HTTP request and XML parsing example iOS display XML data in Table view after parsing iOS NSXMLParser example

You can click on the link given below to look at the sample XML used in this example
http://demo.mysamplecode.com/Servlets_JSP/CountryXMLData

Snippet of the XML data

<?xml version="1.0" encoding="UTF-8" ?>
<CountryData>
 <Continent name="North America">
  <Country>
   <code>ABW</code>
   <name>Aruba</name>
   <gnp>828.00</gnp>
   <population>103000</population>
  </Country>
  <Country>
   <code>AIA</code>
   <name>Anguilla</name>
   <gnp>63.20</gnp>
   <population>8000</population>
  </Country>
  ....
  ....
  ....
 </Continent>
</CountryData>

Java Servlet REST Web Service - CountryXMLData.java

package com.as400samplecode;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

public class CountryXMLData extends HttpServlet {
 
 private static final long serialVersionUID = 1L;
 
 public CountryXMLData() {
  super();
 }

 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  doPost(request,response);
 }

 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

  PrintWriter out = response.getWriter();
  response.setContentType("text/html");
  response.setContentType("text/xml");
  response.setHeader("Cache-control", "no-cache, no-store");
  response.setHeader("Pragma", "no-cache");
  response.setHeader("Expires", "-1");

  
  Connection conn = null;             
  PreparedStatement stmt = null;      
  String sql = null;
  
  PreparedStatement stmt2 = null;      
  String sql2 = null;
  
  out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
  out.println("<CountryData>");

  try {       
   Context ctx = (Context) new InitialContext().lookup("java:comp/env");
   conn = ((DataSource) ctx.lookup("jdbc/mysql")).getConnection(); 

   sql = "Select distinct(continent) as c from country"; 
   stmt = conn.prepareStatement(sql);
   
   sql2 = "Select * from country where continent = ? and code <> 'CIV'"; 
   stmt2 = conn.prepareStatement(sql2);
   
   ResultSet rs = stmt.executeQuery();  
   
   while(rs.next()){ 
    
    String continent = rs.getString(1).trim();
    out.println("<Continent name=\"" + continent + "\">");
    
    stmt2.setString(1, continent.trim());
    ResultSet rs2 = stmt2.executeQuery();  
    
    while(rs2.next()){ 
     out.println("<Country>");
     out.println("<code>" + rs2.getString("code").trim() + "</code>");
     out.println("<name>" + rs2.getString("name").trim() + "</name>");
     out.println("<gnp>" + rs2.getString("gnp").trim() + "</gnp>");
     out.println("<population>" + rs2.getString("population").trim() + "</population>");
     out.println("</Country>");
    }                                                                          

    rs2.close();       
    
    out.println("</Continent>");
   }                                                                          

   rs.close();                                                                
   stmt.close();                                                              
   stmt = null;                                                               
   stmt2.close();                                                              
   stmt2 = null;

   conn.close();                                                              
   conn = null;                                                    

  }                                                                
  catch(Exception e){System.out.println(e);}                       

  finally {                                                        
   
   if (stmt != null) {                                             
    try {                                                          
     stmt.close();                                                 
    } catch (SQLException sqlex) {                                 
     // ignore -- as we can't do anything about it here            
    }                                                              

    stmt = null;                                             
   }                                                         

   if (conn != null) {                                       
    try {                                                    
     conn.close();                                           
    } catch (SQLException sqlex) {                           
     // ignore -- as we can't do anything about it here      
    }                                                        

    conn = null;                                             
   }                                                         
  }               

  out.println("</CountryData>");
  out.close();

 }
 
}

Interface file for Country Object - Country.h

#import <Foundation/Foundation.h>

@interface Country : NSObject

@property (nonatomic, strong) NSString *code;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSNumber *gnp;
@property (nonatomic, strong) NSNumber *population;

@end

Implementation file for Country Object - Country.m

#import "Country.h"

@implementation Country

@synthesize code;
@synthesize name;
@synthesize gnp;
@synthesize population;

@end

Interface file for Continent Object - Continent.h

#import <Foundation/Foundation.h>
#import "Country.h"
#import "Continent.h"

@interface Continent : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray *countryList;

@end

Implementation file for Continent Object - Continent.m

#import "Continent.h"

@implementation Continent

@synthesize name;
@synthesize countryList;

@end

Interface file for App Delegate - HTTPRequestXMLAppDelegate.h

#import <UIKit/UIKit.h>
#import "ListViewController.h"

@interface HTTPRequestXMLAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;
@property (strong, nonatomic) ListViewController *listViewController;

@end

Implementation file for App Delegate - HTTPRequestXMLAppDelegate.m

#import "HTTPRequestXMLAppDelegate.h"

@implementation HTTPRequestXMLAppDelegate

@synthesize navigationController;
@synthesize listViewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    
    //create the navigation controller and add the controllers view to the window
    navigationController = [[UINavigationController alloc] init];
    [self.window addSubview:[self.navigationController view]];

    //check if the list viewcontroller exists, otherwise create it
    if(self.listViewController == nil)
    {
        ListViewController *listView = [[ListViewController alloc] init];
        self.listViewController = listView;
    }
    
    //push the list viewcontroller into the navigation view controller stack
    [self.navigationController pushViewController:self.listViewController animated:YES];
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
    
}

- (void)applicationWillResignActive:(UIApplication *)application {
    //do nothing
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    //do nothing
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    //do nothing
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    //do nothing
}

- (void)applicationWillTerminate:(UIApplication *)application {
    //do nothing
}

@end

Interface file for List View Controller - ListViewController.h

#import <UIKit/UIKit.h>
#import "DetailViewController.h"
#import "Country.h"
#import "Continent.h"

@interface ListViewController : UIViewController <NSXMLParserDelegate,
                                UITableViewDelegate,UITableViewDataSource>

@property (nonatomic, strong) UITableView *myTableView;
@property (nonatomic, strong) NSXMLParser *xmlParser;

@property (nonatomic, strong) NSMutableArray *continentList;
@property (nonatomic, strong) NSMutableArray *countryList;
@property (nonatomic, strong) Continent *continent;
@property (nonatomic, strong) Country *country;
@property (nonatomic, strong) NSString *currentValue;
@property (nonatomic, strong) NSNumberFormatter *formatter;

@property (nonatomic, strong) DetailViewController *detailViewController;


@end

Implementation file for List View Controller - ListViewController.m

#import "ListViewController.h"

@interface ListViewController ()

@end

@implementation ListViewController

@synthesize myTableView;
@synthesize xmlParser;

@synthesize continentList;
@synthesize countryList;
@synthesize continent;
@synthesize country;
@synthesize currentValue;
@synthesize formatter;

@synthesize detailViewController;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        //NSNumberFormatter to format numeric data
        self.formatter = [[NSNumberFormatter alloc] init];
        [self.formatter setNumberStyle:NSNumberFormatterDecimalStyle];
        [self.formatter setMaximumFractionDigits:2];
        [self.formatter setMinimumFractionDigits:2];
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    //set the screen title in the navigation view
    [self.navigationItem setTitle:@"List of Countries"];
    
    //HTTP request to server to get data
    [self getDataFromServer];
    
    //create the table view with a given style
    self.myTableView = [[UITableView alloc] initWithFrame:self.view.bounds
                                                    style:UITableViewStyleGrouped];
    
    //set the table view delegate to the current so we can listen for events
    self.myTableView.delegate = self;
    //set the datasource for the table view to the current object
    self.myTableView.dataSource = self;
    
    //make sure our table view resizes correctly
    self.myTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth |
    UIViewAutoresizingFlexibleHeight;
    
    //add the table view to the main view
    [self.view addSubview:self.myTableView];
}

- (void) getDataFromServer {
    
    //string for the URL request
    NSString *myUrlString = @"http://demo.mysamplecode.com/Servlets_JSP/CountryXMLData";
    //create a NSURL object from the string data
    NSURL *myUrl = [NSURL URLWithString:myUrlString];
    
    //create a mutable HTTP request
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:myUrl];
    //sets the receiver’s timeout interval, in seconds
    [urlRequest setTimeoutInterval:30.0f];
    //sets the receiver’s HTTP request method
    [urlRequest setHTTPMethod:@"POST"];
    
    //allocate a new operation queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    //Loads the data for a URL request and executes a handler block on an
    //operation queue when the request completes or fails.
    [NSURLConnection
     sendAsynchronousRequest:urlRequest
     queue:queue
     completionHandler:^(NSURLResponse *response,
                         NSData *data,
                         NSError *error) {
         
         //looks like we received some data from the server
         if ([data length] >0 && error == nil){
             
             //convert the data to string so that we can display in console log
             NSString *myXMLData = [[NSString alloc] initWithData:data
                                            encoding:NSUTF8StringEncoding];
             NSLog(@"%@", myXMLData);
             
             //time to parse our XML data
             [self parseXMLData:data];
             
         }
         else if ([data length] == 0 && error == nil){
             NSLog(@"Empty Response, not sure why?");
         }
         else if (error != nil){
             NSLog(@"NOoooooo, what is the error = %@", error);
         }
     }];
    
}

- (void) parseXMLData:(NSData *) xml {
    
    //Create an instance of NSXMLParser so that we can parse the XML data
    self.xmlParser = [[NSXMLParser alloc] initWithData:xml];
    //set the delegate to self so that we can get notifications about the parsing elements
    self.xmlParser.delegate = self;
    //starts the event-driven parsing operation
    if ([self.xmlParser parse]){
        NSLog(@"XML parsing was successful");
        dispatch_async(dispatch_get_main_queue(), ^{
            //reload the data in the table view
            [self.myTableView reloadData];
        });
        
    }
    else{
        NSLog(@"XML parsing failed");
    }
    
}

//Sent by the parser object to the delegate when it begins parsing a document
- (void) parserDidStartDocument:(NSXMLParser *)parser{
    
     NSLog(@"START of XML parsing");
    
    NSMutableArray *array = [[NSMutableArray alloc] init];
    self.continentList = array;


}

//Sent by a parser object to its delegate when it encounters a start tag for a given element
- (void) parser:(NSXMLParser *)parser
            didStartElement:(NSString *)elementName
            namespaceURI:(NSString *)namespaceURI
            qualifiedName:(NSString *)qName
            attributes:(NSDictionary *)attributeDict{
    
    NSLog(@"Found a new element in the XML document");
    
    if([elementName isEqualToString:@"Continent"]){
        self.continent = [[Continent alloc] init];
        self.continent.name = [attributeDict objectForKey:@"name"];
        self.countryList = [[NSMutableArray alloc] init];
    }
    else if([elementName isEqualToString:@"Country"]){
        self.country = [[Country alloc] init];
    }
    
    self.currentValue = @"";
    
}

//Sent by a parser object to provide its delegate with a string representing
//all or part of the characters of the current element.
- (void) parser:(NSXMLParser *)parser
        foundCharacters:(NSString *)string{
    
    NSLog(@"Found character string inside an element");
    self.currentValue = [self.currentValue stringByAppendingString:string];
                
}

//Sent by a parser object to its delegate when it encounters an end tag for a specific element
- (void) parser:(NSXMLParser *)parser
            didEndElement:(NSString *)elementName
            namespaceURI:(NSString *)namespaceURI
            qualifiedName:(NSString *)qName{
    
    NSLog(@"Finished parsing the current element");
    
    if([elementName isEqualToString:@"code"]){
        self.country.code = self.currentValue;
    }
    else if([elementName isEqualToString:@"name"]){
        self.country.name = self.currentValue;
    }
    else if([elementName isEqualToString:@"gnp"]){
        self.country.gnp = [self.formatter numberFromString:self.currentValue];
    }
    else if([elementName isEqualToString:@"population"]){
        self.country.population = [self.formatter numberFromString:self.currentValue];
    }
    else if([elementName isEqualToString:@"Country"]){
        [self.countryList addObject:self.country];
    }
    else if([elementName isEqualToString:@"Continent"]){
        self.continent.countryList = self.countryList;
        [self.continentList addObject:self.continent];
    }
    
}

//Sent by the parser object to the delegate when it has successfully completed parsing
- (void)parserDidEndDocument:(NSXMLParser *)parser{

    
    NSLog(@"END of XML parsing");

}

//asks the data source to return the number of sections in the table view
//we are retuneing the number of continents
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    
    NSInteger numberOfSections = 0;
    
    if ([tableView isEqual:self.myTableView]){
        numberOfSections = self.continentList.count;
    }
    return numberOfSections;
}

//asks the data source to return the number of rows in a given section of a table view
//we are returning the number of countries in a given continent
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section{
    
    NSInteger numberOfRows = 0;
    
    if ([tableView isEqual:self.myTableView]){
        
        Continent *myContinent = [[Continent alloc] init];
        myContinent = [self.continentList objectAtIndex:section];
        numberOfRows = myContinent.countryList.count;
        
    }
    return numberOfRows;
}


//asks the data source for a cell to insert in a particular location of the table view
- (UITableViewCell *) tableView:(UITableView *)tableView
          cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *myCellView = nil;
    
    if ([tableView isEqual:self.myTableView]){
        
        static NSString *TableViewCellIdentifier = @"MyCells";
        
        //this method dequeues an existing cell if one is available or creates a new one
        //if no cell is available for reuse, this method returns nil
        myCellView = [tableView dequeueReusableCellWithIdentifier:TableViewCellIdentifier];
        if (myCellView == nil){
            //create a new cell
            myCellView = [[UITableViewCell alloc]
                          initWithStyle:UITableViewCellStyleDefault
                          reuseIdentifier:TableViewCellIdentifier];
        }
        
        //get the continent based on the index path section
        Continent *myContinent = [[Continent alloc] init];
        myContinent = [self.continentList objectAtIndex:indexPath.section];
        
        //get the country to display based on the index path row
        NSMutableArray *myCountryList = [[NSMutableArray alloc] init];
        myCountryList = myContinent.countryList;
        Country *myCountry = [[Country alloc] init];
        myCountry = [myCountryList objectAtIndex:indexPath.row];
        
        //display the country name in main label of the cell
        myCellView.textLabel.text = [NSString stringWithFormat:@"%@",
                                     myCountry.name];
        
        //set the accessory view to be a clickable button
        myCellView.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
        
    }
    return myCellView;
}

//asks the delegate for the height to use for a row in a specified location
- (CGFloat) tableView:(UITableView *)tableView
        heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    NSLog(@"%@",@"Set the row height");
    CGFloat result = 20.0f;
    if ([tableView isEqual:self.myTableView]){
        result = 35.0f;
    }
    return result;
    
}

//Asks the delegate for a view object to display in the header of the specified
//section of the table view, display the continent name in the header view
- (UIView *) tableView:(UITableView *)tableView
viewForHeaderInSection:(NSInteger)section{
    
    UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 30)];
    headerView.backgroundColor = [UIColor clearColor];
    Continent *myContinent = [[Continent alloc] init];
    myContinent = [self.continentList objectAtIndex:section];
    
    if ([tableView isEqual:self.myTableView]){
        UILabel *result = [[UILabel alloc]
                           initWithFrame:CGRectMake(12, 0, tableView.bounds.size.width, 30)];
        result.font = [UIFont boldSystemFontOfSize:16.0f];
        result.text = [NSString stringWithFormat:@"Continent: %@", myContinent.name];
        result.backgroundColor = [UIColor clearColor];
        [headerView addSubview:result];
    }
    return headerView;
    
}


//asks the delegate for the height to use for the header of a particular section
- (CGFloat) tableView:(UITableView *)tableView
heightForHeaderInSection:(NSInteger)section{
    
    CGFloat result = 0.0f;
    if ([tableView isEqual:self.myTableView]){
        result = 35.0f;
    }
    return result;
}


//informs the delegate that the user tapped the accessory view associated with a given row
- (void) tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath{
    
    if ([tableView isEqual:self.myTableView]){
        Country *myCountry = [self getCountryName:indexPath];
        NSLog(@"%@",
              [NSString stringWithFormat:@"Country is %@ and code is %@",
               myCountry.name, myCountry.code]);
        
        //if the detail view controller doesn't exists create it
        if(self.detailViewController == nil){
            DetailViewController *detailView = [[DetailViewController alloc] init];
            self.detailViewController = detailView;
        }
        
        //set the country object of the second view controller
        [self.detailViewController setCountry:myCountry];
        
        //tell the navigation controller to push a new view into the stack
        [self.navigationController pushViewController:self.detailViewController animated:YES];
       
    }
}

//find the country based on a given index path
- (Country *) getCountryName:(NSIndexPath *)indexPath {
    
    Continent *myContinent = [[Continent alloc] init];
    myContinent = [self.continentList objectAtIndex:indexPath.section];
    NSMutableArray *myCountryList = [[NSMutableArray alloc] init];
    myCountryList = myContinent.countryList;
    Country *myCountry = [[Country alloc] init];
    myCountry = [myCountryList objectAtIndex:indexPath.row];
    return myCountry;
    
}


- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

Interface file for Detail View Controller - DetailViewController.h

#import <UIKit/UIKit.h>
#import "Country.h"

@interface DetailViewController : UIViewController

@property (nonatomic, strong) Country *country;

@property (nonatomic, strong) UILabel *countryCode,
                                        *countryName,
                                        *gnp,
                                        *population;

@end

Implementation file for Detail View Controller - DetailViewController.m

#import "DetailViewController.h"

@interface DetailViewController ()

@end

@implementation DetailViewController

@synthesize country;
@synthesize countryCode, countryName, gnp, population;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
 [self.navigationItem setTitle:@"Country Detail"];
    
    [self.view setBackgroundColor:[UIColor whiteColor]];
    
    self.countryCode = [[UILabel alloc] init];
    [self.countryCode setFrame:CGRectMake(10.0,10.0,300.0,30.0)];
    [self.view addSubview: self.countryCode];
    
    self.countryName = [[UILabel alloc] init];
    [self.countryName setFrame:CGRectMake(10.0,40.0,300.0,30.0)];
    [self.view addSubview: self.countryName];

    
    self.gnp = [[UILabel alloc] init];
    [self.gnp setFrame:CGRectMake(10.0,70.0,300.0,30.0)];
    [self.view addSubview: self.gnp];
    
    self.population = [[UILabel alloc] init];
    [self.population setFrame:CGRectMake(10.0,100.0,300.0,30.0)];
    [self.view addSubview: self.population];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    
    [self.countryCode setText:[NSString stringWithFormat:@"Country code is %@ ",
                               country.code]];
    [self.countryName setText:[NSString stringWithFormat:@"for %@ ",
                                 country.name]];
    [self.gnp setText:[NSString stringWithFormat:@"GNP is %@ ",
                               country.gnp]];
    [self.population setText:[NSString stringWithFormat:@"It has a population of %@ ",
                              country.population]];
    
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

No comments :

Post a Comment