// // AcidCowFeedburnerParser.m // PicCast // // Created by Matthew Handler on 4/15/11. // Copyright 2011 Earl Industries. All rights reserved. // #import "AcidCowFeedburnerParser.h" #import "Topic.h" @implementation AcidCowFeedburnerParser @synthesize currentString, currentTopic, parseFormatter, xmlData, rssConnection, downloadAndParsePool; - (void)downloadAndParse:(NSURL *)url { self.downloadAndParsePool = [[NSAutoreleasePool alloc] init]; done = NO; self.parseFormatter = [[[NSDateFormatter alloc] init] autorelease]; [parseFormatter setDateStyle:NSDateFormatterLongStyle]; [parseFormatter setTimeStyle:NSDateFormatterNoStyle]; [parseFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"]; // necessary because iTunes RSS feed is not localized, so if the device region has been set to other than US // the date formatter must be set to US locale in order to parse the dates [parseFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"US"] autorelease]]; self.xmlData = [NSMutableData data]; //[[NSURLCache sharedURLCache] removeAllCachedResponses]; NSURLRequest *theRequest = [NSURLRequest requestWithURL:url]; // create the connection with the request and start loading the data rssConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self]; [self performSelectorOnMainThread:@selector(downloadStarted) withObject:nil waitUntilDone:NO]; if (rssConnection != nil) { do { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } while (!done); } self.rssConnection = nil; self.parseFormatter = nil; self.currentTopic = nil; [downloadAndParsePool release]; self.downloadAndParsePool = nil; } #pragma mark NSURLConnection Delegate methods /* Disable caching so that each time we run this app we are starting with a clean slate. You may not want to do this in your application. */ //- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { // return nil; //} - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { done = YES; [self performSelectorOnMainThread:@selector(parseError:) withObject:error waitUntilDone:NO]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [xmlData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"finishedLoading"); [self performSelectorOnMainThread:@selector(downloadEnded) withObject:nil waitUntilDone:NO]; [self convertXMLData]; NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData]; parser.delegate = self; self.currentString = [NSMutableString string]; //NSTimeInterval start = [NSDate timeIntervalSinceReferenceDate]; [parser parse]; //NSTimeInterval duration = [NSDate timeIntervalSinceReferenceDate] - start; //[self performSelectorOnMainThread:@selector(addToParseDuration:) withObject:[NSNumber numberWithDouble:duration] waitUntilDone:NO]; [self performSelectorOnMainThread:@selector(parseEnded) withObject:nil waitUntilDone:NO]; [parser release]; self.currentString = nil; self.xmlData = nil; // Set the condition which ends the run loop. done = YES; } // needed to convert feedburners xml into utf-8 because the iphone parser breaks on windows-1251 - (void)convertXMLData { NSString *string = [[NSString alloc] initWithData:xmlData encoding:NSWindowsCP1251StringEncoding]; string = [string stringByReplacingOccurrencesOfString:@"encoding=\"windows-1251\"" withString:@""]; // [xmlData release]; // xmlData = nil; xmlData = [[NSMutableData dataWithData:[string dataUsingEncoding:NSUTF8StringEncoding]] retain]; } #pragma mark Parsing support methods static const NSUInteger kAutoreleasePoolPurgeFrequency = 20; - (void)finishedCurrentTopic { [self performSelectorOnMainThread:@selector(parsedTopic:) withObject:currentTopic waitUntilDone:NO]; // performSelectorOnMainThread: will retain the object until the selector has been performed // setting the local reference to nil ensures that the local reference will be released self.currentTopic = nil; countOfParsedTopics++; // Periodically purge the autorelease pool. The frequency of this action may need to be tuned according to the // size of the objects being parsed. The goal is to keep the autorelease pool from growing too large, but // taking this action too frequently would be wasteful and reduce performance. if (countOfParsedTopics == kAutoreleasePoolPurgeFrequency) { [downloadAndParsePool release]; self.downloadAndParsePool = [[NSAutoreleasePool alloc] init]; countOfParsedTopics = 0; } } #pragma mark NSXMLParser Parsing Callbacks // Constants for the XML element names that will be considered during the parse. // Declaring these as static constants reduces the number of objects created during the run // and is less prone to programmer error. static NSString *kName_Item = @"item"; static NSString *kName_Title = @"title"; static NSString *kName_Category = @"category"; static NSString *kName_Creator = @"dc:creator"; static NSString *kName_Description = @"description"; static NSString *kName_ReleaseDate = @"pubDate"; static NSString *kName_Guid = @"guid"; - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *) qualifiedName attributes:(NSDictionary *)attributeDict { //NSLog(@"%@", elementName); if ([elementName isEqualToString:kName_Item]) { self.currentTopic = [[[Topic alloc] init] autorelease]; } else if ([elementName isEqualToString:kName_Title] || [elementName isEqualToString:kName_Category] || [elementName isEqualToString:kName_Guid] || [elementName isEqualToString:kName_Creator] || [elementName isEqualToString:kName_Description] || [elementName isEqualToString:kName_ReleaseDate]) { [currentString setString:@""]; storingCharacters = YES; } } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if ([elementName isEqualToString:kName_Item]) { [self finishedCurrentTopic]; } else if ([elementName isEqualToString:kName_Title]) { NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"[^(]+" options:NSRegularExpressionCaseInsensitive error:nil]; NSArray *results = [regex matchesInString:currentString options:0 range:NSMakeRange(0, [currentString length])]; if ([results count] > 0) { NSString *title = [currentString substringWithRange:[[results objectAtIndex:0] range]]; currentTopic.title = title; } NSRegularExpression* regex2 = [NSRegularExpression regularExpressionWithPattern:@"(?<=\\()\\d+" options:NSRegularExpressionCaseInsensitive error:nil]; NSArray *results2 = [regex2 matchesInString:currentString options:0 range:NSMakeRange(0, [currentString length])]; if ([results2 count] > 0) { NSString *count = [currentString substringWithRange:[[results2 objectAtIndex:0] range]]; currentTopic.picCount = [NSNumber numberWithInt:[count intValue]]; //NSLog(@"%@", count); } else currentTopic.picCount = [NSNumber numberWithInt:1]; //currentTopic.title = currentString; } else if ([elementName isEqualToString:kName_Category]) { currentTopic.category = currentString; } else if ([elementName isEqualToString:kName_Guid]) { currentTopic.guid = currentString; } else if ([elementName isEqualToString:kName_Creator]) { //currentTopic.creator = currentString; } else if ([elementName isEqualToString:kName_Description]) { NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:@"http\\S+(jpg|png|gif|jpeg)" options:NSRegularExpressionCaseInsensitive error:nil]; NSArray *results = [regex matchesInString:currentString options:0 range:NSMakeRange(0, [currentString length])]; // for (NSTextCheckingResult *res in results) { // NSLog(@"regexed: %@", [currentString substringWithRange:res.range]); // } if ([results count] > 0) { NSString *url = [currentString substringWithRange:[[results objectAtIndex:0] range]]; //NSLog(@"%@", url); currentTopic.iconUrl = url; } currentTopic.description = currentString; } else if ([elementName isEqualToString:kName_ReleaseDate]) { currentTopic.releaseDate = [parseFormatter dateFromString:currentString]; } storingCharacters = NO; } - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string { //NSLog(@"foundCharacters: %@", string); // storing string is when you want the inner text if (storingCharacters) [currentString appendString:string]; } /* A production application should include robust error handling as part of its parsing implementation. The specifics of how errors are handled depends on the application. */ - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { NSLog(@"parseError: %@", parseError); // Handle errors as appropriate for your application. } @end