Wednesday, 28 August 2013

Seismic XML

SeismicXML

  • Documentation
    ====================================================================
    DESCRIPTION:

    The SeismicXML sample application demonstrates how to use NSXMLParser to parse XML data.
    When you launch the application it downloads and parses an RSS feed from the United States Geological Survey (USGS) that provides data on recent earthquakes around the world. It displays the location, date, and magnitude of each earthquake, along with a color-coded graphic that indicates the severity of the earthquake. The XML parsing occurs on a background thread and updates the earthquakes table view with batches of parsed objects.

    The USGS feed is at http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml and includes all recent magnitude 2.5 and greater earthquakes world-wide, representing each earthquake with an <entry> element, in the following form:

    <entry>
        <id>urn:earthquake-usgs-gov:us:2008rkbc</id>
        <title>M 5.8, Banda Sea</title>
        <updated>2008-04-29T19:10:01Z</updated>
        <link rel="alternate" type="text/html" href="/eqcenter/recenteqsww/Quakes/us2008rkbc.php"/>
        <link rel="related" type="application/cap+xml" href="/eqcenter/catalogs/cap/us2008rkbc"/>
        <summary type="html">
            <img src="http://earthquake.usgs.gov/images/globes/-5_130.jpg" alt="6.102&#176;S 127.502&#176;E" align="left" hspace="20" /><p>Tuesday, April 29, 2008 19:10:01 UTC<br>Wednesday, April 30, 2008 04:10:01 AM at epicenter</p><p><strong>Depth</strong>: 395.20 km (245.57 mi)</p>
        </summary>
        <georss:point>-6.1020 127.5017</georss:point>
        <georss:elev>-395200</georss:elev>
        <category label="Age" term="Past hour"/>
    </entry>

    NSXMLParser is an "event-driven" parser. This means that it makes a single pass over the XML data and calls back to its delegate with "events". These events include the beginning and end of elements, parsed character data, errors, and more. In this sample, the application delegate, an instance of the "SeismicXMLAppDelegate" class, also implements the delegate methods for the parser object. In these methods, Earthquake objects are instantiated and their properties are set, according to the data provided by the parser. For some data, additional work is required - numbers extracted from strings, or date objects created from strings. 


    ====================================================================
    BUILD REQUIREMENTS

    iOS SDK 4.0

    ====================================================================
    RUNTIME REQUIREMENTS

    iOS OS 3.2 or later

    ====================================================================
    PACKAGING LIST

    SeismicXMLAppDelegate
    Delegate for the application, initiates the download of the XML data and parses the Earthquake objects at launch time.

    Earthquake
    The model class that stores the information about an earthquake.

    RootViewController
    A UITableViewController subclass that manages the table view.

    ParseOperation
    The NSOperation class used to perform the XML parsing of earthquake data.

    ====================================================================

    Copyright (C) 2008-2010 Apple Inc. All rights reserved.

  • Design



File structure





  • Code
   

SeismicXMLAppDelegate.h

#import <UIKit/UIKit.h>

@class Earthquake, RootViewController;

@interface SeismicXMLAppDelegate : NSObject <UIApplicationDelegate, NSXMLParserDelegate> {
    UIWindow *window;
    UINavigationController *navigationController;
    RootViewController *rootViewController;
    
@private
    // for downloading the xml data
    NSURLConnection *earthquakeFeedConnection;
    NSMutableData *earthquakeData;
    
    NSOperationQueue *parseQueue;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
@property (nonatomic, retain) IBOutlet RootViewController *rootViewController;

@end


-----------------------------------------------------------
SeismicXMLAppDelegate.m

#import "SeismicXMLAppDelegate.h"
#import "RootViewController.h"
#import "Earthquake.h"
#import "ParseOperation.h"

// this framework was imported so we could use the kCFURLErrorNotConnectedToInternet error code
#import <CFNetwork/CFNetwork.h>


#pragma mark SeismicXMLAppDelegate () 

// forward declarations
@interface SeismicXMLAppDelegate ()

@property (nonatomic, retain) NSURLConnection *earthquakeFeedConnection;
@property (nonatomic, retain) NSMutableData *earthquakeData;    // the data returned from the NSURLConnection
@property (nonatomic, retain) NSOperationQueue *parseQueue;     // the queue that manages our NSOperation for parsing earthquake data

- (void)addEarthquakesToList:(NSArray *)earthquakes;
- (void)handleError:(NSError *)error;
@end


#pragma mark -
#pragma mark SeismicXMLAppDelegate

@implementation SeismicXMLAppDelegate

@synthesize window;
@synthesize navigationController;
@synthesize rootViewController;
@synthesize earthquakeFeedConnection;
@synthesize earthquakeData;
@synthesize parseQueue;


- (void)dealloc {
    [earthquakeFeedConnection cancel];
    [earthquakeFeedConnection release];
    
    [earthquakeData release];
    [navigationController release];
    [rootViewController release];
    [window release];
    
    [parseQueue release];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kAddEarthquakesNotif object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kEarthquakesErrorNotif object:nil];
    
