-# $Id: Changes 1762 2005-01-01 17:35:44Z btrott $
+# $Id: Changes 1868 2005-08-09 20:42:29Z btrott $
Revision history for XML::Feed
+0.06 2005.08.09
+ - Added Feed->convert and Entry->convert methods to allow conversion
+ between formats.
+ - Added ability to create new Feed and Entry objects, add entries, etc.
+ - Added $PREFERRED_PARSER variable to allow usage of compatible
+ RSS parsers, like XML::RSS::LibXML. Thanks to Tatsuhiko Miyagawa
+ for the patch.
+
0.05 2005.01.01
- Call URI::Fetch::URI_GONE() instead of URI::Fetch::FEED_GONE(). Thanks
to Richard Clamp for the patch.
README
t/00-compile.t
t/01-parse.t
+t/02-create.t
t/samples/atom.xml
t/samples/rss10.xml
t/samples/rss20-no-summary.xml
name: XML-Feed
-version: 0.05
+version: 0.06
abstract: XML Syndication Feed Support
-author: Benjamin Trott <ben+cpan@stupidfool.org>
+author: Six Apart <cpan@sixapart.com>
license: perl
distribution_type: module
requires:
-# $Id: Makefile.PL 942 2004-12-31 23:01:21Z btrott $
+# $Id: Makefile.PL 1869 2005-08-10 00:02:25Z btrott $
use inc::Module::Install;
name('XML-Feed');
abstract('XML Syndication Feed Support');
-author('Benjamin Trott <ben+cpan@stupidfool.org>');
+author('Six Apart <cpan@sixapart.com>');
version_from('lib/XML/Feed.pm');
license('perl');
no_index(directory => 't');
-$Id: README 942 2004-12-31 23:01:21Z btrott $
+$Id: README 1869 2005-08-10 00:02:25Z btrott $
This is XML::Feed, an abstraction above the RSS and Atom syndication
feed formats. It supports both parsing and autodiscovery of feeds.
% make install
-Benjamin Trott / ben+cpan@stupidfool.org
+Six Apart / cpan@sixapart.com
-# $Id: Feed.pm 1762 2005-01-01 17:35:44Z btrott $
+# $Id: Feed.pm 1869 2005-08-10 00:02:25Z btrott $
package XML::Feed;
use strict;
use base qw( Class::ErrorHandler );
use Feed::Find;
use URI::Fetch;
+use Carp;
-our $VERSION = '0.05';
+our $VERSION = '0.06';
+
+sub new {
+ my $class = shift;
+ my($format) = @_;
+ $format ||= 'Atom';
+ my $format_class = 'XML::Feed::' . $format;
+ eval "use $format_class";
+ Carp::croak("Unsupported format $format: $@") if $@;
+ my $feed = bless {}, join('::', __PACKAGE__, $format);
+ $feed->init_empty or return $class->error($feed->errstr);
+ $feed;
+}
+
+sub init_empty { 1 }
sub parse {
my $class = shift;
@feeds;
}
+sub convert {
+ my $feed = shift;
+ my($format) = @_;
+ my $new = __PACKAGE__->new($format);
+ for my $field (qw( title link description language copyright modified generator )) {
+ $new->$field($feed->$field());
+ }
+ for my $entry ($feed->entries) {
+ $new->add_entry($entry->convert($format));
+ }
+ $new;
+}
+
sub format;
sub title;
sub link;
sub description;
sub language;
+sub author;
sub copyright;
sub modified;
sub generator;
+sub add_entry;
sub entries;
+sub as_xml;
-sub tagline { $_[0]->description }
+sub tagline { shift->description(@_) }
sub items { $_[0]->entries }
1;
=head1 USAGE
+=head2 XML::Feed->new($format)
+
+Creates a new empty I<XML::Feed> object using the format I<$format>.
+
=head2 XML::Feed->parse($stream)
Parses a syndication feed identified by I<$stream>. I<$stream> can be any
Returns a list of feed URIs.
+=head2 $feed->convert($format)
+
+Converts the I<XML::Feed> object into the I<$format> format, and returns
+the new object.
+
=head2 $feed->format
Returns the format of the feed (C<Atom>, or some version of C<RSS>).
-=head2 $feed->title
+=head2 $feed->title([ $title ])
The title of the feed/channel.
-=head2 $feed->link
+=head2 $feed->link([ $uri ])
The permalink of the feed/channel.
-=head2 $feed->tagline
+=head2 $feed->tagline([ $tagline ])
The description or tagline of the feed/channel.
-=head2 $feed->description
+=head2 $feed->description([ $description ])
Alias for I<$feed-E<gt>tagline>.
-=head2 $feed->language
+=head2 $feed->author([ $author ])
+
+The author of the feed/channel.
+
+=head2 $feed->language([ $language ])
The language of the feed.
-=head2 $feed->copyright
+=head2 $feed->copyright([ $copyright ])
The copyright notice of the feed.
-=head2 $feed->modified
+=head2 $feed->modified([ $modified ])
A I<DateTime> object representing the last-modified date of the feed.
-=head2 $feed->generator
+If present, I<$modified> should be a I<DateTime> object.
+
+=head2 $feed->generator([ $generator ])
The generator of the feed.
A list of the entries/items in the feed. Returns an array containing
I<XML::Feed::Entry> objects.
+=head2 $feed->add_entry($entry)
+
+Adds an entry to the feed. I<$entry> should be an I<XML::Feed::Entry>
+object in the correct format for the feed.
+
+=head2 $feed->as_xml
+
+Returns an XML representation of the feed, in the format determined by
+the current format of the I<$feed> object.
+
+=head1 PACKAGE VARIABLES
+
+=over 4
+
+=item C<$XML::Feed::RSS::PREFERRED_PARSER>
+
+If you want to use another RSS parser class than XML::RSS (default), you can
+change the class by setting C<$PREFERRED_PARSER> variable in XML::Feed::RSS
+package.
+
+ $XML::Feed::RSS::PREFERRED_PARSER = "XML::RSS::LibXML";
+
+B<Note:> this will only work for parsing feeds, not creating feeds.
+
+=back
+
=head1 LICENSE
I<XML::Feed> is free software; you may redistribute it and/or modify it
=head1 AUTHOR & COPYRIGHT
-Except where otherwise noted, I<XML::Feed> is Copyright 2004 Benjamin
-Trott, ben+cpan@stupidfool.org. All rights reserved.
+Except where otherwise noted, I<XML::Feed> is Copyright 2004-2005
+Six Apart, cpan@sixapart.com. All rights reserved.
=cut
-# $Id: Atom.pm 942 2004-12-31 23:01:21Z btrott $
+# $Id: Atom.pm 1865 2005-08-09 20:15:31Z btrott $
package XML::Feed::Atom;
use strict;
use XML::Atom::Util qw( iso2dt );
use List::Util qw( first );
+sub init_empty {
+ my $feed = shift;
+ $feed->{atom} = XML::Atom::Feed->new;
+ $feed;
+}
+
sub init_string {
my $feed = shift;
my($str) = @_;
sub format { 'Atom' }
-sub title { $_[0]->{atom}->title }
+sub title { shift->{atom}->title(@_) }
sub link {
- my $l = first { $_->rel eq 'alternate' } $_[0]->{atom}->link;
- $l ? $l->href : undef;
+ my $feed = shift;
+ if (@_) {
+ $feed->{atom}->add_link({ rel => 'alternate', href => $_[0],
+ type => 'text/html', });
+ } else {
+ my $l = first { $_->rel eq 'alternate' } $feed->{atom}->link;
+ $l ? $l->href : undef;
+ }
+}
+sub description { shift->{atom}->tagline(@_) }
+sub copyright { shift->{atom}->copyright(@_) }
+sub language { shift->{atom}->language(@_) }
+sub generator { shift->{atom}->generator(@_) }
+
+sub author {
+ my $feed = shift;
+ if (@_ && $_[0]) {
+ my $person = XML::Atom::Person->new;
+ $person->name($_[0]);
+ $feed->{atom}->author($person);
+ } else {
+ $feed->{atom}->author ? $feed->{atom}->author->name : undef;
+ }
+}
+
+sub modified {
+ my $feed = shift;
+ if (@_) {
+ $feed->{atom}->modified($_[0]->iso8601 . 'Z');
+ } else {
+ iso2dt($feed->{atom}->modified);
+ }
}
-sub description { $_[0]->{atom}->tagline }
-sub copyright { $_[0]->{atom}->copyright }
-sub language { $_[0]->{atom}->language }
-sub generator { $_[0]->{atom}->generator }
-sub author { $_[0]->{atom}->author ? $_[0]->{atom}->author->name : undef }
-sub modified { iso2dt($_[0]->{atom}->modified) }
sub entries {
my @entries;
for my $entry ($_[0]->{atom}->entries) {
- push @entries, XML::Feed::Atom::Entry->wrap($entry);
+ push @entries, XML::Feed::Entry::Atom->wrap($entry);
}
@entries;
}
-package XML::Feed::Atom::Entry;
+sub add_entry {
+ my $feed = shift;
+ my($entry) = @_;
+ $feed->{atom}->add_entry($entry->unwrap);
+}
+
+sub as_xml { $_[0]->{atom}->as_xml }
+
+package XML::Feed::Entry::Atom;
use strict;
use base qw( XML::Feed::Entry );
use XML::Atom::Util qw( iso2dt );
use XML::Feed::Content;
+use XML::Atom::Entry;
use List::Util qw( first );
-sub title { $_[0]->{entry}->title }
+sub init_empty {
+ my $entry = shift;
+ $entry->{entry} = XML::Atom::Entry->new;
+ 1;
+}
+
+sub title { shift->{entry}->title(@_) }
sub link {
- my $l = first { $_->rel eq 'alternate' } $_[0]->{entry}->link;
- $l ? $l->href : undef;
+ my $entry = shift;
+ if (@_) {
+ $entry->{entry}->add_link({ rel => 'alternate', href => $_[0],
+ type => 'text/html', });
+ } else {
+ my $l = first { $_->rel eq 'alternate' } $entry->{entry}->link;
+ $l ? $l->href : undef;
+ }
}
sub summary {
- XML::Feed::Content->wrap({ type => 'text/html',
- body => $_[0]->{entry}->summary });
+ my $entry = shift;
+ if (@_) {
+ $entry->{entry}->summary(ref($_[0]) eq 'XML::Feed::Content' ?
+ $_[0]->body : $_[0]);
+ } else {
+ XML::Feed::Content->wrap({ type => 'text/html',
+ body => $entry->{entry}->summary });
+ }
}
sub content {
- my $c = $_[0]->{entry}->content;
- XML::Feed::Content->wrap({ type => $c ? $c->type : undef,
- body => $c ? $c->body : undef });
+ my $entry = shift;
+ if (@_) {
+ my %param;
+ if (ref($_[0]) eq 'XML::Feed::Content') {
+ %param = (Body => $_[0]->body, Type => $_[0]->type || 'text/html');
+ } else {
+ %param = (Body => $_[0], Type => 'text/html');
+ }
+ $entry->{entry}->content(XML::Atom::Content->new(%param));
+ } else {
+ my $c = $entry->{entry}->content;
+ XML::Feed::Content->wrap({ type => $c ? $c->type : undef,
+ body => $c ? $c->body : undef });
+ }
}
sub category {
+ my $entry = shift;
my $ns = XML::Atom::Namespace->new(dc => 'http://purl.org/dc/elements/1.1/');
- $_[0]->{entry}->get($ns, 'subject');
+ if (@_) {
+ $entry->{entry}->set($ns, 'subject', $_[0]);
+ } else {
+ $entry->{entry}->get($ns, 'subject');
+ }
+}
+
+sub author {
+ my $entry = shift;
+ if (@_ && $_[0]) {
+ my $person = XML::Atom::Person->new;
+ $person->name($_[0]);
+ $entry->{entry}->author($person);
+ } else {
+ $entry->{entry}->author ? $entry->{entry}->author->name : undef;
+ }
+}
+
+sub id { shift->{entry}->id(@_) }
+
+sub issued {
+ my $entry = shift;
+ if (@_) {
+ $entry->{entry}->issued($_[0]->iso8601 . 'Z') if $_[0];
+ } else {
+ $entry->{entry}->issued ? iso2dt($entry->{entry}->issued) : undef;
+ }
}
-sub author { $_[0]->{entry}->author ? $_[0]->{entry}->author->name : undef }
-sub id { $_[0]->{entry}->id }
-sub issued { iso2dt($_[0]->{entry}->issued) }
-sub modified { iso2dt($_[0]->{entry}->modified) }
+sub modified {
+ my $entry = shift;
+ if (@_) {
+ $entry->{entry}->modified($_[0]->iso8601 . 'Z') if $_[0];
+ } else {
+ $entry->{entry}->modified ? iso2dt($entry->{entry}->modified) : undef;
+ }
+}
1;
-# $Id: Content.pm 937 2004-10-04 03:38:11Z btrott $
+# $Id: Content.pm 1862 2005-06-20 17:26:11Z btrott $
package XML::Feed::Content;
use strict;
my($c) = @_;
bless { %$c }, $class;
}
+*new = \&wrap;
sub _var {
my $content = shift;
-# $Id: Entry.pm 942 2004-12-31 23:01:21Z btrott $
+# $Id: Entry.pm 1865 2005-08-09 20:15:31Z btrott $
package XML::Feed::Entry;
use strict;
+use base qw( Class::ErrorHandler );
+
+use Carp;
sub wrap {
my $class = shift;
bless { entry => $item }, $class;
}
+sub unwrap { $_[0]->{entry} }
+
+sub new {
+ my $class = shift;
+ my($format) = @_;
+ $format ||= 'Atom';
+ my $format_class = 'XML::Feed::' . $format;
+ eval "use $format_class";
+ Carp::croak("Unsupported format $format: $@") if $@;
+ my $entry = bless {}, join('::', __PACKAGE__, $format);
+ $entry->init_empty or return $class->error($entry->errstr);
+ $entry;
+}
+
+sub init_empty { 1 }
+
+sub convert {
+ my $entry = shift;
+ my($format) = @_;
+ my $new = __PACKAGE__->new($format);
+ for my $field (qw( title link content summary category author id issued modified )) {
+ $new->$field($entry->$field());
+ }
+ $new;
+}
+
sub title;
sub link;
sub content;
=head1 USAGE
-=head2 $entry->title
+=head2 XML::Feed::Entry->new($format)
+
+Creates a new I<XML::Feed::Entry> object in the format I<$format>, which
+should be either I<RSS> or I<Atom>.
+
+=head2 $entry->convert($format)
+
+Converts the I<XML::Feed::Entry> object into the I<$format> format, and
+returns the new object.
+
+=head2 $entry->title([ $title ])
The title of the entry.
-=head2 $entry->link
+=head2 $entry->link([ $uri ])
The permalink of the entry, in most cases, except in cases where it points
instead to an offsite URI referenced in the entry.
-=head2 $entry->content
+=head2 $entry->content([ $content ])
Bn I<XML::Feed::Content> object representing the full entry body, or as
much as is available in the feed.
I<http://www.w3.org/1999/xhtml#body> elements, then fall back to a
I<E<lt>descriptionE<gt>> element.
-=head2 $entry->summary
+=head2 $entry->summary([ $summary ])
An I<XML::Feed::Content> object representing a short summary of the entry.
Possibly.
the summary. Otherwise, we assume that there isn't a summary, and return
an I<XML::Feed::Content> object with an empty string in the I<body>.
-=head2 $entry->category
+=head2 $entry->category([ $category ])
The category in which the entry was posted.
-=head2 $entry->author
+=head2 $entry->author([ $author ])
The name or email address of the person who posted the entry.
-=head2 $entry->id
+=head2 $entry->id([ $id ])
The unique ID of the entry.
-=head2 $entry->issued
+=head2 $entry->issued([ $issued ])
A I<DateTime> object representing the date and time at which the entry
was posted.
-=head2 $entry->modified
+If present, I<$issued> should be a I<DateTime> object.
+
+=head2 $entry->modified([ $modified ])
A I<DateTime> object representing the last-modified date of the entry.
+If present, I<$modified> should be a I<DateTime> object.
+
=head1 AUTHOR & COPYRIGHT
Please see the I<XML::Feed> manpage for author, copyright, and license
-# $Id: RSS.pm 942 2004-12-31 23:01:21Z btrott $
+# $Id: RSS.pm 1865 2005-08-09 20:15:31Z btrott $
package XML::Feed::RSS;
use strict;
use base qw( XML::Feed );
-use XML::RSS;
use DateTime::Format::Mail;
use DateTime::Format::W3CDTF;
+our $PREFERRED_PARSER = "XML::RSS";
+
+sub init_empty {
+ my $feed = shift;
+ eval "use $PREFERRED_PARSER"; die $@ if $@;
+ $feed->{rss} = $PREFERRED_PARSER->new( version => '2.0' );
+ $feed;
+}
+
sub init_string {
my $feed = shift;
my($str) = @_;
- my $rss = $feed->{rss} = XML::RSS->new;
+ $feed->init_empty;
if ($str) {
- $rss->parse($$str);
+ $feed->{rss}->parse($$str);
}
$feed;
}
sub format { 'RSS ' . $_[0]->{rss}->{'version'} }
## The following elements are the same in all versions of RSS.
-sub title { $_[0]->{rss}->channel('title') }
-sub link { $_[0]->{rss}->channel('link') }
-sub description { $_[0]->{rss}->channel('description') }
+sub title { shift->{rss}->channel('title', @_) }
+sub link { shift->{rss}->channel('link', @_) }
+sub description { shift->{rss}->channel('description', @_) }
## This is RSS 2.0 only--what's the equivalent in RSS 1.0?
-sub copyright { $_[0]->{rss}->channel('copyright') }
+sub copyright { shift->{rss}->channel('copyright', @_) }
## The following all work transparently in any RSS version.
sub language {
- $_[0]->{rss}->channel('language') ||
- $_[0]->{rss}->channel->{dc}{language}
+ my $feed = shift;
+ if (@_) {
+ $feed->{rss}->channel('language', $_[0]);
+ $feed->{rss}->channel->{dc}{language} = $_[0];
+ } else {
+ $feed->{rss}->channel('language') ||
+ $feed->{rss}->channel->{dc}{language};
+ }
}
sub generator {
- $_[0]->{rss}->channel('generator') ||
- $_[0]->{rss}->channel->{'http://webns.net/mvcb/'}{generatorAgent};
+ my $feed = shift;
+ if (@_) {
+ $feed->{rss}->channel('generator', $_[0]);
+ $feed->{rss}->channel->{'http://webns.net/mvcb/'}{generatorAgent} =
+ $_[0];
+ } else {
+ $feed->{rss}->channel('generator') ||
+ $feed->{rss}->channel->{'http://webns.net/mvcb/'}{generatorAgent};
+ }
}
sub author {
- $_[0]->{rss}->channel('webMaster') ||
- $_[0]->{rss}->channel->{dc}{creator};
+ my $feed = shift;
+ if (@_) {
+ $feed->{rss}->channel('webMaster', $_[0]);
+ $feed->{rss}->channel->{dc}{creator} = $_[0];
+ } else {
+ $feed->{rss}->channel('webMaster') ||
+ $feed->{rss}->channel->{dc}{creator};
+ }
}
sub modified {
- my $rss = $_[0]->{rss};
- if (my $ts = $rss->channel('pubDate')) {
- return DateTime::Format::Mail->parse_datetime($ts);
- } elsif ($ts = $rss->channel->{dc}{date}) {
- return DateTime::Format::W3CDTF->parse_datetime($ts);
+ my $rss = shift->{rss};
+ if (@_) {
+ $rss->channel('pubDate',
+ DateTime::Format::Mail->format_datetime($_[0]));
+ ## XML::RSS is so weird... if I set this, it will try to use
+ ## the value for the lastBuildDate, which I don't want--because
+ ## this date is formatted for an RSS 1.0 feed. So it's commented out.
+ #$rss->channel->{dc}{date} =
+ # DateTime::Format::W3CDTF->format_datetime($_[0]);
+ } else {
+ if (my $ts = $rss->channel('pubDate')) {
+ return DateTime::Format::Mail->parse_datetime($ts);
+ } elsif ($ts = $rss->channel->{dc}{date}) {
+ return DateTime::Format::W3CDTF->parse_datetime($ts);
+ }
}
}
my $rss = $_[0]->{rss};
my @entries;
for my $item (@{ $rss->{items} }) {
- push @entries, XML::Feed::RSS::Entry->wrap($item);
+ push @entries, XML::Feed::Entry::RSS->wrap($item);
}
@entries;
}
-package XML::Feed::RSS::Entry;
+sub add_entry {
+ my $feed = shift;
+ my($entry) = @_;
+ $feed->{rss}->add_item(%{ $entry->unwrap });
+}
+
+sub as_xml { $_[0]->{rss}->as_string }
+
+package XML::Feed::Entry::RSS;
use strict;
use XML::Feed::Content;
use base qw( XML::Feed::Entry );
-sub title { $_[0]->{entry}{title} }
-sub link { $_[0]->{entry}{link} || $_[0]->{entry}{guid} }
+sub init_empty { $_[0]->{entry} = { } }
+
+sub title {
+ my $entry = shift;
+ @_ ? $entry->{entry}{title} = $_[0] : $entry->{entry}{title};
+}
+
+sub link {
+ my $entry = shift;
+ if (@_) {
+ $entry->{entry}{link} = $_[0];
+ ## For RSS 2.0 output from XML::RSS. Sigh.
+ $entry->{entry}{permaLink} = $_[0];
+ } else {
+ $entry->{entry}{link} || $entry->{entry}{guid};
+ }
+}
sub summary {
- my $item = $_[0]->{entry};
- ## Some RSS feeds use <description> for a summary, and some use it
- ## for the full content. Pretty gross. We don't want to return the
- ## full content if the caller expects a summary, so the heuristic is:
- ## if the <entry> contains both a <description> and one of the elements
- ## typically used for the full content, use <description> as the summary.
- my $txt;
- if ($item->{description} &&
- ($item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} ||
- $item->{'http://www.w3.org/1999/xhtml'}{body})) {
- $txt = $item->{description};
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{description} = ref($_[0]) eq 'XML::Feed::Content' ?
+ $_[0]->body : $_[0];
+ ## Because of the logic below, we need to add some dummy content,
+ ## so that we'll properly recognize the description we enter as
+ ## the summary.
+ if (!$item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} &&
+ !$item->{'http://www.w3.org/1999/xhtml'}{body}) {
+ $item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} = ' ';
+ }
+ } else {
+ ## Some RSS feeds use <description> for a summary, and some use it
+ ## for the full content. Pretty gross. We don't want to return the
+ ## full content if the caller expects a summary, so the heuristic is:
+ ## if the <entry> contains both a <description> and one of the elements
+ ## typically used for the full content, use <description> as summary.
+ my $txt;
+ if ($item->{description} &&
+ ($item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} ||
+ $item->{'http://www.w3.org/1999/xhtml'}{body})) {
+ $txt = $item->{description};
+ }
+ XML::Feed::Content->wrap({ type => 'text/plain', body => $txt });
}
- XML::Feed::Content->wrap({ type => 'text/plain', body => $txt });
}
sub content {
- my $item = $_[0]->{entry};
- my $body =
- $_[0]->{entry}{'http://purl.org/rss/1.0/modules/content/'}{encoded} ||
- $_[0]->{entry}{'http://www.w3.org/1999/xhtml'}{body} ||
- $_[0]->{entry}{description};
- XML::Feed::Content->wrap({ type => 'text/html', body => $body });
+ my $item = shift->{entry};
+ if (@_) {
+ my $c = ref($_[0]) eq 'XML::Feed::Content' ? $_[0]->body : $_[0];
+ $item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} = $c;
+ } else {
+ my $body =
+ $item->{'http://purl.org/rss/1.0/modules/content/'}{encoded} ||
+ $item->{'http://www.w3.org/1999/xhtml'}{body} ||
+ $item->{description};
+ XML::Feed::Content->wrap({ type => 'text/html', body => $body });
+ }
}
sub category {
- $_[0]->{entry}{category} || $_[0]->{entry}{dc}{subject};
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{category} = $item->{dc}{subject} = $_[0];
+ } else {
+ $item->{category} || $item->{dc}{subject};
+ }
}
sub author {
- $_[0]->{entry}{author} || $_[0]->{entry}{dc}{creator};
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{author} = $item->{dc}{creator} = $_[0];
+ } else {
+ $item->{author} || $item->{dc}{creator};
+ }
}
## XML::RSS doesn't give us access to the rdf:about for the <item>,
## so we have to fall back to the <link> element in RSS 1.0 feeds.
sub id {
- $_[0]->{entry}{guid} || $_[0]->{entry}{link};
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{guid} = $_[0];
+ } else {
+ $item->{guid} || $item->{link};
+ }
}
sub issued {
- if (my $ts = $_[0]->{entry}{pubDate}) {
- my $parser = DateTime::Format::Mail->new;
- $parser->loose;
- return $parser->parse_datetime($ts);
- } elsif ($ts = $_[0]->{entry}{dc}{date}) {
- return DateTime::Format::W3CDTF->parse_datetime($ts);
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{dc}{date} = DateTime::Format::W3CDTF->format_datetime($_[0]);
+ $item->{pubDate} = DateTime::Format::Mail->format_datetime($_[0]);
+ } else {
+ if (my $ts = $item->{pubDate}) {
+ my $parser = DateTime::Format::Mail->new;
+ $parser->loose;
+ return $parser->parse_datetime($ts);
+ } elsif ($ts = $item->{dc}{date}) {
+ return DateTime::Format::W3CDTF->parse_datetime($ts);
+ }
}
}
sub modified {
- if (my $ts = $_[0]->{entry}{'http://purl.org/rss/1.0/modules/dcterms/'}{modified}) {
- return DateTime::Format::W3CDTF->parse_datetime($ts);
+ my $item = shift->{entry};
+ if (@_) {
+ $item->{'http://purl.org/rss/1.0/modules/dcterms/'}{modified} =
+ DateTime::Format::W3CDTF->format_datetime($_[0]);
+ } else {
+ if (my $ts = $item->{'http://purl.org/rss/1.0/modules/dcterms/'}{modified}) {
+ return DateTime::Format::W3CDTF->parse_datetime($ts);
+ }
}
}
-# $Id: 00-compile.t 922 2004-05-29 18:19:50Z btrott $
+# $Id: 00-compile.t 1867 2005-08-09 20:41:15Z btrott $
-my $loaded;
-BEGIN { print "1..1\n" }
-use XML::Feed;
-use XML::Feed::Entry;
-use XML::Feed::RSS;
-use XML::Feed::Atom;
-$loaded++;
-print "ok 1\n";
-END { print "not ok 1\n" unless $loaded }
+use strict;
+use Test::More tests => 4;
+
+use_ok('XML::Feed');
+use_ok('XML::Feed::Entry');
+use_ok('XML::Feed::RSS');
+use_ok('XML::Feed::Atom');
-# $Id: 01-parse.t 933 2004-07-29 16:43:33Z btrott $
+# $Id: 01-parse.t 1867 2005-08-09 20:41:15Z btrott $
use strict;
-use Test;
+use Test::More tests => 70;
use XML::Feed;
use URI;
-BEGIN { plan tests => 70 }
-
my %Feeds = (
't/samples/atom.xml' => 'Atom',
't/samples/rss10.xml' => 'RSS 1.0',
## First, test all of the various ways of calling parse.
my $feed;
my $file = 't/samples/atom.xml';
-ok($feed = XML::Feed->parse($file));
-ok($feed->title, 'First Weblog');
+$feed = XML::Feed->parse($file);
+isa_ok($feed, 'XML::Feed::Atom');
+is($feed->title, 'First Weblog');
open my $fh, $file or die "Can't open $file: $!";
-ok($feed = XML::Feed->parse($fh));
-ok($feed->title, 'First Weblog');
+$feed = XML::Feed->parse($fh);
+isa_ok($feed, 'XML::Feed::Atom');
+is($feed->title, 'First Weblog');
seek $fh, 0, 0;
my $xml = do { local $/; <$fh> };
-ok($feed = XML::Feed->parse(\$xml));
-ok($feed->title, 'First Weblog');
-ok($feed = XML::Feed->parse(URI->new("file:$file")));
-ok($feed->title, 'First Weblog');
+$feed = XML::Feed->parse(\$xml);
+isa_ok($feed, 'XML::Feed::Atom');
+is($feed->title, 'First Weblog');
+$feed = XML::Feed->parse(URI->new("file:$file"));
+isa_ok($feed, 'XML::Feed::Atom');
+is($feed->title, 'First Weblog');
## Then try calling all of the unified API methods.
for my $file (sort keys %Feeds) {
my $feed = XML::Feed->parse($file) or die XML::Feed->errstr;
- ok($feed);
- ok($feed->format, $Feeds{$file});
- ok($feed->language, 'en-us');
- ok($feed->title, 'First Weblog');
- ok($feed->link, 'http://localhost/weblog/');
- ok($feed->tagline, 'This is a test weblog.');
- ok($feed->description, 'This is a test weblog.');
+ my($subclass) = $Feeds{$file} =~ /^(\w+)/;
+ isa_ok($feed, 'XML::Feed::' . $subclass);
+ is($feed->format, $Feeds{$file});
+ is($feed->language, 'en-us');
+ is($feed->title, 'First Weblog');
+ is($feed->link, 'http://localhost/weblog/');
+ is($feed->tagline, 'This is a test weblog.');
+ is($feed->description, 'This is a test weblog.');
my $dt = $feed->modified;
- ok(ref($dt), 'DateTime');
+ isa_ok($dt, 'DateTime');
$dt->set_time_zone('UTC');
- ok($dt->iso8601, '2004-05-30T07:39:57');
- ok($feed->author, 'Melody');
+ is($dt->iso8601, '2004-05-30T07:39:57');
+ is($feed->author, 'Melody');
my @entries = $feed->entries;
- ok(scalar @entries, 2);
+ is(scalar @entries, 2);
my $entry = $entries[0];
- ok($entry->title, 'Entry Two');
- ok($entry->link, 'http://localhost/weblog/2004/05/entry_two.html');
+ is($entry->title, 'Entry Two');
+ is($entry->link, 'http://localhost/weblog/2004/05/entry_two.html');
$dt = $entry->issued;
- ok(ref($dt), 'DateTime');
+ isa_ok($dt, 'DateTime');
$dt->set_time_zone('UTC');
- ok($dt->iso8601, '2004-05-30T07:39:25');
- ok($entry->content->body =~ /<p>Hello!<\/p>/);
- ok($entry->summary->body, 'Hello!...');
- ok($entry->category, 'Travel');
- ok($entry->author, 'Melody');
+ is($dt->iso8601, '2004-05-30T07:39:25');
+ like($entry->content->body, qr/<p>Hello!<\/p>/);
+ is($entry->summary->body, 'Hello!...');
+ is($entry->category, 'Travel');
+ is($entry->author, 'Melody');
ok($entry->id);
}
or die XML::Feed->errstr;
my $entry = ($feed->entries)[0];
ok(!$entry->summary->body);
-ok($entry->content->body =~ m!<p>This is a test.</p>!);
+like($entry->content->body, qr/<p>This is a test.<\/p>/);
--- /dev/null
+# $Id$
+
+use strict;
+use Test::More tests => 66;
+use XML::Feed;
+use XML::Feed::Entry;
+use XML::Feed::Content;
+use DateTime;
+
+for my $format (qw( Atom RSS )) {
+ my $feed = XML::Feed->new($format);
+ isa_ok($feed, 'XML::Feed::' . $format);
+ like($feed->format, qr/^$format/, 'Format is correct');
+ $feed->title('My Feed');
+ is($feed->title, 'My Feed', 'feed title is correct');
+ $feed->link('http://www.example.com/');
+ is($feed->link, 'http://www.example.com/', 'feed link is correct');
+ $feed->description('Wow!');
+ is($feed->description, 'Wow!', 'feed description is correct');
+ is($feed->tagline, 'Wow!', 'tagline works as alias');
+ $feed->tagline('Again');
+ is($feed->tagline, 'Again', 'setting via tagline works');
+ $feed->language('en_US');
+ is($feed->language, 'en_US', 'feed language is correct');
+ $feed->author('Ben');
+ is($feed->author, 'Ben', 'feed author is correct');
+ $feed->copyright('Copyright 2005 Me');
+ is($feed->copyright, 'Copyright 2005 Me', 'feed copyright is correct');
+ my $now = DateTime->now;
+ $feed->modified($now);
+ isa_ok($feed->modified, 'DateTime', 'modified returns a DateTime');
+ is($feed->modified->iso8601, $now->iso8601, 'feed modified is correct');
+ $feed->generator('Movable Type');
+ is($feed->generator, 'Movable Type', 'feed generator is correct');
+ ok($feed->as_xml, 'as_xml returns something');
+
+ my $entry = XML::Feed::Entry->new($format);
+ isa_ok($entry, 'XML::Feed::Entry::' . $format);
+ $entry->title('Foo Bar');
+ is($entry->title, 'Foo Bar', 'entry title is correct');
+ $entry->link('http://www.example.com/foo/bar.html');
+ is($entry->link, 'http://www.example.com/foo/bar.html', 'entry link is correct');
+ $entry->summary('This is a summary.');
+ isa_ok($entry->summary, 'XML::Feed::Content');
+ is($entry->summary->body, 'This is a summary.', 'entry summary is correct');
+ $entry->content('This is the content.');
+ isa_ok($entry->content, 'XML::Feed::Content');
+ is($entry->content->type, 'text/html', 'entry content type is correct');
+ is($entry->content->body, 'This is the content.', 'entry content body is correct');
+ $entry->content(XML::Feed::Content->new({
+ body => 'This is the content (again).',
+ type => 'text/plain',
+ }));
+ isa_ok($entry->content, 'XML::Feed::Content');
+ is($entry->content->body, 'This is the content (again).', 'setting with XML::Feed::Content works');
+ $entry->category('Television');
+ is($entry->category, 'Television', 'entry category is correct');
+ $entry->author('Foo Baz');
+ is($entry->author, 'Foo Baz', 'entry author is correct');
+ $entry->id('foo:bar-15132');
+ is($entry->id, 'foo:bar-15132', 'entry id is correct');
+ my $dt = DateTime->now;
+ $entry->issued($dt);
+ isa_ok($entry->issued, 'DateTime');
+ is($entry->issued->iso8601, $dt->iso8601, 'entry issued is correct');
+ $entry->modified($dt);
+ isa_ok($entry->modified, 'DateTime');
+ is($entry->modified->iso8601, $dt->iso8601, 'entry modified is correct');
+
+ $feed->add_entry($entry);
+ my @e = $feed->entries;
+ is(scalar @e, 1, 'One post in the feed');
+ is($e[0]->title, 'Foo Bar', 'Correct post');
+}