diff --git a/Classes/MWFeedInfo.h b/Classes/MWFeedInfo.h index 3473071..c672099 100644 --- a/Classes/MWFeedInfo.h +++ b/Classes/MWFeedInfo.h @@ -31,16 +31,19 @@ @interface MWFeedInfo : NSObject { - NSString *title; // Feed title - NSString *link; // Feed link - NSString *summary; // Feed summary / description - NSURL *url; // Feed url - + NSString *title; // Feed title + NSString *link; // Feed link + NSString *summary; // Feed summary / description + NSURL *url; // Feed url + + // Custom attributes: + // - NSDictionary with the keys related to custom attributes defined on parser + NSDictionary *customAttributes; } @property (nonatomic, copy) NSString *title; @property (nonatomic, copy) NSString *link; @property (nonatomic, copy) NSString *summary; @property (nonatomic, copy) NSURL *url; - +@property (nonatomic, copy) NSDictionary *customAttributes; @end diff --git a/Classes/MWFeedInfo.m b/Classes/MWFeedInfo.m index d762318..fee32c3 100644 --- a/Classes/MWFeedInfo.m +++ b/Classes/MWFeedInfo.m @@ -33,7 +33,7 @@ @implementation MWFeedInfo -@synthesize title, link, summary, url; +@synthesize title, link, summary, url, customAttributes; #pragma mark NSObject @@ -54,6 +54,7 @@ - (id)initWithCoder:(NSCoder *)decoder { link = [decoder decodeObjectForKey:@"link"]; summary = [decoder decodeObjectForKey:@"summary"]; url = [decoder decodeObjectForKey:@"url"]; + customAttributes = [decoder decodeObjectForKey:@"customAttributes"]; } return self; } @@ -63,6 +64,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { if (link) [encoder encodeObject:link forKey:@"link"]; if (summary) [encoder encodeObject:summary forKey:@"summary"]; if (url) [encoder encodeObject:url forKey:@"url"]; + if (customAttributes) [encoder encodeObject:customAttributes forKey:@"customAttributes"]; } @end diff --git a/Classes/MWFeedItem.h b/Classes/MWFeedItem.h index 47bea85..b4c9393 100644 --- a/Classes/MWFeedItem.h +++ b/Classes/MWFeedItem.h @@ -47,6 +47,10 @@ // type: what its type is, a standard MIME type (NSString) NSArray *enclosures; + + // Custom attributes: + // - NSDictionary with the keys related to custom attributes defined on parser + NSDictionary *customAttributes; } @property (nonatomic, copy) NSString *identifier; @@ -58,5 +62,6 @@ @property (nonatomic, copy) NSString *content; @property (nonatomic, copy) NSString *author; @property (nonatomic, copy) NSArray *enclosures; +@property (nonatomic, copy) NSDictionary *customAttributes; @end diff --git a/Classes/MWFeedItem.m b/Classes/MWFeedItem.m index 7ccb7e4..f9aff69 100644 --- a/Classes/MWFeedItem.m +++ b/Classes/MWFeedItem.m @@ -33,7 +33,7 @@ @implementation MWFeedItem -@synthesize identifier, title, link, date, updated, summary, content, author, enclosures; +@synthesize identifier, title, link, date, updated, summary, content, author, enclosures, customAttributes; #pragma mark NSObject @@ -60,6 +60,8 @@ - (id)initWithCoder:(NSCoder *)decoder { content = [decoder decodeObjectForKey:@"content"]; author = [decoder decodeObjectForKey:@"author"]; enclosures = [decoder decodeObjectForKey:@"enclosures"]; + customAttributes = [decoder decodeObjectForKey:@"customAttributes"]; + } return self; } @@ -74,6 +76,8 @@ - (void)encodeWithCoder:(NSCoder *)encoder { if (content) [encoder encodeObject:content forKey:@"content"]; if (author) [encoder encodeObject:author forKey:@"author"]; if (enclosures) [encoder encodeObject:enclosures forKey:@"enclosures"]; + if (customAttributes) [encoder encodeObject:customAttributes forKey:@"customAttributes"]; + } @end diff --git a/Classes/MWFeedParser.h b/Classes/MWFeedParser.h index d2a8a39..11f6896 100644 --- a/Classes/MWFeedParser.h +++ b/Classes/MWFeedParser.h @@ -83,7 +83,9 @@ typedef enum { FeedTypeUnknown, FeedTypeRSS, FeedTypeRSS1, FeedTypeAtom } FeedTy NSXMLParser *feedParser; FeedType feedType; NSDateFormatter *dateFormatterRFC822, *dateFormatterRFC3339; - + NSArray *customFeedChannelKeys; + NSArray *customFeedItemKeys; + // Parsing State NSURL *url; BOOL aborted; // Whether parse stopped due to abort @@ -117,6 +119,12 @@ typedef enum { FeedTypeUnknown, FeedTypeRSS, FeedTypeRSS1, FeedTypeAtom } FeedTy // Set whether to download asynchronously or synchronously @property (nonatomic) ConnectionType connectionType; +// Custom attributes to find in the feed channel +@property (nonatomic) NSArray *customFeedChannelKeys; + +// Custom attributes to find in the feed item +@property (nonatomic) NSArray *customFeedItemKeys; + // Whether parsing was stopped @property (nonatomic, readonly, getter=isStopped) BOOL stopped; diff --git a/Classes/MWFeedParser.m b/Classes/MWFeedParser.m index 7d900ad..fc9c542 100644 --- a/Classes/MWFeedParser.m +++ b/Classes/MWFeedParser.m @@ -54,6 +54,7 @@ @implementation MWFeedParser @synthesize feedParseType, feedParser, currentPath, currentText, currentElementAttributes, item, info; @synthesize pathOfElementWithXHTMLType; @synthesize stopped, failed, parsing; +@synthesize customFeedChannelKeys, customFeedItemKeys; #pragma mark - #pragma mark NSObject @@ -196,6 +197,7 @@ - (void)startParsingData:(NSData *)data textEncodingName:(NSString *)textEncodin MWFeedInfo *i = [[MWFeedInfo alloc] init]; i.url = self.url; self.info = i; + self.currentCustomPropertiesOfChannel = [NSMutableDictionary dictionary]; // Check whether it's UTF-8 if (![[textEncodingName lowercaseString] isEqualToString:@"utf-8"]) { @@ -515,7 +517,7 @@ - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName nam // New item MWFeedItem *newItem = [[MWFeedItem alloc] init]; self.item = newItem; - + self.currentCustomPropertiesOfItem = [NSMutableDictionary dictionary]; // Return return; @@ -583,6 +585,34 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName // Remove newlines and whitespace from currentText NSString *processedText = [currentText stringByRemovingNewLinesAndWhitespace]; + __weak typeof(self)selfB = self; + + void (^findCustomChannelKeysWithBasePath)(NSString *) = ^ (NSString *path){ + [selfB.customFeedChannelKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) { + NSString *searchPath = [NSString stringWithFormat:path, key]; + if ([currentPath isEqualToString:searchPath]) { + if (processedText.length > 0) { + selfB.currentCustomPropertiesOfChannel[key] = processedText; + } else if (currentElementAttributes.count) { + selfB.currentCustomPropertiesOfChannel[key] = currentElementAttributes; + } + } + }]; + }; + + void (^findCustomItemKeysWithBasePath)(NSString *) = ^ (NSString *path){ + [selfB.customFeedItemKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop) { + NSString *searchPath = [NSString stringWithFormat:path, key]; + if ([currentPath isEqualToString:searchPath]) { + if (processedText.length > 0) { + selfB.currentCustomPropertiesOfItem[key] = processedText; + } else if (currentElementAttributes.count) { + selfB.currentCustomPropertiesOfItem[key] = currentElementAttributes; + } + } + }]; + }; + // Process switch (feedType) { case FeedTypeRSS: { @@ -603,6 +633,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName else if ([currentPath isEqualToString:@"/rss/channel/item/pubDate"]) { if (processedText.length > 0) item.date = [NSDate dateFromInternetDateTimeString:processedText formatHint:DateFormatHintRFC822]; processed = YES; } else if ([currentPath isEqualToString:@"/rss/channel/item/enclosure"]) { [self createEnclosureFromAttributes:currentElementAttributes andAddToItem:item]; processed = YES; } else if ([currentPath isEqualToString:@"/rss/channel/item/dc:date"]) { if (processedText.length > 0) item.date = [NSDate dateFromInternetDateTimeString:processedText formatHint:DateFormatHintRFC3339]; processed = YES; } + else if (self.customFeedItemKeys.count) { findCustomItemKeysWithBasePath(@"/rss/channel/item/%@"); } } // Info @@ -610,6 +641,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName if ([currentPath isEqualToString:@"/rss/channel/title"]) { if (processedText.length > 0) info.title = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/rss/channel/description"]) { if (processedText.length > 0) info.summary = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/rss/channel/link"]) { if (processedText.length > 0) info.link = processedText; processed = YES; } + else if (self.customFeedChannelKeys.count) { findCustomChannelKeysWithBasePath(@"/rss/channel/%@"); } } break; @@ -630,6 +662,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName else if ([currentPath isEqualToString:@"/rdf:RDF/item/dc:creator"]) { if (processedText.length > 0) item.author = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/rdf:RDF/item/dc:date"]) { if (processedText.length > 0) item.date = [NSDate dateFromInternetDateTimeString:processedText formatHint:DateFormatHintRFC3339]; processed = YES; } else if ([currentPath isEqualToString:@"/rdf:RDF/item/enc:enclosure"]) { [self createEnclosureFromAttributes:currentElementAttributes andAddToItem:item]; processed = YES; } + else if (self.customFeedItemKeys.count) { findCustomItemKeysWithBasePath(@"/rdf:RDF/item/%@"); } } // Info @@ -637,6 +670,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName if ([currentPath isEqualToString:@"/rdf:RDF/channel/title"]) { if (processedText.length > 0) info.title = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/rdf:RDF/channel/description"]) { if (processedText.length > 0) info.summary = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/rdf:RDF/channel/link"]) { if (processedText.length > 0) info.link = processedText; processed = YES; } + else if (self.customFeedChannelKeys.count) { findCustomChannelKeysWithBasePath(@"/rdf:RDF/channel/%@"); } } break; @@ -658,6 +692,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName else if ([currentPath isEqualToString:@"/feed/entry/dc:creator"]) { if (processedText.length > 0) item.author = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/feed/entry/published"]) { if (processedText.length > 0) item.date = [NSDate dateFromInternetDateTimeString:processedText formatHint:DateFormatHintRFC3339]; processed = YES; } else if ([currentPath isEqualToString:@"/feed/entry/updated"]) { if (processedText.length > 0) item.updated = [NSDate dateFromInternetDateTimeString:processedText formatHint:DateFormatHintRFC3339]; processed = YES; } + else if (self.customFeedItemKeys.count) { findCustomItemKeysWithBasePath(@"/feed/entry/%@"); } } // Info @@ -665,6 +700,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName if ([currentPath isEqualToString:@"/feed/title"]) { if (processedText.length > 0) info.title = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/feed/description"]) { if (processedText.length > 0) info.summary = processedText; processed = YES; } else if ([currentPath isEqualToString:@"/feed/link"]) { [self processAtomLink:currentElementAttributes andAddToMWObject:info]; processed = YES;} + else if (self.customFeedChannelKeys.count) { findCustomChannelKeysWithBasePath(@"/feed/%@"); } } break; @@ -681,6 +717,10 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName if (((feedType == FeedTypeRSS || feedType == FeedTypeRSS1) && [qName isEqualToString:@"item"]) || (feedType == FeedTypeAtom && [qName isEqualToString:@"entry"])) { + if(self.currentCustomPropertiesOfItem.count) { + item.customAttributes = self.currentCustomPropertiesOfItem; + } + // Dispatch item to delegate [self dispatchFeedItemToDelegate]; @@ -693,6 +733,11 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName (feedType == FeedTypeRSS1 && [qName isEqualToString:@"rdf:RDF"]) || (feedType == FeedTypeAtom && [qName isEqualToString:@"feed"])) { + + if(self.currentCustomPropertiesOfChannel.count) { + info.customAttributes = self.currentCustomPropertiesOfChannel; + } + // Document ending so if we havent sent off feed info yet, do so if (info && feedParseType != ParseTypeItemsOnly) [self dispatchFeedInfoToDelegate]; @@ -804,6 +849,7 @@ - (void)dispatchFeedInfoToDelegate { // Finish self.info = nil; + self.currentCustomPropertiesOfChannel = nil; } } @@ -824,6 +870,7 @@ - (void)dispatchFeedItemToDelegate { // Finish self.item = nil; + self.currentCustomPropertiesOfItem = nil; } } diff --git a/Classes/MWFeedParser_Private.h b/Classes/MWFeedParser_Private.h index 4f69583..895d543 100644 --- a/Classes/MWFeedParser_Private.h +++ b/Classes/MWFeedParser_Private.h @@ -47,6 +47,9 @@ @property (nonatomic, strong) MWFeedInfo *info; @property (nonatomic, copy) NSString *pathOfElementWithXHTMLType; +@property (nonatomic, strong) NSMutableDictionary *currentCustomPropertiesOfChannel; +@property (nonatomic, strong) NSMutableDictionary *currentCustomPropertiesOfItem; + #pragma mark Private Methods // Parsing Methods diff --git a/MWFeedParser.podspec b/MWFeedParser.podspec index a5f3b96..ee96221 100644 --- a/MWFeedParser.podspec +++ b/MWFeedParser.podspec @@ -11,6 +11,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/mwaterfall/MWFeedParser.git', :tag => '1.0.1' + :tag => '1.0.1b' } s.platform = :ios, '5.1.1' s.requires_arc = true diff --git a/MWFeedParser.xcodeproj/project.pbxproj b/MWFeedParser.xcodeproj/project.pbxproj index f7f461c..18c2c76 100755 --- a/MWFeedParser.xcodeproj/project.pbxproj +++ b/MWFeedParser.xcodeproj/project.pbxproj @@ -158,8 +158,8 @@ 4CC89602119F0CDE00ED61B6 /* MWFeedParser */ = { isa = PBXGroup; children = ( - 4CC89603119F0CED00ED61B6 /* MWFeedParser.h */, 4CAA79C911A406890009078E /* MWFeedParser_Private.h */, + 4CC89603119F0CED00ED61B6 /* MWFeedParser.h */, 4CC89604119F0CED00ED61B6 /* MWFeedParser.m */, 4CC89605119F0CED00ED61B6 /* MWFeedInfo.h */, 4CC89606119F0CED00ED61B6 /* MWFeedInfo.m */, @@ -320,8 +320,7 @@ ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_ENABLE_OBJC_ARC = NO; CODE_SIGN_IDENTITY = ""; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 5.1.1; @@ -336,8 +335,7 @@ ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_ENABLE_OBJC_ARC = NO; CODE_SIGN_IDENTITY = ""; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_VERSION = com.apple.compilers.llvmgcc42; + GCC_C_LANGUAGE_STANDARD = gnu99; GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 5.1.1;