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
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
NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.