    [super dealloc];
}

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // Add the navigation view controller to the window.
    [window addSubview:navigationController.view];
    
    // Use NSURLConnection to asynchronously download the data. This means the main thread will not
    // be blocked - the application will remain responsive to the user. 
    //
    // IMPORTANT! The main thread of the application should never be blocked!
    // Also, avoid synchronous network access on any thread.
    //
    static NSString *feedURLString = @"http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml";
    NSURLRequest *earthquakeURLRequest =
    [NSURLRequest requestWithURL:[NSURL URLWithString:feedURLString]];
    self.earthquakeFeedConnection =
    [[[NSURLConnection alloc] initWithRequest:earthquakeURLRequest delegate:self] autorelease];
    
    // Test the validity of the connection object. The most likely reason for the connection object
    // to be nil is a malformed URL, which is a programmatic error easily detected during development.
    // If the URL is more dynamic, then you should implement a more flexible validation technique,
    // and be able to both recover from errors and communicate problems to the user in an
    // unobtrusive manner.
    NSAssert(self.earthquakeFeedConnection != nil, @"Failure to create URL connection.");
    
    // Start the status bar network activity indicator. We'll turn it off when the connection
    // finishes or experiences an error.
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    
    parseQueue = [NSOperationQueue new];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(addEarthquakes:)
                                                 name:kAddEarthquakesNotif
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(earthquakesError:)
                                                 name:kEarthquakesErrorNotif
                                               object:nil];
}


#pragma mark -
#pragma mark NSURLConnection delegate methods

// The following are delegate methods for NSURLConnection. Similar to callback functions, this is
// how the connection object, which is working in the background, can asynchronously communicate back
// to its delegate on the thread from which it was started - in this case, the main thread.
//
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    // check for HTTP status code for proxy authentication failures
    // anything in the 200 to 299 range is considered successful,
    // also make sure the MIMEType is correct:
    //
    NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
    if ((([httpResponse statusCode]/100) == 2) && [[response MIMEType] isEqual:@"application/atom+xml"]) {
        self.earthquakeData = [NSMutableData data];
    } else {
        NSDictionary *userInfo = [NSDictionary dictionaryWithObject:
                                  NSLocalizedString(@"HTTP Error",
                                                    @"Error message displayed when receving a connection error.")
                                                             forKey:NSLocalizedDescriptionKey];
        NSError *error = [NSError errorWithDomain:@"HTTP" code:[httpResponse statusCode] userInfo:userInfo];
        [self handleError:error];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [earthquakeData appendData:data];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
    if ([error code] == kCFURLErrorNotConnectedToInternet) {
        // if we can identify the error, we can present a more precise message to the user.
        NSDictionary *userInfo =
        [NSDictionary dictionaryWithObject:
         NSLocalizedString(@"No Connection Error",
                           @"Error message displayed when not connected to the Internet.")
                                    forKey:NSLocalizedDescriptionKey];
        NSError *noConnectionError = [NSError errorWithDomain:NSCocoaErrorDomain
                                                         code:kCFURLErrorNotConnectedToInternet
                                                     userInfo:userInfo];
        [self handleError:noConnectionError];
    } else {
        // otherwise handle the error generically
        [self handleError:error];
    }
    self.earthquakeFeedConnection = nil;
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    self.earthquakeFeedConnection = nil;
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;   
    
    // Spawn an NSOperation to parse the earthquake data so that the UI is not blocked while the
    // application parses the XML data.
    //
    // IMPORTANT! - Don't access or affect UIKit objects on secondary threads.
    //
    ParseOperation *parseOperation = [[ParseOperation alloc] initWithData:self.earthquakeData];
    [self.parseQueue addOperation:parseOperation];
    [parseOperation release];   // once added to the NSOperationQueue it's retained, we don't need it anymore
    
    // earthquakeData will be retained by the NSOperation until it has finished executing,
    // so we no longer need a reference to it in the main thread.
    self.earthquakeData = nil;
}

