Make feed validate
[catagits/XML-Feed.git] / lib / XML / Feed / RSS.pm
CommitLineData
fe3b3201 1# $Id: RSS.pm 1934 2006-04-22 05:13:55Z btrott $
0d5e38d1 2
3package XML::Feed::RSS;
4use strict;
5
6use base qw( XML::Feed );
0d5e38d1 7use DateTime::Format::Mail;
8use DateTime::Format::W3CDTF;
9
973e1f9e 10our $PREFERRED_PARSER = "XML::RSS";
11
12sub init_empty {
4e9c4625 13 my ($feed, %args) = @_;
14 $args{'version'} ||= '2.0';
973e1f9e 15 eval "use $PREFERRED_PARSER"; die $@ if $@;
4e9c4625 16 $feed->{rss} = $PREFERRED_PARSER->new(%args);
fe3b3201 17 $feed->{rss}->add_module(prefix => "content", uri => 'http://purl.org/rss/1.0/modules/content/');
813f78d8 18 $feed->{rss}->add_module(prefix => "dcterms", uri => 'http://purl.org/dc/terms/');
973e1f9e 19 $feed;
20}
21
0d5e38d1 22sub init_string {
23 my $feed = shift;
24 my($str) = @_;
973e1f9e 25 $feed->init_empty;
0d5e38d1 26 if ($str) {
973e1f9e 27 $feed->{rss}->parse($$str);
0d5e38d1 28 }
29 $feed;
30}
31
32sub format { 'RSS ' . $_[0]->{rss}->{'version'} }
33
34## The following elements are the same in all versions of RSS.
973e1f9e 35sub title { shift->{rss}->channel('title', @_) }
36sub link { shift->{rss}->channel('link', @_) }
37sub description { shift->{rss}->channel('description', @_) }
0d5e38d1 38
813f78d8 39# This doesn't exist in RSS
40sub id { }
41
0d5e38d1 42## This is RSS 2.0 only--what's the equivalent in RSS 1.0?
973e1f9e 43sub copyright { shift->{rss}->channel('copyright', @_) }
0d5e38d1 44
45## The following all work transparently in any RSS version.
46sub language {
973e1f9e 47 my $feed = shift;
48 if (@_) {
49 $feed->{rss}->channel('language', $_[0]);
50 $feed->{rss}->channel->{dc}{language} = $_[0];
51 } else {
52 $feed->{rss}->channel('language') ||
53 $feed->{rss}->channel->{dc}{language};
54 }
0d5e38d1 55}
56
57sub generator {
973e1f9e 58 my $feed = shift;
59 if (@_) {
60 $feed->{rss}->channel('generator', $_[0]);
61 $feed->{rss}->channel->{'http://webns.net/mvcb/'}{generatorAgent} =
62 $_[0];
63 } else {
64 $feed->{rss}->channel('generator') ||
65 $feed->{rss}->channel->{'http://webns.net/mvcb/'}{generatorAgent};
66 }
0d5e38d1 67}
68
69sub author {
973e1f9e 70 my $feed = shift;
71 if (@_) {
72 $feed->{rss}->channel('webMaster', $_[0]);
73 $feed->{rss}->channel->{dc}{creator} = $_[0];
74 } else {
75 $feed->{rss}->channel('webMaster') ||
76 $feed->{rss}->channel->{dc}{creator};
77 }
0d5e38d1 78}
79
80sub modified {
973e1f9e 81 my $rss = shift->{rss};
82 if (@_) {
83 $rss->channel('pubDate',
84 DateTime::Format::Mail->format_datetime($_[0]));
85 ## XML::RSS is so weird... if I set this, it will try to use
86 ## the value for the lastBuildDate, which I don't want--because
87 ## this date is formatted for an RSS 1.0 feed. So it's commented out.
88 #$rss->channel->{dc}{date} =
89 # DateTime::Format::W3CDTF->format_datetime($_[0]);
fe3b3201 90 } else {
91 my $date;
92 eval {
93 if (my $ts = $rss->channel('pubDate')) {
94 $date = DateTime::Format::Mail->parse_datetime($ts);
95 } elsif ($ts = $rss->channel->{dc}{date}) {
96 $date = DateTime::Format::W3CDTF->parse_datetime($ts);
97 }
98 };
99 return $date;
0d5e38d1 100 }
101}
102
103sub entries {
104 my $rss = $_[0]->{rss};
105 my @entries;
106 for my $item (@{ $rss->{items} }) {
973e1f9e 107 push @entries, XML::Feed::Entry::RSS->wrap($item);
0d5e38d1 108 }
109 @entries;
110}
111
973e1f9e 112sub add_entry {
113 my $feed = shift;
114 my($entry) = @_;
115 $feed->{rss}->add_item(%{ $entry->unwrap });
116}
117
118sub as_xml { $_[0]->{rss}->as_string }
119
120package XML::Feed::Entry::RSS;
0d5e38d1 121use strict;
122
a749d9b9 123use XML::Feed::Content;
124
0d5e38d1 125use base qw( XML::Feed::Entry );
126
973e1f9e 127sub init_empty { $_[0]->{entry} = { } }
128
129sub title {
130 my $entry = shift;
131 @_ ? $entry->{entry}{title} = $_[0] : $entry->{entry}{title};
132}
133
134sub link {
135 my $entry = shift;
136 if (@_) {
137 $entry->{entry}{link} = $_[0];
138 ## For RSS 2.0 output from XML::RSS. Sigh.
139 $entry->{entry}{permaLink} = $_[0];
140 } else {
141 $entry->{entry}{link} || $entry->{entry}{guid};
142 }
143}
a749d9b9 144
145sub summary {
973e1f9e 146 my $item = shift->{entry};
147 if (@_) {
148 $item->{description} = ref($_[0]) eq 'XML::Feed::Content' ?
149 $_[0]->body : $_[0];
150 ## Because of the logic below, we need to add some dummy content,
151 ## so that we'll properly recognize the description we enter as
152 ## the summary.
fe3b3201 153 if (!$item->{content}{encoded} &&
973e1f9e 154 !$item->{'http://www.w3.org/1999/xhtml'}{body}) {
fe3b3201 155 $item->{content}{encoded} = ' ';
973e1f9e 156 }
157 } else {
158 ## Some RSS feeds use <description> for a summary, and some use it
159 ## for the full content. Pretty gross. We don't want to return the
160 ## full content if the caller expects a summary, so the heuristic is:
161 ## if the <entry> contains both a <description> and one of the elements
162 ## typically used for the full content, use <description> as summary.
163 my $txt;
164 if ($item->{description} &&
fe3b3201 165 ($item->{content}{encoded} ||
973e1f9e 166 $item->{'http://www.w3.org/1999/xhtml'}{body})) {
167 $txt = $item->{description};
168 }
169 XML::Feed::Content->wrap({ type => 'text/plain', body => $txt });
a749d9b9 170 }
a749d9b9 171}
0d5e38d1 172
173sub content {
973e1f9e 174 my $item = shift->{entry};
175 if (@_) {
176 my $c = ref($_[0]) eq 'XML::Feed::Content' ? $_[0]->body : $_[0];
fe3b3201 177 $item->{content}{encoded} = $c;
973e1f9e 178 } else {
179 my $body =
fe3b3201 180 $item->{content}{encoded} ||
973e1f9e 181 $item->{'http://www.w3.org/1999/xhtml'}{body} ||
182 $item->{description};
183 XML::Feed::Content->wrap({ type => 'text/html', body => $body });
184 }
0d5e38d1 185}
186
187sub category {
973e1f9e 188 my $item = shift->{entry};
189 if (@_) {
190 $item->{category} = $item->{dc}{subject} = $_[0];
191 } else {
192 $item->{category} || $item->{dc}{subject};
193 }
0d5e38d1 194}
195
196sub author {
973e1f9e 197 my $item = shift->{entry};
198 if (@_) {
199 $item->{author} = $item->{dc}{creator} = $_[0];
200 } else {
201 $item->{author} || $item->{dc}{creator};
202 }
0d5e38d1 203}
204
205## XML::RSS doesn't give us access to the rdf:about for the <item>,
206## so we have to fall back to the <link> element in RSS 1.0 feeds.
207sub id {
973e1f9e 208 my $item = shift->{entry};
209 if (@_) {
210 $item->{guid} = $_[0];
211 } else {
212 $item->{guid} || $item->{link};
213 }
0d5e38d1 214}
215
216sub issued {
973e1f9e 217 my $item = shift->{entry};
218 if (@_) {
219 $item->{dc}{date} = DateTime::Format::W3CDTF->format_datetime($_[0]);
220 $item->{pubDate} = DateTime::Format::Mail->format_datetime($_[0]);
221 } else {
ecac864a 222 ## Either of these could die if the format is invalid.
223 my $date;
224 eval {
225 if (my $ts = $item->{pubDate}) {
226 my $parser = DateTime::Format::Mail->new;
227 $parser->loose;
228 $date = $parser->parse_datetime($ts);
229 } elsif ($ts = $item->{dc}{date}) {
230 $date = DateTime::Format::W3CDTF->parse_datetime($ts);
231 }
232 };
233 return $date;
0d5e38d1 234 }
235}
236
237sub modified {
973e1f9e 238 my $item = shift->{entry};
239 if (@_) {
fe3b3201 240 $item->{dcterms}{modified} =
973e1f9e 241 DateTime::Format::W3CDTF->format_datetime($_[0]);
242 } else {
fe3b3201 243 if (my $ts = $item->{dcterms}{modified}) {
ecac864a 244 return eval { DateTime::Format::W3CDTF->parse_datetime($ts) };
973e1f9e 245 }
0d5e38d1 246 }
247}
248
2491;