Skip to content

Commit

Permalink
Updated to handle enclosures for attachments (podcasts, mp3s, etc.) f…
Browse files Browse the repository at this point in the history
…or RSS & Atom feeds. Item updated dates are now accessible if available (Atom only).

- [NEW] Better handling of feed: URI scheme
- [NEW] Now processes enclosures (RSS & Atom) for attachments (podcasts, mp3, etc.)
- [NEW] Feed item updated data now accessible if available (Atom feeds)
  • Loading branch information
mwaterfall committed Aug 8, 2010
1 parent dc55efa commit b95cdfa
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 193 deletions.
6 changes: 3 additions & 3 deletions Classes/MWFeedInfo.h
Expand Up @@ -10,9 +10,9 @@

@interface MWFeedInfo : NSObject <NSCoding> {

NSString *title;
NSString *link;
NSString *summary;
NSString *title; // Feed title
NSString *link; // Feed link
NSString *summary; // Feed summary / description

}

Expand Down
18 changes: 14 additions & 4 deletions Classes/MWFeedItem.h
Expand Up @@ -10,18 +10,28 @@

@interface MWFeedItem : NSObject <NSCoding> {

NSString *title;
NSString *link;
NSString *title; // Item title
NSString *link; // Item URL
NSDate *date; // Date the item was published
NSDate *updated; // Date the item was updated if available
NSString *summary; // Description of item
NSString *content; // More detailed content (if available)
NSDate *date;

// Enclosures: Holds 1 or more item enclosures (i.e. podcasts, mp3. pdf, etc)
// - NSArray of NSDictionaries with the following keys:
// url: where the enclosure is located (NSString)
// length: how big it is in bytes (NSNumber)
// type: what its type is, a standard MIME type (NSString)
NSArray *enclosures;

}

@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *link;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSDate *updated;
@property (nonatomic, copy) NSString *summary;
@property (nonatomic, copy) NSString *content;
@property (nonatomic, copy) NSDate *date;
@property (nonatomic, copy) NSArray *enclosures;

@end
8 changes: 7 additions & 1 deletion Classes/MWFeedItem.m
Expand Up @@ -12,7 +12,7 @@

@implementation MWFeedItem

@synthesize title, link, date, summary, content;
@synthesize title, link, date, updated, summary, content, enclosures;

#pragma mark NSObject

Expand All @@ -29,8 +29,10 @@ - (void)dealloc {
[title release];
[link release];
[date release];
[updated release];
[summary release];
[content release];
[enclosures release];
[super dealloc];
}

Expand All @@ -41,8 +43,10 @@ - (id)initWithCoder:(NSCoder *)decoder {
title = [[decoder decodeObjectForKey:@"title"] retain];
link = [[decoder decodeObjectForKey:@"link"] retain];
date = [[decoder decodeObjectForKey:@"date"] retain];
updated = [[decoder decodeObjectForKey:@"updated"] retain];
summary = [[decoder decodeObjectForKey:@"summary"] retain];
content = [[decoder decodeObjectForKey:@"content"] retain];
enclosures = [[decoder decodeObjectForKey:@"enclosures"] retain];
}
return self;
}
Expand All @@ -51,8 +55,10 @@ - (void)encodeWithCoder:(NSCoder *)encoder {
if (title) [encoder encodeObject:title forKey:@"title"];
if (link) [encoder encodeObject:link forKey:@"link"];
if (date) [encoder encodeObject:date forKey:@"date"];
if (updated) [encoder encodeObject:updated forKey:@"updated"];
if (summary) [encoder encodeObject:summary forKey:@"summary"];
if (content) [encoder encodeObject:content forKey:@"content"];
if (enclosures) [encoder encodeObject:enclosures forKey:@"enclosures"];
}