// Handle errors in the download by showing an alert to the user. This is a very
// simple way of handling the error, partly because this application does not have any offline
// functionality for the user. Most real applications should handle the error in a less obtrusive
// way and provide offline functionality to the user.
//
- (void)handleError:(NSError *)error {
    NSString *errorMessage = [error localizedDescription];
    UIAlertView *alertView =
    [[UIAlertView alloc] initWithTitle:
     NSLocalizedString(@"Error Title",
                       @"Title for alert displayed when download or parse error occurs.")
                               message:errorMessage
                              delegate:nil
                     cancelButtonTitle:@"OK"
                     otherButtonTitles:nil];
    [alertView show];
    [alertView release];
}

// Our NSNotification callback from the running NSOperation to add the earthquakes
//
- (void)addEarthquakes:(NSNotification *)notif {
    assert([NSThread isMainThread]);
    
    [self addEarthquakesToList:[[notif userInfo] valueForKey:kEarthquakeResultsKey]];
}

// Our NSNotification callback from the running NSOperation when a parsing error has occurred
//
- (void)earthquakesError:(NSNotification *)notif {
    assert([NSThread isMainThread]);
    
    [self handleError:[[notif userInfo] valueForKey:kEarthquakesMsgErrorKey]];
}

// The NSOperation "ParseOperation" calls addEarthquakes: via NSNotification, on the main thread
// which in turn calls this method, with batches of parsed objects.
// The batch size is set via the kSizeOfEarthquakeBatch constant.
//
- (void)addEarthquakesToList:(NSArray *)earthquakes {
    
    // insert the earthquakes into our rootViewController's data source (for KVO purposes)
    [self.rootViewController insertEarthquakes:earthquakes];
}

@end
-----------------------------------------------------------
RootViewController.h

#import <UIKit/UIKit.h>

@interface RootViewController : UITableViewController <UIActionSheetDelegate> {
    NSMutableArray *earthquakeList;
    
    // This date formatter is used to convert NSDate objects to NSString objects, using the user's preferred formats.
    NSDateFormatter *dateFormatter;
}

@property (nonatomic, retain) NSMutableArray *earthquakeList;
@property (nonatomic, retain, readonly) NSDateFormatter *dateFormatter;

- (void)insertEarthquakes:(NSArray *)earthquakes;   // addition method of earthquakes (for KVO purposes)

@end

-----------------------------------------------------------

RootViewController.m


#import "RootViewController.h"
#import "Earthquake.h"

@implementation RootViewController

@synthesize earthquakeList;

#pragma mark -

- (void)dealloc {
    [earthquakeList release];
    [dateFormatter release];
    
    [super dealloc];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.earthquakeList = [NSMutableArray array];
    
    // The table row height is not the standard value. Since all the rows have the same height,
    // it is more efficient to set this property on the table, rather than using the delegate
    // method -tableView:heightForRowAtIndexPath:
    //
    self.tableView.rowHeight = 48.0;
    
    // KVO: listen for changes to our earthquake data source for table view updates
    [self addObserver:self forKeyPath:@"earthquakeList" options:0 context:NULL];
}

- (void)viewDidUnload {
    [super viewDidUnload];
    
    self.earthquakeList = nil;
    
    [self removeObserver:self forKeyPath:@"earthquakeList"];
}

// On-demand initializer for read-only property.
- (NSDateFormatter *)dateFormatter {
    if (dateFormatter == nil) {
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
        [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    }
    return dateFormatter;
}

// Based on the magnitude of the earthquake, return an image indicating its seismic strength.
- (UIImage *)imageForMagnitude:(CGFloat)magnitude {
if (magnitude >= 5.0) {
return [UIImage imageNamed:@"5.0.png"];
}
if (magnitude >= 4.0) {
return [UIImage imageNamed:@"4.0.png"];
}
if (magnitude >= 3.0) {
return [UIImage imageNamed:@"3.0.png"];
}
if (magnitude >= 2.0) {
return [UIImage imageNamed:@"2.0.png"];
}
return nil;
}


#pragma mark -
#pragma mark KVO support

- (void)insertEarthquakes:(NSArray *)earthquakes
{
    // this will allow us as an observer to notified (see observeValueForKeyPath)
    // so we can update our UITableView
    //
    [self willChangeValueForKey:@"earthquakeList"];
    [self.earthquakeList addObjectsFromArray:earthquakes];
    [self didChangeValueForKey:@"earthquakeList"];
}

// listen for changes to the earthquake list coming from our app delegate.


- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    [self.tableView reloadData];
}


