
#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; cpos<ccount; cpos++){
	RSSNode *cnode = [children objectAtIndex:cpos];
	NSString *cstr = [cnode description];

	[desc
	    replaceCharactersInRange:
		NSMakeRange(shift+[cnode offset], 0)
	    withString:cstr];
	shift += [cstr length];
    }
    
    return
	[NSString 
	    stringWithFormat:
		(ccount>0 ? 
		 @"<%@%@>\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