@end
102 changes: 89 additions & 13 deletions Classes/MWFeedParser.m
Expand Up @@ -35,9 +35,12 @@ @implementation MWFeedParser
- (id)initWithFeedURL:(NSString *)feedURL {
if (self = [super init]) {

// URL
self.url = [feedURL stringByReplacingOccurrencesOfString:@"feed://" withString:@"http://"];

// URI Scheme
// http://en.wikipedia.org/wiki/Feed:_URI_scheme
self.url = feedURL;
if ([url hasPrefix:@"feed://"]) self.url = [NSString stringWithFormat:@"http://%@", [url substringFromIndex:7]];
if ([url hasPrefix:@"feed:"]) self.url = [url substringFromIndex:5];

// Defaults
feedParseType = ParseTypeFull;
connectionType = ConnectionTypeSynchronously;
Expand Down Expand Up @@ -169,7 +172,7 @@ - (void)startParsingData:(NSData *)data {
MWFeedInfo *i = [[MWFeedInfo alloc] init];
self.info = i;
[i release];

// Parse!
feedParser = [[NSXMLParser alloc] initWithData:data];
feedParser.delegate = self;
Expand Down Expand Up @@ -426,8 +429,6 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName names
// Store data
BOOL processed = NO;
if (currentText) {

// Use
switch (feedType) {
case FeedTypeRSS: {

Expand All @@ -438,6 +439,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName names
else if ([currentPath isEqualToString:@"/rss/channel/item/description"]) { if (currentText.length > 0) item.summary = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/rss/channel/item/content:encoded"]) { if (currentText.length > 0) item.content = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/rss/channel/item/pubDate"]) { if (currentText.length > 0) item.date = [self dateFromRFC822String:currentText]; processed = YES; }
else if ([currentPath isEqualToString:@"/rss/channel/item/enclosure"]) { [self createEnclosureFromAttributes:currentElementAttributes andAddToItem:item]; processed = YES; }
}

// Info
Expand All @@ -458,6 +460,7 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName names
else if ([currentPath isEqualToString:@"/rdf:RDF/item/description"]) { if (currentText.length > 0) item.summary = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/rdf:RDF/item/content:encoded"]) { if (currentText.length > 0) item.content = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/rdf:RDF/item/dc:date"]) { if (currentText.length > 0) item.date = [self dateFromRFC3339String:currentText]; processed = YES; }
else if ([currentPath isEqualToString:@"/rdf:RDF/item/enc:enclosure"]) { [self createEnclosureFromAttributes:currentElementAttributes andAddToItem:item]; processed = YES; }
}

// Info
Expand All @@ -474,17 +477,18 @@ - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName names
// Item
if (!processed) {
if ([currentPath isEqualToString:@"/feed/entry/title"]) { if (currentText.length > 0) item.title = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/link"]) { NSString *link = [self linkFromAtomLinkAttributes:currentElementAttributes]; if (link) item.link = link; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/link"]) { [self processAtomLink:currentElementAttributes andAddToMWObject:item]; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/summary"]) { if (currentText.length > 0) item.summary = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/content"]) { if (currentText.length > 0) item.content = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/published"]) { if (currentText.length > 0) item.date = [self dateFromRFC3339String:currentText]; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/entry/updated"]) { if (currentText.length > 0) item.updated = [self dateFromRFC3339String:currentText]; processed = YES; }
}

// Info
if (!processed && feedParseType != ParseTypeItemsOnly) {
if ([currentPath isEqualToString:@"/feed/title"]) { if (currentText.length > 0) info.title = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/description"]) { if (currentText.length > 0) info.summary = currentText; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/link"]) { NSString *link = [self linkFromAtomLinkAttributes:currentElementAttributes]; if (link) info.link = link; processed = YES; }
else if ([currentPath isEqualToString:@"/feed/link"]) { [self processAtomLink:currentElementAttributes andAddToMWObject:info]; processed = YES;}
}