#pragma mark -
#pragma mark UITableViewDelegate

// The number of rows is equal to the number of earthquakes in the array.


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [earthquakeList count];
}




// The cell uses a custom layout, but otherwise has standard behavior for UITableViewCell.
// In these cases, it's preferable to modify the view hierarchy of the cell's content view, rather
// than subclassing. Instead, view "tags" are used to identify specific controls, such as labels,
// image views, etc.
//




- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {


    // Each subview in the cell will be identified by a unique tag.
    static NSUInteger const kLocationLabelTag = 2;
    static NSUInteger const kDateLabelTag = 3;
    static NSUInteger const kMagnitudeLabelTag = 4;
    static NSUInteger const kMagnitudeImageTag = 5;
    
    // Declare references to the subviews which will display the earthquake data.
    UILabel *locationLabel = nil;
    UILabel *dateLabel = nil;
    UILabel *magnitudeLabel = nil;
    UIImageView *magnitudeImage = nil;
    
static NSString *kEarthquakeCellID = @"EarthquakeCellID";    
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kEarthquakeCellID];


if (cell == nil) {

        // No reusable cell was available, so we create a new cell and configure its subviews.

        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:kEarthquakeCellID] autorelease];
        
        locationLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 3, 190, 20)] autorelease];
        locationLabel.tag = kLocationLabelTag;
        locationLabel.font = [UIFont boldSystemFontOfSize:14];
        [cell.contentView addSubview:locationLabel];
        
        dateLabel = [[[UILabel alloc] initWithFrame:CGRectMake(10, 28, 170, 14)] autorelease];
        dateLabel.tag = kDateLabelTag;
        dateLabel.font = [UIFont systemFontOfSize:10];
        [cell.contentView addSubview:dateLabel];

        magnitudeLabel = [[[UILabel alloc] initWithFrame:CGRectMake(277, 9, 170, 29)] autorelease];
        magnitudeLabel.tag = kMagnitudeLabelTag;
        magnitudeLabel.font = [UIFont boldSystemFontOfSize:24];
        magnitudeLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
        [cell.contentView addSubview:magnitudeLabel];
        
        magnitudeImage = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"5.0.png"]] autorelease];
        CGRect imageFrame = magnitudeImage.frame;
        imageFrame.origin = CGPointMake(180, 2);
        magnitudeImage.frame = imageFrame;
        magnitudeImage.tag = kMagnitudeImageTag;
        magnitudeImage.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
        [cell.contentView addSubview:magnitudeImage];
    } else {
        // A reusable cell was available, so we just need to get a reference to the subviews
        // using their tags.
        //
        locationLabel = (UILabel *)[cell.contentView viewWithTag:kLocationLabelTag];
        dateLabel = (UILabel *)[cell.contentView viewWithTag:kDateLabelTag];
        magnitudeLabel = (UILabel *)[cell.contentView viewWithTag:kMagnitudeLabelTag];
        magnitudeImage = (UIImageView *)[cell.contentView viewWithTag:kMagnitudeImageTag];
    }
    
    // Get the specific earthquake for this row.
Earthquake *earthquake = [earthquakeList objectAtIndex:indexPath.row];
    
    // Set the relevant data for each subview in the cell.
    locationLabel.text = earthquake.location;
    dateLabel.text = [NSString stringWithFormat:@"%@", [self.dateFormatter stringFromDate:earthquake.date]];
    magnitudeLabel.text = [NSString stringWithFormat:@"%.1f", earthquake.magnitude];
    magnitudeImage.image = [self imageForMagnitude:earthquake.magnitude];

return cell;
}

// When the user taps a row in the table, display the USGS web page that displays details of the
// earthquake they selected.
//




- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UIActionSheet *sheet =

        [[UIActionSheet alloc] initWithTitle:
            NSLocalizedString(@"External App Sheet Title",
                              @"Title for sheet displayed with options for displaying Earthquake data in other applications")
                               delegate:self
                      cancelButtonTitle:NSLocalizedString(@"Cancel", @"Cancel")
                 destructiveButtonTitle:nil
                      otherButtonTitles:NSLocalizedString(@"Show USGS Site in Safari", @"Show USGS Site in Safari"),
                                        NSLocalizedString(@"Show Location in Maps", @"Show Location in Maps"),
                                        nil];
    [sheet showInView:self.view];
    [sheet release];
}

