Cocoa Programming for Mac OS X - Aaron Hillegass [109]
return nil;
NSLog(@"Received %ld bytes.", [data length]);
return nil;
}
@end
We will use the NSDateFormatter later in the parsing process. Before we get into XML parsing, however, let’s test our fetchClassesWithError: method. In RanchForecastAppDelegate.m:
#import "RanchForecastAppDelegate.h"
#import "ScheduleFetcher.h"
@implementation RanchForecastAppDelegate
@synthesize window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
ScheduleFetcher *fetcher = [[ScheduleFetcher alloc] init];
NSError *error = nil;
[fetcher fetchClassesWithError:&error];
}
Build and run your application. You should see a log message indicating that data was received. Resolve any problems before continuing. It may help to log the error, if an error is being set.
Add XML Parsing to ScheduleFetcher
Now we need to parse the XML data from the Web service. We will do this by using an instance of NSXMLParser. Modify fetchClassesWithError: to instantiate and run the parser:
- (NSArray *)fetchClassesWithError:(NSError **)outError
{
BOOL success;
NSURL *xmlURL = [NSURL URLWithString:
@"http://bignerdranch.com/xml/schedule"];
NSURLRequest *req = [NSURLRequest requestWithURL:xmlURL
cachePolicy:NSURLRequestReturnCacheDataElseLoad
timeoutInterval:30];
NSURLResponse *resp = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:req
returningResponse:&resp
error:outError];
if (!data)
return nil;
[classes removeAllObjects];
NSXMLParser *parser;
parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
success = [parser parse];
if (!success)
{
*outError = [parser parserError];
return nil;
}
NSArray *output = [classes copy];
return output;
}
Note that the parser’s delegate is set to self. As all the NSXMLParserDelegate protocol methods are optional, we need to add only the ones we are interested in. Add them now to ScheduleFetcher.m:
#pragma mark -
#pragma mark NSXMLParserDelegate Methods
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqual:@"class"])
{
currentFields = [[NSMutableDictionary alloc] init];
}
else if ([elementName isEqual:@"offering"])
{
[currentFields setObject:[attributeDict objectForKey:@"href"]
forKey:@"href"];
}
}
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
if ([elementName isEqual:@"class"])
{
ScheduledClass *currentClass = [[ScheduledClass alloc] init];
[currentClass setName:[currentFields
objectForKey:@"offering"]];
[currentClass setLocation:[currentFields
objectForKey:@"location"]];
[currentClass setHref:[currentFields
objectForKey:@"href"]];
NSString *beginString = [currentFields objectForKey:@"begin"];
NSDate *beginDate = [dateFormatter
dateFromString:beginString];
[currentClass setBegin:beginDate];
[classes addObject:currentClass];
currentClass = nil;
currentFields = nil;
}
else if (currentFields && currentString)
{
NSString *trimmed;
trimmed = [currentString stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[currentFields setObject:trimmed forKey:elementName];
}
currentString = nil;
}
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string
{
if (!currentString) {
currentString = [[NSMutableString alloc] init];
}
[currentString appendString:string];
}
As mentioned at the start of the chapter, when NSXMLParser’s parse method is called, it scans through the data provided, calling its delegate for each change in the structure of the XML data that it encounters. As is this case in this exercise, you will frequently be interested only in the start and end of elements, as well as the character data that occurs in between them.
The delegate is responsible for managing any state information during the parsing process.