summaryrefslogtreecommitdiffstats
path: root/Classes/AcidCowFeedburnerParser.m
blob: 46d7ec50f387686d21b584b9fef1982dd7c1815c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//
//  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 {
	NSLog(@"downloadAndParse");
    self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
    done = NO;
    self.parseFormatter = [[[NSDateFormatter alloc] init] autorelease];
    [parseFormatter setDateStyle:NSDateFormatterLongStyle];
    [parseFormatter setTimeStyle:NSDateFormatterNoStyle];
    // 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