// Called when the user selects an option in the sheet. The sheet will automatically be dismissed.

- (void)actionSheet:(UIActionSheet *)actionSheet willDismissWithButtonIndex:(NSInteger)buttonIndex {

    NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];

    Earthquake *earthquake = (Earthquake *)[earthquakeList objectAtIndex:selectedIndexPath.row];

    switch (buttonIndex) {
        case 0: {

            NSURL *webLink = [earthquake USGSWebLink];
            [[UIApplication sharedApplication] openURL:webLink];

        } break;
        case 1: {

            NSString *mapsQuery =
                [NSString stringWithFormat:@"http://maps.google.com/maps?z=6&t=h&ll=%f,%f",
                                            earthquake.latitude, earthquake.longitude];
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:mapsQuery]];
        
        } break;
        default:
            break;
    }
    [self.tableView deselectRowAtIndexPath:selectedIndexPath animated:YES];
}

@end


-----------------------------------------------------------
Earthquake.h


#import <Foundation/Foundation.h>

@interface Earthquake : NSObject {
@private
    // Magnitude of the earthquake on the Richter scale.
    CGFloat magnitude;
    // Name of the location of the earthquake.
    NSString *location;
    // Date and time at which the earthquake occurred.
    NSDate *date;
    // Holds the URL to the USGS web page of the earthquake. The application uses this URL to open that page in Safari.
    NSURL *USGSWebLink;
    // Latitude and longitude of the earthquake. These properties are not displayed by the application, but are used to  
    // create a URL for opening the Maps application. They could alternatively be used in conjuction with MapKit 
    // to be shown in a map view.
    double latitude;
    double longitude;
}

@property (nonatomic, assign) CGFloat magnitude;
@property (nonatomic, retain) NSString *location;
@property (nonatomic, retain) NSDate *date;
@property (nonatomic, retain) NSURL *USGSWebLink;
@property (nonatomic, assign) double latitude;
@property (nonatomic, assign) double longitude;

@end


-----------------------------------------------------------

Earthquake.m


#import "Earthquake.h"

@implementation Earthquake

@synthesize magnitude;
@synthesize location;
@synthesize date;
@synthesize USGSWebLink;
@synthesize latitude;
@synthesize longitude;

- (void)dealloc {
    [location release];
    [date release];
    [USGSWebLink release];
    [super dealloc];
}

@end

-----------------------------------------------------------


ParseOperation.h


extern NSString *kAddEarthquakesNotif;
extern NSString *kEarthquakeResultsKey;

extern NSString *kEarthquakesErrorNotif;
extern NSString *kEarthquakesMsgErrorKey;

@class Earthquake;

@interface ParseOperation : NSOperation {
    NSData *earthquakeData;

@private
    NSDateFormatter *dateFormatter;
    
    // these variables are used during parsing
    Earthquake *currentEarthquakeObject;
    NSMutableArray *currentParseBatch;
    NSMutableString *currentParsedCharacterData;
    
    BOOL accumulatingParsedCharacterData;
    BOOL didAbortParsing;
    NSUInteger parsedEarthquakesCounter;
}

@property (copy, readonly) NSData *earthquakeData;

@end

-----------------------------------------------------------

ParseOperation.m


#import "ParseOperation.h"
#import "Earthquake.h"

// NSNotification name for sending earthquake data back to the app delegate
NSString *kAddEarthquakesNotif = @"AddEarthquakesNotif";

// NSNotification userInfo key for obtaining the earthquake data
NSString *kEarthquakeResultsKey = @"EarthquakeResultsKey";

// NSNotification name for reporting errors
NSString *kEarthquakesErrorNotif = @"EarthquakeErrorNotif";

// NSNotification userInfo key for obtaining the error message
NSString *kEarthquakesMsgErrorKey = @"EarthquakesMsgErrorKey";


@interface ParseOperation () <NSXMLParserDelegate>
    @property (nonatomic, retain) Earthquake *currentEarthquakeObject;
    @property (nonatomic, retain) NSMutableArray *currentParseBatch;
    @property (nonatomic, retain) NSMutableString *currentParsedCharacterData;
