From: Simon Wistow Date: Tue, 22 Apr 2008 18:00:32 +0000 (+0000) Subject: Load XML-Feed-0.06 into trunk. X-Git-Tag: v0.06^0 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=973e1f9eef850132765d4434d9f05e4ca7821c94;hp=b3b6d2fc8240a4bcccb5fc102e451f1a7e62c062;p=catagits%2FXML-Feed.git Load XML-Feed-0.06 into trunk. --- diff --git a/Changes b/Changes index 8887d36..732bc33 100644 --- a/Changes +++ b/Changes @@ -1,7 +1,15 @@ -# $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. diff --git a/MANIFEST b/MANIFEST index 62905cb..3ca848a 100644 --- a/MANIFEST +++ b/MANIFEST @@ -23,6 +23,7 @@ META.yml 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 diff --git a/META.yml b/META.yml index 7a29758..21b4070 100644 --- a/META.yml +++ b/META.yml @@ -1,7 +1,7 @@ name: XML-Feed -version: 0.05 +version: 0.06 abstract: XML Syndication Feed Support -author: Benjamin Trott +author: Six Apart license: perl distribution_type: module requires: diff --git a/Makefile.PL b/Makefile.PL index 1e684c0..951c7b8 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -1,10 +1,10 @@ -# $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 '); +author('Six Apart '); version_from('lib/XML/Feed.pm'); license('perl'); no_index(directory => 't'); diff --git a/README b/README index f553efc..af1caa1 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -$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. @@ -34,4 +34,4 @@ Then install it: % make install -Benjamin Trott / ben+cpan@stupidfool.org +Six Apart / cpan@sixapart.com diff --git a/lib/XML/Feed.pm b/lib/XML/Feed.pm index 634cdb0..6bb8bad 100644 --- a/lib/XML/Feed.pm +++ b/lib/XML/Feed.pm @@ -1,4 +1,4 @@ -# $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; @@ -6,8 +6,23 @@ 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; @@ -78,17 +93,33 @@ sub find_feeds { @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; @@ -142,6 +173,10 @@ I objects, which it then returns to the caller. =head1 USAGE +=head2 XML::Feed->new($format) + +Creates a new empty I object using the format I<$format>. + =head2 XML::Feed->parse($stream) Parses a syndication feed identified by I<$stream>. I<$stream> can be any @@ -174,39 +209,50 @@ from that page (using IlinkE> tags). Returns a list of feed URIs. +=head2 $feed->convert($format) + +Converts the I object into the I<$format> format, and returns +the new object. + =head2 $feed->format Returns the format of the feed (C, or some version of C). -=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-Etagline>. -=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 object representing the last-modified date of the feed. -=head2 $feed->generator +If present, I<$modified> should be a I object. + +=head2 $feed->generator([ $generator ]) The generator of the feed. @@ -215,6 +261,32 @@ The generator of the feed. A list of the entries/items in the feed. Returns an array containing I objects. +=head2 $feed->add_entry($entry) + +Adds an entry to the feed. I<$entry> should be an I +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 this will only work for parsing feeds, not creating feeds. + +=back + =head1 LICENSE I is free software; you may redistribute it and/or modify it @@ -222,7 +294,7 @@ under the same terms as Perl itself. =head1 AUTHOR & COPYRIGHT -Except where otherwise noted, I is Copyright 2004 Benjamin -Trott, ben+cpan@stupidfool.org. All rights reserved. +Except where otherwise noted, I is Copyright 2004-2005 +Six Apart, cpan@sixapart.com. All rights reserved. =cut diff --git a/lib/XML/Feed/Atom.pm b/lib/XML/Feed/Atom.pm index 0ae1ad3..cc55d3d 100644 --- a/lib/XML/Feed/Atom.pm +++ b/lib/XML/Feed/Atom.pm @@ -1,4 +1,4 @@ -# $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; @@ -8,6 +8,12 @@ use XML::Atom::Feed; 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) = @_; @@ -20,59 +26,152 @@ sub init_string { 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; diff --git a/lib/XML/Feed/Content.pm b/lib/XML/Feed/Content.pm index c1bf0c2..45d195c 100644 --- a/lib/XML/Feed/Content.pm +++ b/lib/XML/Feed/Content.pm @@ -1,4 +1,4 @@ -# $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; @@ -10,6 +10,7 @@ sub wrap { my($c) = @_; bless { %$c }, $class; } +*new = \&wrap; sub _var { my $content = shift; diff --git a/lib/XML/Feed/Entry.pm b/lib/XML/Feed/Entry.pm index 5221e6a..d64a30d 100644 --- a/lib/XML/Feed/Entry.pm +++ b/lib/XML/Feed/Entry.pm @@ -1,7 +1,10 @@ -# $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; @@ -9,6 +12,32 @@ sub wrap { 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; @@ -40,16 +69,26 @@ feed. =head1 USAGE -=head2 $entry->title +=head2 XML::Feed::Entry->new($format) + +Creates a new I object in the format I<$format>, which +should be either I or I. + +=head2 $entry->convert($format) + +Converts the I 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 object representing the full entry body, or as much as is available in the feed. @@ -59,7 +98,7 @@ I and I elements, then fall back to a IdescriptionE> element. -=head2 $entry->summary +=head2 $entry->summary([ $summary ]) An I object representing a short summary of the entry. Possibly. @@ -72,27 +111,31 @@ or I--we treat that as the summary. Otherwise, we assume that there isn't a summary, and return an I object with an empty string in the I. -=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 object representing the date and time at which the entry was posted. -=head2 $entry->modified +If present, I<$issued> should be a I object. + +=head2 $entry->modified([ $modified ]) A I object representing the last-modified date of the entry. +If present, I<$modified> should be a I object. + =head1 AUTHOR & COPYRIGHT Please see the I manpage for author, copyright, and license diff --git a/lib/XML/Feed/RSS.pm b/lib/XML/Feed/RSS.pm index 3f992ef..be50930 100644 --- a/lib/XML/Feed/RSS.pm +++ b/lib/XML/Feed/RSS.pm @@ -1,19 +1,27 @@ -# $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; } @@ -21,35 +29,64 @@ sub init_string { 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); + } } } @@ -57,73 +94,140 @@ sub entries { 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 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 contains both a and one of the elements - ## typically used for the full content, use 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 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 contains both a and one of the elements + ## typically used for the full content, use 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 , ## so we have to fall back to the 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); + } } } diff --git a/t/00-compile.t b/t/00-compile.t index b555c62..cbf2520 100644 --- a/t/00-compile.t +++ b/t/00-compile.t @@ -1,11 +1,9 @@ -# $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'); diff --git a/t/01-parse.t b/t/01-parse.t index bce2f91..f71f25f 100644 --- a/t/01-parse.t +++ b/t/01-parse.t @@ -1,12 +1,10 @@ -# $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', @@ -16,47 +14,52 @@ my %Feeds = ( ## 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 =~ /

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/

Hello!<\/p>/); + is($entry->summary->body, 'Hello!...'); + is($entry->category, 'Travel'); + is($entry->author, 'Melody'); ok($entry->id); } @@ -64,4 +67,4 @@ $feed = XML::Feed->parse('t/samples/rss20-no-summary.xml') or die XML::Feed->errstr; my $entry = ($feed->entries)[0]; ok(!$entry->summary->body); -ok($entry->content->body =~ m!

This is a test.

!); +like($entry->content->body, qr/

This is a test.<\/p>/); diff --git a/t/02-create.t b/t/02-create.t new file mode 100644 index 0000000..a55b41b --- /dev/null +++ b/t/02-create.t @@ -0,0 +1,74 @@ +# $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'); +}