#import "RSSHandler.h" #import "StringAdditions.h" @implementation RSSNode + nodeWithParent:(RSSNode *)theParent { RSSNode *inst = [self alloc]; [inst initWithParent:theParent]; [inst autorelease]; return inst; } - initWithParent:(RSSNode *)theParent { parent = theParent; children = [[NSMutableArray alloc] init]; name = nil; value = nil; attributes = nil; offset = 0; return self; } - setOffset:(unsigned long)pos { offset = pos; return self; } - (unsigned long)offset { return offset; } - setName:(NSString *)theName { ASSIGN(name, theName); return self; } - setValue:(NSString *)theValue { ASSIGN(value, theValue); return self; } - setAttributes:(NSMutableDictionary *)attribs { ASSIGN(attributes, attribs); return self; } - (NSString *)name { return name; } - (NSString *)value { return value; } - attributeNamed:(NSString *)aName { if(attributes==nil){ return nil; } return [attributes objectForKey:aName]; } - addChild:(RSSNode *)child { [children addObject:child]; return self; } - (NSArray *)children { return children; } - (RSSNode *)parent { return parent; } - (RSSNode *)childNamed:(NSString *)theName { int cpos; for(cpos=0; cpos<[children count]; cpos++){ RSSNode *child = [children objectAtIndex:cpos]; if([[child name] isEqualToString:theName]){ return child; } } return nil; } - (RSSNode *)childNamed:(NSString *)theName withAttribute:(NSString *)theAttr atValue:(NSString *)theValue { int cpos; for(cpos=0; cpos<[children count]; cpos++){ RSSNode *child = [children objectAtIndex:cpos]; if([[child name] isEqualToString:theName]){ NSString *aValue = [child attributeNamed:theAttr]; if(aValue!=nil && [aValue isEqualToString:theValue]){ return child; } } } return nil; } - (NSString *)description { NSMutableString *attrstr = [NSMutableString stringWithString:@""]; if(attributes!=nil){ NSEnumerator *keyen = [attributes keyEnumerator]; NSString *key; while((key=[keyen nextObject])!=nil){ [attrstr appendFormat:@" %@=\"%@\"", key, [attributes objectForKey:key]]; } } NSMutableString *desc = [NSMutableString stringWithString:value]; unsigned long shift = 0; int cpos, ccount = [children count]; for(cpos=0; cpos0 ? @"<%@%@>\n%@\n" : @"<%@%@>%@"), name, attrstr, desc, name]; } - (void)dealloc { TEST_RELEASE(name); TEST_RELEASE(value); TEST_RELEASE(attributes); [children release]; [super dealloc]; } @end @implementation RSSHandler + (RSSHandler *)rssHandler { RSSHandler *inst = [self alloc]; [inst init]; [inst autorelease]; return inst; } - init { [super init]; current = [[RSSNode alloc] initWithParent:nil]; [current setName:[[self class] description]]; messages = [NSMutableArray array]; [messages retain]; return self; } - (void)startElement:(NSString *)elementName attributes:(NSMutableDictionary *)elementAttributes { RSSNode *newNode = [RSSNode nodeWithParent:current]; [newNode setName:elementName]; [newNode setAttributes:elementAttributes]; NSString *val = [current value]; [newNode setOffset:(val==nil ? 0 : [val length])]; [current addChild:newNode]; current = newNode; } - (void)characters:(NSString *)value { NSString *val = [current value]; [current setValue: (val==nil ? value : [val stringByAppendingString:value])]; } - (void)cdataBlock:(NSData*)value { NSString *str = [[NSString alloc] initWithBytes:[value bytes] length:[value length] encoding:NSASCIIStringEncoding]; AUTORELEASE(str); RSSNode *cdata = [RSSNode nodeWithParent:current]; [cdata setName:[NSString stringWithString:@"cdata"]]; [cdata setValue:str]; [current addChild:cdata]; } - (void)endElement:(NSString *)elementName { current = [current parent]; } - (void)error:(NSString*)e colNumber:(int)colNumber lineNumber:(int)lineNumber { [self fatalError:e colNumber:colNumber lineNumber:lineNumber]; } - (void)fatalError:(NSString*)e colNumber:(int)colNumber lineNumber:(int)lineNumber { [messages addObject: [NSString stringWithFormat:@"col. %d, line %d: %@", colNumber, lineNumber, [e stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]]]; } - (RSSNode *)root { return current; } - (NSMutableArray *)messages { return messages; } - (void)dealloc { [current release]; [messages release]; [super dealloc]; } @end @implementation GenericArticle static id _english = nil; + (NSDictionary *)englishLocale { if(_english == nil){ NSBundle *gbundle = [NSBundle bundleForLibrary:@"gnustep-base"]; NSString *path = [gbundle pathForResource:@"English" ofType:nil inDirectory:@"Languages"]; if(path!=nil){ _english = [[NSDictionary alloc] initWithContentsOfFile:path]; } } return _english; } + (NSString *)authorFromAtomAuthor:(RSSNode *)subnode { NSString *anAuthor = [NSString stringWithString:@""]; RSSNode *field; if((field=[subnode childNamed:@"name"])!=nil){ anAuthor = [anAuthor stringByAppendingString:[field value]]; } if((field=[subnode childNamed:@"uri"])!=nil){ anAuthor = [anAuthor stringByAppendingFormat: @", %@", [field value]]; } if((field=[subnode childNamed:@"email"])!=nil){ anAuthor = [anAuthor stringByAppendingFormat: @", %@", [field value]]; } return anAuthor; } + (NSDate *)dateFromAtomDate:(RSSNode *)subnode { NSMutableString *datestr = [NSMutableString stringWithString:[subnode value]]; [datestr replaceOccurrencesOfString:@"T" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [datestr length])]; [datestr replaceOccurrencesOfString:@"Z" withString:@"+00:00" options:NSLiteralSearch range:NSMakeRange(0, [datestr length])]; NSDate *aDate = [NSCalendarDate dateWithString:datestr calendarFormat:ATOM_CALFORMAT1 locale:[GenericArticle englishLocale]]; if(aDate==nil){ aDate = [NSCalendarDate dateWithString:datestr calendarFormat:ATOM_CALFORMAT2 locale:[GenericArticle englishLocale]]; } return aDate; } + newWithNode:(RSSNode *)node inWindow:(TickerWindow *)win { id inst = [self alloc]; [inst initWithNode:node inWindow:win]; [inst autorelease]; return inst; } - initWithNode:(RSSNode *)node inWindow:(TickerWindow *)win { [self subclassResponsibility:_cmd]; return self; } - concludeInit { flags = 0; flags |= (title!=nil ? ART_HAS_TITLE : 0); flags |= (desc!=nil ? ART_HAS_DESC : 0); flags |= (guid!=nil ? ART_HAS_GUID : 0); flags |= (link!=nil ? ART_HAS_LINK : 0); flags |= (date!=nil ? ART_HAS_DATE : 0); flags |= (author!=nil ? ART_HAS_AUTHOR : 0); TEST_RETAIN(title); TEST_RETAIN(desc); TEST_RETAIN(guid); TEST_RETAIN(link); TEST_RETAIN(date); TEST_RETAIN(author); text = nil; uniqueid = nil; return self; } - buildTitle { if(flags & ART_HAS_DESC){ if(text==nil){ [self buildText]; } title = [[text firstComponent] stringByUnescapingHTML]; if([title length]>TEXTLEN_FOR_TITLE){ title = [title substringToIndex:TEXTLEN_FOR_TITLE]; } } else{ title = [NSString stringWithString:_(@"untitled")]; } [title retain]; return title; } - buildText { if(window==nil){ text = [NSString stringWithString: _(@"(no window for untag)")]; } else if([window respondsToSelector:@selector(untag:title:)]==NO){ text = [NSString stringWithString: _(@"(window does not respond to untag:title:)")]; } else if(desc!=nil){ text = [window untag:desc title:(title==nil ? _(@"(untitled)") : title)]; } else { text = [NSString stringWithString:_(@"(no text for this article)")]; } [text retain]; return self; } - buildUniqueid { if(flags & ART_HAS_GUID){ uniqueid = [NSString stringWithString:guid]; } else if(flags & ART_HAS_TITLE){ uniqueid = [NSString stringWithString:title]; } else if(flags & ART_HAS_DESC){ if(text==nil){ [self buildText]; } uniqueid = [text stringByTrimmingWhitespaceAndNewlines]; if([uniqueid length]>TEXTLEN_FOR_UNIQUEID){ uniqueid = [uniqueid substringToIndex:TEXTLEN_FOR_UNIQUEID]; } } else{ uniqueid = [self description]; } [uniqueid retain]; return self; } - (NSString *)title { if(title==nil){ [self buildTitle]; } return title; } - (NSString *)desc { return desc; } - (NSString *)text { if(text==nil){ [self buildText]; } return text; } - (NSString *)author { return author; } - (NSString *)uniqueid { if(uniqueid==nil){ [self buildUniqueid]; } return uniqueid; } - (NSString *)link { return link; } - (NSAttributedString *)contentWithTitleFont:(NSFont *)aFont foreground:(NSColor *)fg background:(NSColor *)bg { NSMutableAttributedString *content = [[NSMutableAttributedString alloc] initWithString: [NSString stringWithFormat:@"%@\n\n\n%@\n\n%@\n%@\n", [self title], [self text], (author==nil ? @"" : author), [self date]]]; AUTORELEASE(content); [content addAttribute:NSFontAttributeName value:aFont range:NSMakeRange(0, [[self title] length])]; [content addAttribute:NSForegroundColorAttributeName value:fg range:NSMakeRange(0, [content length])]; [content addAttribute:NSBackgroundColorAttributeName value:bg range:NSMakeRange(0, [content length])]; return content; } - setAuthor:(NSString *)anAuthor { ASSIGN(author, anAuthor); return self; } - setDate:(NSDate *)aDate { ASSIGN(date, aDate); return self; } - (NSDate *)date { return date; } - (NSComparisonResult)compareByDate:(GenericArticle *)anArticle { return [[anArticle date] compare:date]; } - (void)dealloc { TEST_RELEASE(title); TEST_RELEASE(desc); TEST_RELEASE(text); TEST_RELEASE(guid); TEST_RELEASE(uniqueid); TEST_RELEASE(link); TEST_RELEASE(date); TEST_RELEASE(author); [super dealloc]; } @end @implementation RSSArticle - initWithNode:(RSSNode *)node inWindow:(TickerWindow *)win { window = win; // not retained (circular) RSSNode *subnode; title = nil; desc = nil; guid = nil; link = nil; date = nil; if((subnode=[node childNamed:ART_FIELD_TITLE])!=nil){ title = [[[subnode value] stringByTrimmingWhitespaceAndNewlines] stringByUnescapingHTML]; if(![title length]){ title = [NSString stringWithString:_(@"empty title")]; } } if((subnode=[node childNamed:ART_FIELD_GUID])!=nil){ guid = [subnode value]; } if((subnode=[node childNamed:ART_FIELD_LINK])!=nil){ link = [subnode value]; } if((subnode=[node childNamed:ART_FIELD_DESC])!=nil){ RSSNode *cdata; if((cdata=[subnode childNamed:ART_FIELD_CDATA])!=nil){ desc = [cdata value]; } else{ desc = [subnode value]; } } if((subnode=[node childNamed:ART_FIELD_DATE])!=nil){ date = [NSCalendarDate dateWithString:[subnode value] calendarFormat:RSS_CALFORMAT1 locale:[GenericArticle englishLocale]]; if(date==nil){ date = [NSCalendarDate dateWithString:[subnode value] calendarFormat:RSS_CALFORMAT2 locale:[GenericArticle englishLocale]]; } // NSLog(@"parsed %@ into %@", [subnode value], date); } else if((subnode=[node childNamed:ART_FIELD_DATE1])!=nil){ date = [GenericArticle dateFromAtomDate:subnode]; } if((subnode=[node childNamed:ART_FIELD_ATHRAUTHOR])!=nil){ author = [subnode value]; } else if((subnode=[node childNamed:ART_FIELD_ATHRCREATOR])!=nil){ author = [subnode value]; } [self concludeInit]; return self; } @end @implementation AtomArticle - initWithNode:(RSSNode *)node inWindow:(TickerWindow *)win { window = win; // not retained (circular) RSSNode *subnode; title = nil; desc = nil; guid = nil; link = nil; date = nil; if((subnode=[node childNamed:ART_FIELD_TITLE])!=nil || (subnode=[node childNamed:ART_FIELD_SUMMARY])!=nil){ title = [[[subnode value] stringByTrimmingWhitespaceAndNewlines] stringByUnescapingHTML]; if(![title length]){ title = [NSString stringWithString:_(@"empty title")]; } } if((subnode=[node childNamed:ART_FIELD_ID])!=nil){ guid = [subnode value]; } if((subnode=[node childNamed:ART_FIELD_CONTENT])!=nil){ NSString *type = [subnode attributeNamed:@"type"]; RSSNode *div; if([type isEqualToString:@"text"] || [type isEqualToString:@"html"] || [type isEqualToString:@"text/html"] || [type hasPrefix:@"application/html"]){ desc = [subnode value]; } else if(([type isEqualToString:@"xhtml"] || [type isEqualToString:@"text/xhtml"] || [type hasPrefix:@"application/xhtml"]) && (div=[subnode childNamed:@"div"])!=nil){ desc = [div description]; } } else if((subnode=[node childNamed:ART_FIELD_SUMMARY])!=nil){ desc = [subnode value]; } if((subnode=[node childNamed:ART_FIELD_LINK withAttribute:@"type" atValue:@"text/html"])!=nil || ((subnode=[node childNamed:ART_FIELD_LINK])!=nil && [subnode attributeNamed:@"type"]==nil)){ link = [subnode attributeNamed:@"href"]; } if((subnode=[node childNamed:ART_FIELD_AUPDATED])!=nil || (subnode=[node childNamed:ART_FIELD_AMODIFIED])!=nil || (subnode=[node childNamed:ART_FIELD_ACREATED])!=nil || (subnode=[node childNamed:ART_FIELD_AISSUED])!=nil || (subnode=[node childNamed:ART_FIELD_APUBLISHED])!=nil){ date = [GenericArticle dateFromAtomDate:subnode]; } if((subnode=[node childNamed:ART_FIELD_ATHRAUTHOR])!=nil){ author = [GenericArticle authorFromAtomAuthor:subnode]; } [self concludeInit]; return self; } @end