@end

@implementation ParseOperation

@synthesize earthquakeData, currentEarthquakeObject, currentParsedCharacterData, currentParseBatch;



- (id)initWithData:(NSData *)parseData
{
    if (self = [super init]) {    
        earthquakeData = [parseData copy];
        
        dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
        [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
    }
    return self;
}






- (void)addEarthquakesToList:(NSArray *)earthquakes {
    assert([NSThread isMainThread]);
    
    [[NSNotificationCenter defaultCenter] postNotificationName:kAddEarthquakesNotif
                                                        object:self
                                                      userInfo:[NSDictionary dictionaryWithObject:earthquakes
                                                                                           forKey:kEarthquakeResultsKey]]; 
}
     
// the main function for this NSOperation, to start the parsing


- (void)main {

    self.currentParseBatch = [NSMutableArray array];
    self.currentParsedCharacterData = [NSMutableString string];
    
    // It's also possible to have NSXMLParser download the data, by passing it a URL, but this is
    // not desirable because it gives less control over the network, particularly in responding to
    // connection errors.
    //


    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:self.earthquakeData];
    [parser setDelegate:self];
    [parser parse];
    
    // depending on the total number of earthquakes parsed, the last batch might not have been a
    // "full" batch, and thus not been part of the regular batch transfer. So, we check the count of
    // the array and, if necessary, send it to the main thread.
    //


    if ([self.currentParseBatch count] > 0) {
        [self performSelectorOnMainThread:@selector(addEarthquakesToList:)
                               withObject:self.currentParseBatch
                            waitUntilDone:NO];
    }
    
    self.currentParseBatch = nil;
    self.currentEarthquakeObject = nil;
    self.currentParsedCharacterData = nil;
    
    [parser release];
}




- (void)dealloc {
    [earthquakeData release];
    
    [currentEarthquakeObject release];
    [currentParsedCharacterData release];
    [currentParseBatch release];
    [dateFormatter release];
    
    [super dealloc];
}


#pragma mark -
#pragma mark Parser constants

// Limit the number of parsed earthquakes to 50
// (a given day may have more than 50 earthquakes around the world, so we only take the first 50)
//



static const const NSUInteger kMaximumNumberOfEarthquakesToParse = 50;

// When an Earthquake object has been fully constructed, it must be passed to the main thread and
// the table view in RootViewController must be reloaded to display it. It is not efficient to do
// this for every Earthquake object - the overhead in communicating between the threads and reloading
// the table exceed the benefit to the user. Instead, we pass the objects in batches, sized by the
// constant below. In your application, the optimal batch size will vary 
// depending on the amount of data in the object and other factors, as appropriate.
//



static NSUInteger const kSizeOfEarthquakeBatch = 10;





// Reduce potential parsing errors by using string constants declared in a single place.



static NSString * const kEntryElementName = @"entry";
static NSString * const kLinkElementName = @"link";
static NSString * const kTitleElementName = @"title";
static NSString * const kUpdatedElementName = @"updated";
static NSString * const kGeoRSSPointElementName = @"georss:point";


#pragma mark -
#pragma mark NSXMLParser delegate methods





- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
                                        namespaceURI:(NSString *)namespaceURI
                                       qualifiedName:(NSString *)qName
                                          attributes:(NSDictionary *)attributeDict {
    // If the number of parsed earthquakes is greater than
    // kMaximumNumberOfEarthquakesToParse, abort the parse.
    //
    if (parsedEarthquakesCounter >= kMaximumNumberOfEarthquakesToParse) {
        // Use the flag didAbortParsing to distinguish between this deliberate stop
        // and other parser errors.
        //
        didAbortParsing = YES;
        [parser abortParsing];
    }
    if ([elementName isEqualToString:kEntryElementName]) {
        Earthquake *earthquake = [[Earthquake alloc] init];
        self.currentEarthquakeObject = earthquake;
        [earthquake release];
    } else if ([elementName isEqualToString:kLinkElementName]) {
        NSString *relAttribute = [attributeDict valueForKey:@"rel"];
        if ([relAttribute isEqualToString:@"alternate"]) {
            NSString *USGSWebLink = [attributeDict valueForKey:@"href"];
            self.currentEarthquakeObject.USGSWebLink = [NSURL URLWithString:USGSWebLink];
        }
    } else if ([elementName isEqualToString:kTitleElementName] ||
               [elementName isEqualToString:kUpdatedElementName] ||
               [elementName isEqualToString:kGeoRSSPointElementName]) {
        // For the 'title', 'updated', or 'georss:point' element begin accumulating parsed character data.
        // The contents are collected in parser:foundCharacters:.
        accumulatingParsedCharacterData = YES;
        // The mutable string needs to be reset to empty.
        [currentParsedCharacterData setString:@""];
    }
}




- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
                                      namespaceURI:(NSString *)namespaceURI
                                     qualifiedName:(NSString *)qName {     
    if ([elementName isEqualToString:kEntryElementName]) {

        [self.currentParseBatch addObject:self.currentEarthquakeObject];

        parsedEarthquakesCounter++;

        if ([self.currentParseBatch count] >= kMaximumNumberOfEarthquakesToParse) {

            [self performSelectorOnMainThread:@selector(addEarthquakesToList:)
                                   withObject:self.currentParseBatch
                                waitUntilDone:NO];

            self.currentParseBatch = [NSMutableArray array];
        }
    } 
    else if ([elementName isEqualToString:kTitleElementName]) {

        // The title element contains the magnitude and location in the following format:
        // <title>M 3.6, Virgin Islands region<title/>
        // Extract the magnitude and the location using a scanner:

        NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
        // Scan past the "M " before the magnitude.

        if ([scanner scanString:@"M " intoString:NULL]) {
            CGFloat magnitude;

            if ([scanner scanFloat:&magnitude]) {

                self.currentEarthquakeObject.magnitude = magnitude;
                
                // Scan past the ", " before the title.

                if ([scanner scanString:@", " intoString:NULL]) {
                   
                    NSString *location = nil;

                    // Scan the remainer of the string.

                    if ([scanner scanUpToCharactersFromSet:
                         [NSCharacterSet illegalCharacterSet] intoString:&location]) {

                        self.currentEarthquakeObject.location = location;
                    }
                }
            }
        }
    } 
    else if ([elementName isEqualToString:kUpdatedElementName]) {

        if (self.currentEarthquakeObject != nil) {

            self.currentEarthquakeObject.date =
            [dateFormatter dateFromString:self.currentParsedCharacterData];

        }
        else {

            // kUpdatedElementName can be found outside an entry element (i.e. in the XML header)
            // so don't process it here.
        }

    } else if ([elementName isEqualToString:kGeoRSSPointElementName]) {
      
        // The georss:point element contains the latitude and longitude of the earthquake epicenter.
        // 18.6477 -66.7452
        //

        NSScanner *scanner = [NSScanner scannerWithString:self.currentParsedCharacterData];
        double latitude, longitude;

        if ([scanner scanDouble:&latitude]) {

            if ([scanner scanDouble:&longitude]) {

                self.currentEarthquakeObject.latitude = latitude;

                self.currentEarthquakeObject.longitude = longitude;
            }

        }

    }

    // Stop accumulating parsed character data. We won't start again until specific elements begin.
    accumulatingParsedCharacterData = NO;
}

// This method is called by the parser when it find parsed character data ("PCDATA") in an element.
// The parser is not guaranteed to deliver all of the parsed character data for an element in a single
// invocation, so it is necessary to accumulate character data until the end of the element is reached.
//



- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
    
    if (accumulatingParsedCharacterData) {

        // If the current element is one whose content we care about, append 'string'
        // to the property that holds the content of the current element.
        //

        [self.currentParsedCharacterData appendString:string];
    }

}



// an error occurred while parsing the earthquake data,
// post the error as an NSNotification to our app delegate.
// 


- (void)handleEarthquakesError:(NSError *)parseError {

    [[NSNotificationCenter defaultCenter] postNotificationName:kEarthquakesErrorNotif
                                                    object:self
                                                  userInfo:[NSDictionary dictionaryWithObject:parseError
                                                                                       forKey:kEarthquakesMsgErrorKey]];
}

// an error occurred while parsing the earthquake data,
// pass the error to the main thread for handling.
// (note: don't report an error if we aborted the parse due to a max limit of earthquakes)
//


- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError {

    if ([parseError code] != NSXMLParserDelegateAbortedParseError && !didAbortParsing)
    {

        [self performSelectorOnMainThread:@selector(handleEarthquakesError:)
                               withObject:parseError
                            waitUntilDone:NO];

    }
}

@end

No comments:

Post a Comment