break;
Expand Down Expand Up @@ -661,12 +665,84 @@ - (BOOL)isStopped {
#pragma mark -
#pragma mark Misc

// Determine whether to use the link from atom feed (where rel is not set to "self" etc...)
- (NSString *)linkFromAtomLinkAttributes:(NSDictionary *)attributes {
if (attributes && [attributes objectForKey:@"rel"] && [[attributes objectForKey:@"rel"] isEqualToString:@"alternate"]) {
return [attributes objectForKey:@"href"];
// Create an enclosure NSDictionary from enclosure (or link) attributes
- (BOOL)createEnclosureFromAttributes:(NSDictionary *)attributes andAddToItem:(MWFeedItem *)currentItem {

// Create enclosure
NSDictionary *enclosure = nil;
NSString *encURL, *encType;
NSNumber *encLength;
if (attributes) {
switch (feedType) {
case FeedTypeRSS: { // http://cyber.law.harvard.edu/rss/rss.html#ltenclosuregtSubelementOfLtitemgt
// <enclosure>
encURL = [attributes objectForKey:@"url"];
encType = [attributes objectForKey:@"type"];
encLength = [NSNumber numberWithLongLong:[((NSString *)[attributes objectForKey:@"length"]) longLongValue]];
break;
}
case FeedTypeRSS1: { // http://www.xs4all.nl/~foz/mod_enclosure.html
// <enc:enclosure>
encURL = [attributes objectForKey:@"rdf:resource"];
encType = [attributes objectForKey:@"enc:type"];
encLength = [NSNumber numberWithLongLong:[((NSString *)[attributes objectForKey:@"enc:length"]) longLongValue]];
break;
}
case FeedTypeAtom: { // http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rel_attribute
// <link rel="enclosure" href=...
if ([[attributes objectForKey:@"rel"] isEqualToString:@"enclosure"]) {
encURL = [attributes objectForKey:@"href"];
encType = [attributes objectForKey:@"type"];
encLength = [NSNumber numberWithLongLong:[((NSString *)[attributes objectForKey:@"length"]) longLongValue]];
}
break;
}
}
}
if (encURL) {
NSMutableDictionary *e = [[NSMutableDictionary alloc] initWithCapacity:3];
[e setObject:encURL forKey:@"url"];
if (encType) [e setObject:encType forKey:@"type"];
if (encLength) [e setObject:encLength forKey:@"length"];
enclosure = [NSDictionary dictionaryWithDictionary:e];
[e release];
}

// Add to item
if (enclosure) {
if (currentItem.enclosures) {
currentItem.enclosures = [currentItem.enclosures arrayByAddingObject:enclosure];
} else {
currentItem.enclosures = [NSArray arrayWithObject:enclosure];
}
return YES;
} else {
return NO;
}

}

// Process ATOM link and determine whether to ignore it, add it as the link element or add as enclosure
// Links can be added to MWObject (info or item)
- (BOOL)processAtomLink:(NSDictionary *)attributes andAddToMWObject:(id)MWObject {
if (attributes && [attributes objectForKey:@"rel"]) {

// Use as link if rel == alternate
if ([[attributes objectForKey:@"rel"] isEqualToString:@"alternate"]) {
[MWObject setLink:[attributes objectForKey:@"href"]]; // Can be added to MWFeedItem or MWFeedInfo
return YES;
}

// Use as enclosure if rel == enclosure
if ([[attributes objectForKey:@"rel"] isEqualToString:@"enclosure"]) {
if ([MWObject isMemberOfClass:[MWFeedItem class]]) { // Enclosures can only be added to MWFeedItem
[self createEnclosureFromAttributes:attributes andAddToItem:(MWFeedItem *)MWObject];
return YES;
}
}

}
return nil;
return NO;
}

- (NSDate *)dateFromRFC822String:(NSString *)dateString {
Expand Down
3 changes: 2 additions & 1 deletion Classes/MWFeedParser_Private.h
Expand Up @@ -41,7 +41,8 @@
- (void)failWithErrorCode:(int)code description:(NSString *)description;

// Misc
- (NSString *)linkFromAtomLinkAttributes:(NSDictionary *)attributes;
- (BOOL)createEnclosureFromAttributes:(NSDictionary *)attributes andAddToItem:(MWFeedItem *)currentItem;
- (BOOL)processAtomLink:(NSDictionary *)attributes andAddToMWObject:(id)MWObject;
- (NSDate *)dateFromRFC822String:(NSString *)dateString;
- (NSDate *)dateFromRFC3339String:(NSString *)dateString;

Expand Down

0 comments on commit b95cdfa

Please sign in to comment.