/*
SRSDRSSDocument.m

Author: Makoto Kinoshita

Copyright 2004 The Shiira Project. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted 
provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions 
  and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of 
  conditions and the following disclaimer in the documentation and/or other materials provided 
  with the distribution.

THIS SOFTWARE IS PROVIDED BY THE SHIIRA PROJECT ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SHIIRA PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/

#import <objc/objc-runtime.h>
#import "SRSDRSSDocument.h"
#import "NSDateEx.h"
#import "NSXMLEx.h"
#import "SRSDUtil.h"

@interface SRSDRSSDocument (private)
- (NSString*)itemValueForXPath:(NSString*)key atIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (private)

- (NSString*)itemValueForXPath:(NSString*)path atIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:path];
}

@end

#pragma mark -

@interface SRSDRSSDocument (Content)
- (NSString*)contentEncodedAtIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (Content)

- (NSString*)contentEncodedAtIndex:(unsigned int)index
{
    return [self itemValueForXPath:@"content:encoded" atIndex:index];
}

@end

@interface SRSDRSSDocument (DublinCore)
- (NSString*)dcTitleAtIndex:(unsigned int)index;
- (NSString*)dcCreatorAtIndex:(unsigned int)index;
- (NSString*)dcSubjectAtIndex:(unsigned int)index;
- (NSString*)dcDescriptionAtIndex:(unsigned int)index;
- (NSString*)dcPublisherAtIndex:(unsigned int)index;
- (NSString*)dcContributorAtIndex:(unsigned int)index;
- (NSString*)dcDateAtIndex:(unsigned int)index;
- (NSString*)dcTypeAtIndex:(unsigned int)index;
- (NSString*)dcFormatAtIndex:(unsigned int)index;
- (NSString*)dcIdentifierAtIndex:(unsigned int)index;
- (NSString*)dcSourceAtIndex:(unsigned int)index;
- (NSString*)dcLanguageAtIndex:(unsigned int)index;
- (NSString*)dcRelationAtIndex:(unsigned int)index;
- (NSString*)dcCoverageAtIndex:(unsigned int)index;
- (NSString*)dcRightsAtIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (DublinCore)

- (NSString*)dcTitleAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:title" atIndex:index];
}

- (NSString*)dcCreatorAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:creator" atIndex:index];
}

- (NSString*)dcSubjectAtIndex:(unsigned int)index
{
     return [self itemValueForXPath:@"dc:subject" atIndex:index];
}

- (NSString*)dcDescriptionAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:description" atIndex:index];
}

- (NSString*)dcPublisherAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:subject" atIndex:index];
}

- (NSString*)dcContributorAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:contributor" atIndex:index];
}

- (NSString*)dcDateAtIndex:(unsigned int)index
{
     return [self itemValueForXPath:@"dc:date" atIndex:index];
}

- (NSString*)dcTypeAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:type" atIndex:index];
}

- (NSString*)dcFormatAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:format" atIndex:index];
}

- (NSString*)dcIdentifierAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:identifier" atIndex:index];
}

- (NSString*)dcSourceAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:source" atIndex:index];
}

- (NSString*)dcLanguageAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:language" atIndex:index];
}

- (NSString*)dcRelationAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:relation" atIndex:index];
}

- (NSString*)dcCoverageAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:converage" atIndex:index];
}

- (NSString*)dcRightsAtIndex:(unsigned int)index;
{
     return [self itemValueForXPath:@"dc:rights" atIndex:index];
}

@end

#pragma mark -

@interface SRSDRSSDocument (RSS10)

// Channel
- (NSString*)RSS10_channelTitle;
- (NSString*)RSS10_channelLink;
- (NSString*)RSS10_channelImageURL;
- (NSDate*)RSS10_channelDate;

// Item
- (NSArray*)RSS10_items;
- (unsigned int)RSS10_countOfItems;
- (NSString*)RSS10_itemTitleAtIndex:(unsigned int)index;
- (NSString*)RSS10_itemLinkAtIndex:(unsigned int)index;
- (NSString*)RSS10_itemDescriptionAtIndex:(unsigned int)index;
- (NSString*)RSS10_itemCategoryAtIndex:(unsigned int)index;
- (NSDate*)RSS10_itemDateAtIndex:(unsigned int)index;

- (void)RSS10_addItem:(SRXMLNode*)item;
- (void)RSS10_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index;
- (void)RSS10_removeItemAtIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (RSS10)

//--------------------------------------------------------------//
#pragma mark -- Channel --
//--------------------------------------------------------------//

- (NSString*)_RSS10_ChannelValueForXPath:(NSString*)path
{
    if (!_rdfChannelElement) {
        return nil;
    }
    
    // Get value for path
    return [_rdfChannelElement stringValueForXPath:path];
}

- (NSString*)RSS10_channelTitle
{
    return [self _RSS10_ChannelValueForXPath:@"title"];
}

- (NSString*)RSS10_channelLink
{
    return [self _RSS10_ChannelValueForXPath:@"link"];
}

- (NSString*)RSS10_channelImageURL
{
    NSString*   imageURL;
    imageURL = [self _RSS10_ChannelValueForXPath:@"image/url"];
    if (imageURL) {
        return imageURL;
    }
    
    return [_rdfElement stringValueForXPath:@"image/url"];
}

- (NSDate*)RSS10_channelDate
{
    NSString*       pubDate;
    pubDate = [self _RSS10_ChannelValueForXPath:@"dc:date"];
    if (!pubDate) {
        pubDate = [self _RSS10_ChannelValueForXPath:@"pubDate"];
    }
    if (!pubDate) {
        pubDate = [self _RSS10_ChannelValueForXPath:@"lastBuildDate"];
    }
    if (!pubDate) {
        return nil;
    }
    return [NSDate dateWithFormattedString:pubDate];
}

//--------------------------------------------------------------//
#pragma mark -- Item --
//--------------------------------------------------------------//

- (NSArray*)RSS10_items
{
    NSArray*    items;
    items = [_rdfElement nodesForXPath:@"item" error:NULL];
    if (items) {
        return items;
    }
    
    items = [_rdfChannelElement nodesForXPath:@"item" error:NULL];
    if (items) {
        return items;
    }
    
    return nil;
}

- (unsigned int)RSS10_countOfItems
{
    return [[self RSS10_items] count];
}

- (NSString*)RSS10_itemTitleAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self RSS10_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"title"];
}

- (NSString*)RSS10_itemLinkAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self RSS10_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"link"];
}

- (NSString*)RSS10_itemDescriptionAtIndex:(unsigned int)index
{
    NSString*   contentEncoded;
    contentEncoded = [self contentEncodedAtIndex:index];
    if (contentEncoded) {
        return contentEncoded;
    }
    
    NSArray*    nodes;
    nodes = [self RSS10_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"description"];
}

- (NSString*)RSS10_itemCategoryAtIndex:(unsigned int)index
{
    NSString*   dcSubject;
    dcSubject = [self dcSubjectAtIndex:index];
    return dcSubject;
}

- (NSDate*)RSS10_itemDateAtIndex:(unsigned int)index
{
    NSString*   dcDate;
    dcDate = [self dcDateAtIndex:index];
    if (dcDate) {
        return [NSDate dateWithFormattedString:dcDate];
    }
    
    // Use channel date
    return [self RSS10_channelDate];
}

- (void)RSS10_addItem:(SRXMLNode*)item
{
    [_rdfElement addChild:[[item copy] autorelease]];
}

- (void)RSS10_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index
{
    [_rdfElement insertChild:[[item copy] autorelease] atIndex:index];
}

- (void)RSS10_removeItemAtIndex:(unsigned int)index
{
    // Get item
    NSArray*        items;
    SRXMLElement*   item;
    items = [self RSS10_items];
    if ([items count] < index) {
        NSLog(@"Item count is less than index, %d", index);
        return;
    }
    item = [[self RSS10_items] objectAtIndex:index];
    
    // Remove item
    unsigned int    itemIndex;
    itemIndex = [[_rdfElement children] indexOfObject:item];
    if (itemIndex == NSNotFound) {
        NSLog(@"Can't find item in children");
        return;
    }
    [_rdfElement removeChildAtIndex:itemIndex];
}

@end

#pragma mark -

@interface SRSDRSSDocument (RSS20)

// Channel
- (NSString*)RSS20_channelTitle;
- (NSString*)RSS20_channelLink;
- (NSString*)RSS20_channelImageURL;
- (NSDate*)RSS20_channelDate;

// Item
- (NSArray*)RSS20_items;
- (unsigned int)RSS20_countOfItems;
- (NSString*)RSS20_itemTitleAtIndex:(unsigned int)index;
- (NSString*)RSS20_itemLinkAtIndex:(unsigned int)index;
- (NSString*)RSS20_itemDescriptionAtIndex:(unsigned int)index;
- (NSString*)RSS20_itemCategoryAtIndex:(unsigned int)index;
- (NSDate*)RSS20_itemDateAtIndex:(unsigned int)index;

- (void)RSS20_addItem:(SRXMLNode*)item;
- (void)RSS20_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index;
- (void)RSS20_removeItemAtIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (RSS20)

//--------------------------------------------------------------//
#pragma mark -- Channel --
//--------------------------------------------------------------//

- (NSString*)_RSS20_ChannelValueForXPath:(NSString*)path
{
    if (!_rssChannelElement) {
        return nil;
    }
    
    // Get value for path
    return [_rssChannelElement stringValueForXPath:path];
}

- (NSString*)RSS20_channelTitle
{
    return [self _RSS20_ChannelValueForXPath:@"title"];
}

- (NSString*)RSS20_channelLink
{
    return [self _RSS20_ChannelValueForXPath:@"link"];
}

- (NSString*)RSS20_channelImageURL
{
    return [self _RSS20_ChannelValueForXPath:@"image/url"];
}

- (NSDate*)RSS20_channelDate
{
    NSString*       pubDate;
    pubDate = [self _RSS20_ChannelValueForXPath:@"pubDate"];
    if (!pubDate) {
        pubDate = [self _RSS20_ChannelValueForXPath:@"lastBuildDate"];
    }
    if (!pubDate) {
        return nil;
    }
    return [NSDate dateWithFormattedString:pubDate];
}

//--------------------------------------------------------------//
#pragma mark -- Item --
//--------------------------------------------------------------//

- (NSArray*)RSS20_items
{
    return [_rssChannelElement nodesForXPath:@"item" error:NULL];
}

- (unsigned int)RSS20_countOfItems
{
    return [[self RSS20_items] count];
}

- (NSString*)RSS20_itemTitleAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self RSS20_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"title"];
}

- (NSString*)RSS20_itemLinkAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self RSS20_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"link"];
}

- (NSString*)RSS20_itemDescriptionAtIndex:(unsigned int)index
{
    NSString*   contentEncoded;
    contentEncoded = [self contentEncodedAtIndex:index];
    if (contentEncoded) {
        return contentEncoded;
    }
    
    NSArray*    nodes;
    nodes = [self RSS20_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"description"];
}

- (NSString*)RSS20_itemCategoryAtIndex:(unsigned int)index
{
    NSString*   dcSubject;
    dcSubject = [self dcSubjectAtIndex:index];
    if (dcSubject) {
        return dcSubject;
    }
    
    NSArray*    nodes;
    nodes = [self RSS20_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"category"];
}

- (NSDate*)RSS20_itemDateAtIndex:(unsigned int)index
{
    NSString*   dcDate;
    dcDate = [self dcDateAtIndex:index];
    if (dcDate) {
        return [NSDate dateWithFormattedString:dcDate];
    }
    
    NSArray*    nodes;
    nodes = [self RSS20_items];
    if (nodes && [nodes count] > index) {
        SRXMLElement*   itemElement;
        NSString*       pubDate;
        itemElement = [nodes objectAtIndex:index];
        pubDate = [itemElement stringValueForXPath:@"pubDate"];
        if (pubDate) {
            return [NSDate dateWithFormattedString:pubDate];
        }
    }
    
    // Use channel date
    return [self RSS20_channelDate];
}

- (void)RSS20_addItem:(SRXMLNode*)item
{
    [_rssChannelElement addChild:[[item copy] autorelease]];
}

- (void)RSS20_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index
{
    [_rssChannelElement insertChild:[[item copy] autorelease] atIndex:index];
}

- (void)RSS20_removeItemAtIndex:(unsigned int)index
{
    // Get item
    NSArray*        items;
    SRXMLElement*   item;
    items = [self RSS20_items];
    if ([items count] < index) {
        NSLog(@"Item count is less than index, %d", index);
        return;
    }
    item = [[self RSS20_items] objectAtIndex:index];
    
    // Remove item
    unsigned int    itemIndex;
    itemIndex = [[_rdfElement children] indexOfObject:item];
    if (itemIndex == NSNotFound) {
        NSLog(@"Can't find item in children");
        return;
    }
    [_rdfElement removeChildAtIndex:itemIndex];
}

@end

#pragma mark -

@interface SRSDRSSDocument (Atom)

// Channel
- (NSString*)Atom_channelTitle;
- (NSString*)Atom_channelLink;
- (NSString*)Atom_channelImageURL;
- (NSDate*)Atom_channelDate;

// Item
- (NSArray*)Atom_items;
- (unsigned int)Atom_countOfItems;
- (NSString*)Atom_itemTitleAtIndex:(unsigned int)index;
- (NSString*)Atom_itemLinkAtIndex:(unsigned int)index;
- (NSString*)Atom_itemDescriptionAtIndex:(unsigned int)index;
- (NSString*)Atom_itemCategoryAtIndex:(unsigned int)index;
- (NSDate*)Atom_itemDateAtIndex:(unsigned int)index;

- (void)Atom_addItem:(SRXMLNode*)item;
- (void)Atom_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index;
- (void)Atom_removeItemAtIndex:(unsigned int)index;
@end

#pragma mark -

@implementation SRSDRSSDocument (Atom)

//--------------------------------------------------------------//
#pragma mark -- Channel --
//--------------------------------------------------------------//

- (NSString*)_Atom_ChannelValueForXPath:(NSString*)path
{
    if (!_feedElement) {
        return nil;
    }
    
    // Get value for path
    return [_feedElement stringValueForXPath:path];
}

- (NSString*)Atom_channelTitle
{
    return [self _Atom_ChannelValueForXPath:@"title"];
}

- (NSString*)Atom_channelLink
{
    NSArray*        nodes;
    SRXMLElement*   linkElement;
    nodes = [_feedElement nodesForXPath:@"link" error:NULL];
    if (!nodes || [nodes count] == 0) {
        return nil;
    }
    linkElement = [nodes objectAtIndex:0];
    
    id  hrefAttr;
    hrefAttr = [linkElement attributeForName:@"href"];
    if (!hrefAttr) {
        return nil;
    }
    
    return [hrefAttr stringValue];
}

- (NSString*)Atom_channelImageURL
{
    return [self _Atom_ChannelValueForXPath:@"logo"];
}

- (NSDate*)Atom_channelDate
{
    NSString*       pubDate;
    pubDate = [self _Atom_ChannelValueForXPath:@"updated"];
    if (!pubDate) {
        pubDate = [self _Atom_ChannelValueForXPath:@"modified"];
    }
    if (!pubDate) {
        return nil;
    }
    return [NSDate dateWithFormattedString:pubDate];
}

//--------------------------------------------------------------//
#pragma mark -- Item --
//--------------------------------------------------------------//

- (NSArray*)Atom_items
{
    return [_feedElement nodesForXPath:@"entry" error:NULL];
}

- (unsigned int)Atom_countOfItems
{
    return [[self Atom_items] count];
}

- (NSString*)Atom_itemTitleAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self Atom_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"title"];
}

- (NSString*)Atom_itemLinkAtIndex:(unsigned int)index
{
    NSArray*    nodes;
    nodes = [self Atom_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    
    SRXMLElement*   linkElement;
    nodes = [itemElement nodesForXPath:@"link" error:NULL];
    if (!nodes || [nodes count] == 0) {
        return nil;
    }
    linkElement = [nodes objectAtIndex:0];
    
    id  hrefAttr;
    hrefAttr = [linkElement attributeForName:@"href"];
    if (!hrefAttr) {
        return nil;
    }
    
    return [hrefAttr stringValue];
}

- (NSString*)Atom_itemDescriptionAtIndex:(unsigned int)index
{
    NSString*   contentEncoded;
    contentEncoded = [self contentEncodedAtIndex:index];
    if (contentEncoded) {
        return contentEncoded;
    }
    
    NSArray*    nodes;
    nodes = [self Atom_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"content"];
}

- (NSString*)Atom_itemCategoryAtIndex:(unsigned int)index
{
    NSString*   dcSubject;
    dcSubject = [self dcSubjectAtIndex:index];
    if (dcSubject) {
        return dcSubject;
    }
    
    NSArray*    nodes;
    nodes = [self Atom_items];
    if (!nodes || [nodes count] < index) {
        return nil;
    }
    
    SRXMLElement*   itemElement;
    itemElement = [nodes objectAtIndex:index];
    return [itemElement stringValueForXPath:@"category"];
}

- (NSDate*)Atom_itemDateAtIndex:(unsigned int)index
{
    NSString*   dcDate;
    dcDate = [self dcDateAtIndex:index];
    if (dcDate) {
        return [NSDate dateWithFormattedString:dcDate];
    }
    
    NSArray*    nodes;
    nodes = [self Atom_items];
    if (nodes && [nodes count] > index) {
        SRXMLElement*   itemElement;
        NSString*       pubDate;
        itemElement = [nodes objectAtIndex:index];
        
        pubDate = [itemElement stringValueForXPath:@"updated"];
        if (pubDate) {
            return [NSDate dateWithFormattedString:pubDate];
        }
        
        pubDate = [itemElement stringValueForXPath:@"modified"];
        if (pubDate) {
            return [NSDate dateWithFormattedString:pubDate];
        }
    }
    
    // Use channel date
    return [self Atom_channelDate];
}

- (void)Atom_addItem:(SRXMLNode*)item
{
//    [_feedElement addChild:[[item copy] autorelease]];
}

- (void)Atom_insertItem:(SRXMLNode*)item atIndex:(unsigned int)index
{
//    [_feedElement insertChild:[[item copy] autorelease] atIndex:index];
}

- (void)Atom_removeItemAtIndex:(unsigned int)index
{
#if 0
    // Get item
    NSArray*        items;
    SRXMLElement*   item;
    items = [self Atom_items];
    if ([items count] < index) {
        NSLog(@"Item count is less than index, %d", index);
        return;
    }
    item = [[self Atom_items] objectAtIndex:index];
    
    // Remove item
    unsigned int    itemIndex;
    itemIndex = [[_rdfElement children] indexOfObject:item];
    if (itemIndex == NSNotFound) {
        NSLog(@"Can't find item in children");
        return;
    }
    [_feedElement removeChildAtIndex:itemIndex];
#endif
}

@end

#pragma mark -

@implementation SRSDRSSDocument

//--------------------------------------------------------------//
#pragma mark -- Initialize --
//--------------------------------------------------------------//

- (void)_init
{
    // Create identifier
    _identifier = [SRCreateUUID() retain];
    
    // Get rdf:RDF element
    _rdfElement = (SRXMLElement*)[_document singleNodeForXPath:@"/rdf:RDF"];
    if (_rdfElement) {
        _rdfChannelElement = (SRXMLElement*)[_rdfElement singleNodeForXPath:@"channel"];
    }
    
    // Get rss element
    _rssElement = (SRXMLElement*)[_document singleNodeForXPath:@"/rss"];
    if (_rssElement) {
        _rssChannelElement = (SRXMLElement*)[_rssElement singleNodeForXPath:@"channel"];
    }
    
    // Get feed element
    _feedElement = (SRXMLElement*)[_document singleNodeForXPath:@"/feed"];
    
    // Decide channel
    NSDate* channelDate;
    channelDate = [self channelDate];
    if (!channelDate) {
        // Use first item date
        if ([[self items] count] > 0) {
            channelDate = [self itemDateAtIndex:0];
        }
    }
    if (!channelDate) {
#if 0
        // Use now
        channelDate = [NSDate date];
        [self setChannelDate:channelDate];
#endif
    }
}

- (id)initWithContentsOfURL:(NSURL*)url options:(unsigned int)mask error:(NSError**)error
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _document = [[NSClassFromString(@"SRXMLDocument") alloc] initWithContentsOfURL:url options:mask error:error];
    
    [self _init];
    
    return self;
}

- (id)initWithData:(NSData*)data options:(unsigned int)mask error:(NSError**)error
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _document = [[NSClassFromString(@"SRXMLDocument") alloc] initWithData:data options:mask error:error];
    
    [self _init];
    
    return self;
}

- (id)initWithXMLString:(NSString*)string options:(unsigned int)mask error:(NSError**)error
{
    self = [super init];
    if (!self) {
        return nil;
    }
    
    _document = [[NSClassFromString(@"SRXMLDocument") alloc] initWithXMLString:string options:mask error:error];
    
    [self _init];
    
    return self;
}

- (void)dealloc
{
    [_document release];
    [_identifier release];
    
    [_channelDate release];
    
    [super dealloc];
}

//--------------------------------------------------------------//
#pragma mark -- XML document --
//--------------------------------------------------------------//

- (SRXMLDocument*)document
{
    return _document;
}

//--------------------------------------------------------------//
#pragma mark -- Identifier --
//--------------------------------------------------------------//

- (NSString*)identifier
{
    return _identifier;
}

- (void)setIdentifier:(NSString*)identifier
{
    [_identifier release];
    _identifier = [[identifier copyWithZone:[self zone]] retain];
}

//--------------------------------------------------------------//
#pragma mark -- Channel --
//--------------------------------------------------------------//

- (SRXMLElement*)channelElement
{
    if (_rdfChannelElement) {
        return _rdfChannelElement;
    }
    if (_rssChannelElement) {
        return _rssChannelElement;
    }
    if (_feedElement) {
        return _feedElement;
    }
    
    return nil;
}

- (NSString*)channelTitle
{
    if (_rdfChannelElement) {
        return [self RSS10_channelTitle];
    }
    if (_rssChannelElement) {
        return [self RSS20_channelTitle];
    }
    if (_feedElement) {
        return [self Atom_channelTitle];
    }
    
    return nil;
}

- (NSString*)channelLink
{
    if (_rdfChannelElement) {
        return [self RSS10_channelLink];
    }
    if (_rssChannelElement) {
        return [self RSS20_channelLink];
    }
    if (_feedElement) {
        return [self Atom_channelLink];
    }
    
    return nil;
}

- (NSString*)channelImageURL
{
    if (_rdfChannelElement) {
        return [self RSS10_channelImageURL];
    }
    if (_rssChannelElement) {
        return [self RSS20_channelImageURL];
    }
    if (_feedElement) {
        return [self Atom_channelImageURL];
    }
    
    return nil;
}

- (NSDate*)channelDate
{
    NSDate* date;
    
    if (_rdfChannelElement) {
        date = [self RSS10_channelDate];
        if (date) {
            return date;
        }
    }
    if (_rssChannelElement) {
        date = [self RSS20_channelDate];
        if (date) {
            return date;
        }
    }
    if (_feedElement) {
        date = [self Atom_channelDate];
        if (date) {
            return date;
        }
    }
    
    return _channelDate;
}

- (void)setChannelDate:(NSDate*)date
{
    [_channelDate release];
    _channelDate = [date copy];
}

//--------------------------------------------------------------//
#pragma mark -- Item --
//--------------------------------------------------------------//

- (NSArray*)items
{
    if (_rdfElement) {
        return [self RSS10_items];
    }
    if (_rssChannelElement) {
        return [self RSS20_items];
    }
    if (_feedElement) {
        return [self Atom_items];
    }
    
    return nil;
}

- (unsigned int)countOfItems
{
    if (_rdfElement) {
        return [self RSS10_countOfItems];
    }
    if (_rssChannelElement) {
        return [self RSS20_countOfItems];
    }
    if (_feedElement) {
        return [self Atom_countOfItems];
    }
    
    return 0;
}

- (NSString*)itemTitleAtIndex:(unsigned int)index
{
    if (_rdfElement) {
        return [self RSS10_itemTitleAtIndex:index];
    }
    if (_rssChannelElement) {
        return [self RSS20_itemTitleAtIndex:index];
    }
    if (_feedElement) {
        return [self Atom_itemTitleAtIndex:index];
    }
    
    return nil;
}

- (NSString*)itemLinkAtIndex:(unsigned int)index
{
    if (_rdfElement) {
        return [self RSS10_itemLinkAtIndex:index];
    }
    if (_rssChannelElement) {
        return [self RSS20_itemLinkAtIndex:index];
    }
    if (_feedElement) {
        return [self Atom_itemLinkAtIndex:index];
    }
    
    return nil;
}

- (NSString*)itemDescriptionAtIndex:(unsigned int)index
{
    if (_rdfElement) {
        return [self RSS10_itemDescriptionAtIndex:index];
    }
    if (_rssChannelElement) {
        return [self RSS20_itemDescriptionAtIndex:index];
    }
    if (_feedElement) {
        return [self Atom_itemDescriptionAtIndex:index];
    }
    
    return nil;
}

- (NSString*)itemCategoryAtIndex:(unsigned int)index
{
    if (_rdfElement) {
        return [self RSS10_itemCategoryAtIndex:index];
    }
    if (_rssChannelElement) {
        return [self RSS20_itemCategoryAtIndex:index];
    }
    if (_feedElement) {
        return [self Atom_itemCategoryAtIndex:index];
    }
    
    return nil;
}

- (NSDate*)itemDateAtIndex:(unsigned int)index
{
    NSDate* date;
    
    if (_rdfElement) {
        date = [self RSS10_itemDateAtIndex:index];
        if (date) {
            return date;
        }
    }
    if (_rssChannelElement) {
        date = [self RSS20_itemDateAtIndex:index];
        if (date) {
            return date;
        }
    }
    if (_feedElement) {
        date = [self Atom_itemDateAtIndex:index];
        if (date) {
            return date;
        }
    }
    
    return [self channelDate];
}

- (NSString*)itemIdentifierAtIndex:(unsigned int)index
{
    // Get title and link
    NSString*   title;
    NSString*   link;
    title = [self itemTitleAtIndex:index];
    if (!title) {
        title = @"(Untitled)";
    }
    link = [self itemLinkAtIndex:index];
    if (!link) {
        link = @"(No link)";
    }
    
    return [[self class] itemIdentifierWithItemTitle:title andLink:link];
}

+ (NSString*)itemIdentifierWithItemTitle:(NSString*)title andLink:(NSString*)link
{
    if (!title || !link) {
        return nil;
    }
    
    return [NSString stringWithFormat:@"%@+%@", title, link];
}

+ (NSString*)itemIdentifierWithItem:(NSDictionary*)item
{
    NSString*   title;
    NSString*   link;
    title = [item objectForKey:@"title"];
    link = [item objectForKey:@"link"];
    if (!title || !link) {
        return nil;
    }
    
    return [self itemIdentifierWithItemTitle:title andLink:link];
}

- (unsigned int)indexOfItemIdentifier:(NSString*)itemIdentifier
{
    int i;
    for (i = 0; i < [self countOfItems]; i++) {
        NSString*   identifier;
        identifier = [self itemIdentifierAtIndex:i];
        if ([identifier isEqualToString:itemIdentifier]) {
            return i;
        }
    }
    
    return NSNotFound;
}

- (void)addItem:(SRXMLNode*)item
{
    if (_rdfElement) {
        [self RSS10_addItem:item];
    }
    if (_rssChannelElement) {
        [self RSS20_addItem:item];
    }
    if (_feedElement) {
        [self Atom_addItem:item];
    }
}

- (void)insertItem:(SRXMLNode*)item atIndex:(unsigned int)index
{
    if (_rdfElement) {
        [self RSS10_insertItem:item atIndex:index];
    }
    if (_rssChannelElement) {
        [self RSS20_insertItem:item atIndex:index];
    }
    if (_feedElement) {
        [self Atom_insertItem:item atIndex:index];
    }
}

- (void)removeItemAtIndex:(unsigned int)index
{
    if (_rdfElement) {
        [self RSS10_removeItemAtIndex:index];
    }
    if (_rssChannelElement) {
        [self RSS20_removeItemAtIndex:index];
    }
    if (_feedElement) {
        [self Atom_removeItemAtIndex:index];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Merge --
//--------------------------------------------------------------//

- (void)mergeItemsWithDocument:(SRSDRSSDocument*)document
{
    // Merge items
    int i;
    for (i = 0; i < [document countOfItems]; i++) {
        NSString*   itemId;
        itemId = [document itemIdentifierAtIndex:i];
        if ([self indexOfItemIdentifier:itemId] != NSNotFound) {
            continue;
        }
        
        // Insert at the top
        [self insertItem:[[document items] objectAtIndex:i] atIndex:0];
    }
}

//--------------------------------------------------------------//
#pragma mark -- Parse --
//--------------------------------------------------------------//

+ (NSMutableDictionary*)parseWithContentsOfURL:(NSURL*)url options:(unsigned int)mask error:(NSError**)error
{
    // Parse XML document
    SRSDRSSDocument*    document;
    document = [[SRSDRSSDocument alloc] initWithContentsOfURL:url options:mask error:error];
    if (!document) {
        // Failed to parse XML file
        return nil;
    }
    
    // Create channel dict
    NSMutableDictionary*    channelDict;
    channelDict = [NSMutableDictionary dictionary];
    
    NSString*   str;
    NSDate*     date;
    
    // Parse channel
    str = [document channelTitle];
    if (str) {
        [channelDict setObject:[NSString stringWithString:str] forKey:@"title"];
    }
    
    str = [document channelLink];
    if (str) {
        [channelDict setObject:[NSString stringWithString:str] forKey:@"link"];
    }
    
    str = [document channelImageURL];
    if (str) {
        [channelDict setObject:[NSString stringWithString:str] forKey:@"imageURL"];
    }
    
    date = [document channelDate];
    if (!date) {
        if ([document countOfItems] > 0) {
            date = [document itemDateAtIndex:0];
            if (date) {
                [document setChannelDate:date];
            }
        }
    }
    if (!date) {
#if 0
        date = [NSDate date];
        [document setChannelDate:date];
#endif
    }
    if (date) {
        [channelDict setObject:[[date copy] autorelease] forKey:@"date"];
    }
    
    // Create items
    NSMutableArray* items;
    items = [NSMutableArray array];
    [channelDict setObject:items forKey:@"items"];
    
    // Parse items
    int i;
    for (i = 0; i < [document countOfItems]; i++) {
        NSMutableDictionary*    item;
        item = [NSMutableDictionary dictionary];
        
        str = [document itemTitleAtIndex:i];
        if (str) {
            [item setObject:[NSString stringWithString:str] forKey:@"title"];
        }
        
        str = [document itemLinkAtIndex:i];
        if (str) {
            [item setObject:[NSString stringWithString:str] forKey:@"link"];
        }
        
        str = [document itemDescriptionAtIndex:i];
        if (str) {
            [item setObject:[NSString stringWithString:str] forKey:@"description"];
        }
        
        str = [document itemCategoryAtIndex:i];
        if (str) {
            [item setObject:[NSString stringWithString:str] forKey:@"category"];
        }
        
        date = [document itemDateAtIndex:i];
        if (!date) {
            date = [document channelDate];
        }
        if (date) {
            [item setObject:[[date copy] autorelease] forKey:@"date"];
        }
        
        // Add item
        [items addObject:item];
    }
    
    [document release];
    
    return channelDict;
}

+ (SRXMLDocument*)XMLDocumentWithChannel:(NSDictionary*)channel
{
    NSMutableString*    XML;
    XML = [NSMutableString string];
    
    [XML appendString:@"<?xml version=\"1.0\" encoding=\"UTF-8\"?><rss version=\"2.0\">"];
    
    NSString*   str;
    NSDate*     date;
    
    // Append channel info
    [XML appendString:@"<channel>"];
    
    str = [channel objectForKey:@"title"];
    if (str) {
        [XML appendFormat:@"<title>%@</title>", str];
    }
    str = [channel objectForKey:@"link"];
    if (str) {
        [XML appendFormat:@"<link>%@</link>", str];
    }
    date = [channel objectForKey:@"date"];
    if (date) {
        [XML appendFormat:@"<pubDate>%@</pubDate>", [date description]];
    }
    
    // Append item info
    NSEnumerator*   enumerator;
    NSDictionary*   item;
    enumerator = [[channel objectForKey:@"items"] objectEnumerator];
    while (item = [enumerator nextObject]) {
        [XML appendFormat:@"<item>"];
        
        str = [item objectForKey:@"title"];
        if (str) {
            [XML appendFormat:@"<title>%@</title>", str];
        }
        str = [item objectForKey:@"link"];
        if (str) {
            [XML appendFormat:@"<link>%@</link>", str];
        }
        str = [item objectForKey:@"category"];
        if (str) {
            [XML appendFormat:@"<category>%@</category>", str];
        }
        date = [item objectForKey:@"date"];
        if (date) {
            [XML appendFormat:@"<pubDate>%@</pubDate>", [date description]];
        }
        str = [item objectForKey:@"description"];
        if (str) {
            NSMutableString*    description;
            description = [NSMutableString stringWithString:str];
            [description replaceOccurrencesOfString:@"&" 
                    withString:@"&amp;" options:0 range:NSMakeRange(0, [description length])];
            [description replaceOccurrencesOfString:@"<" 
                    withString:@"&lt;" options:0 range:NSMakeRange(0, [description length])];
            [description replaceOccurrencesOfString:@">" 
                    withString:@"&gt;" options:0 range:NSMakeRange(0, [description length])];
            [description replaceOccurrencesOfString:@"\"" 
                    withString:@"&quot;" options:0 range:NSMakeRange(0, [description length])];
            [XML appendFormat:@"<description>%@</description>", description];
        }
        
        [XML appendFormat:@"</item>"];
    }
    
    [XML appendFormat:@"</channel></rss>"];
    
    NSData* data;
    data = [XML dataUsingEncoding:NSUTF8StringEncoding];
    
    SRXMLDocument*  XMLDocument;
    NSError*        error;
    XMLDocument = [[NSClassFromString(@"SRXMLDocument") alloc] initWithData:data options:0 error:&error];
    if (!XMLDocument) {
        // Error
        NSLog([error localizedDescription]);
        return nil;
    }
    [XMLDocument autorelease];
    
    return XMLDocument;
}

@end

#pragma mark -

//--------------------------------------------------------------//
#pragma mark -- Utility --
//--------------------------------------------------------------//

#if 0
NSString* SRSDDateStringFromString(
        NSString* dateString, 
        NSString* format)
{
    struct tm   tm;
    char*       result;
    memset(&tm, 0, sizeof(struct tm));
    result = strptime([dateString cString], "%a, %d %b %Y %H:%M:%S %Z", &tm);   // [Sat, 20 Sep 2005 12:34:56 +09:00]
    if (!result) {
        result = strptime([dateString cString], "%a, %d %b %Y %H:%M:%S %z", &tm);   // [20 Sep 2005 12:34:56 EDT]
    }
    if (!result) {
        result = strptime([dateString cString], "%d %b %Y %H:%M:%S %Z", &tm);   // [20 Sep 2005 12:34:56 +09:00]
    }
    if (!result) {
        result = strptime([dateString cString], "%d %b %Y %H:%M:%S %z", &tm);   // [20 Sep 2005 12:34:56 EDT]
    }
    if (!result) {
        result = strptime([dateString cString], "%Y-%m-%dT%H:%M:%S%z", &tm);    // [2005-09-20T12:34:56+09:00]
    }
    if (!result) {
        result = strptime([dateString cString], "%Y-%m-%dT%H:%M", &tm);         // [2005-09-20T12:34]
    }
    if (!result) {
        result = strptime([dateString cString], "%Y-%m-%d", &tm);               // [2005-09-20]
    }
    if (!result) {
        return nil;
    }
    
    char    buf[255];
    if (!format) {
        format = @"%Y-%m-%d %H:%M:%S";
        strftime(buf, sizeof(buf), [format cString], &tm);
        
        // Create offset string
        NSString*   offset;
        offset = [NSString stringWithCString:result];
        
        // For abbreviation
        NSTimeZone* timeZone;
        timeZone = [NSTimeZone timeZoneWithAbbreviation:offset];
        if (timeZone) {
            int seconds;
            seconds = [timeZone secondsFromGMT];
            
            NSString*   sign;
            if (seconds < 0) {
                sign = @"-";
                seconds *= -1;
            }
            else {
                sign = @"+";
            }
            
            offset = [NSString stringWithFormat:@"%@%02d%02d", sign, seconds / 3600, seconds % 3600];
        }
        // For '+HH:MM'
        else {
            NSRange     range;
            range = [offset rangeOfString:@":"];
            if (range.location != NSNotFound) {
                offset = [NSString stringWithFormat:@"%@%@", 
                        [offset substringToIndex:range.location], [offset substringFromIndex:range.location + range.length]];
            }
        }
        
        return [NSString stringWithFormat:@"%s %@", buf, offset];
    }
    else {
        strftime(buf, sizeof(buf), [format cString], &tm);
        return [NSString stringWithCString:buf];
    }
}

NSDate* SRSDDateFromString(
        NSString* dateString)
{
    // Create string
    NSString*   string;
    string = SRSDDateStringFromString(dateString, nil);
    if (!string) {
        return nil;
    }
    
    // Create date, omit time zone
    NSDate* date;
    date = [NSDate dateWithString:string];
    return date;
}
#endif
