File Coverage

blib/lib/Catalyst/View/XML/Feed.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Catalyst::View::XML::Feed;
2 2     2   8806118 use Moose;
  0            
  0            
3             extends 'Catalyst::View';
4             use XML::Feed;
5             use Scalar::Util ();
6             use namespace::autoclean;
7              
8             our $VERSION = '0.07';
9              
10             has default_format => (
11             is => 'ro',
12             isa => 'Str',
13             required => 1,
14             default => 'RSS 1.0',
15             documentation => 'The default format for a feed, when the format cannot otherwise be determined. Acceptable values are: "Atom", "RSS 0.91", "RSS 1.0", "RSS 2.0".',
16             );
17             has xml_feed_attributes => (
18             is => 'ro',
19             isa => 'ArrayRef',
20             required => 1,
21             default => sub {
22             [ qw(id title link description modified base tagline
23             author language copyright generator self_link)
24             ]
25             },
26             );
27             has xml_feed_entry_attributes => (
28             is => 'ro',
29             isa => 'ArrayRef',
30             required => 1,
31             default => sub {
32             [ qw(id title content link modified issued base summary category tags author) ]
33             },
34             );
35              
36             sub render {
37             my ($self, $c, $feed) = @_;
38              
39             $feed = $self->_make_feed_recognizable($feed);
40              
41             return $self->_render($feed);
42             }
43              
44             sub process {
45             my ($self, $c) = @_;
46             my $feed = $c->stash->{feed};
47              
48             if (defined $feed) {
49             $feed = $self->_make_feed_recognizable($feed);
50             $c->res->content_type( $self->_content_type_for_feed($feed) );
51             $c->res->body( $self->_render($feed) );
52             return 1;
53              
54             } else {
55             $c->log->error("No 'feed' value was found in the stash.");
56             return 0;
57             }
58             }
59              
60             # You need to run the feed through _make_feed_recognizable() first.
61             sub _render {
62             my ($self, $feed) = @_;
63             return undef unless $feed;
64              
65             # Plain string.
66             if (! ref $feed) {
67             return $feed;
68              
69             # Common RSS/Atom objects...
70             } elsif ($feed->can('as_xml')) {
71             return $feed->as_xml();
72             } elsif ($feed->can('as_string')) {
73             return $feed->as_string();
74             }
75              
76             return undef;
77             }
78              
79             # Returns one of the common RSS or Atom modules, or a string.
80             sub _make_feed_recognizable {
81             my ($self, $feed) = @_;
82              
83             # XML in a string? pass through
84             if (! ref $feed) {
85             return $feed;
86              
87             # Common Atom/RSS module? pass through
88             } elsif (Scalar::Util::blessed($feed)) {
89             for my $module (('XML::Feed', 'XML::RSS', 'XML::Atom::SimpleFeed',
90             'XML::Atom::Feed', 'XML::Atom::Syndication::Feed'))
91             {
92             if ($feed->isa($module)) {
93             return $feed;
94             }
95             }
96             }
97              
98             return $feed unless Scalar::Util::blessed($feed) || ref $feed eq 'HASH';
99              
100             # Otherwise, let's convert it to an XML::Feed.
101              
102             my $format = Scalar::Util::blessed($feed) && $feed->can('format')
103             ? $feed->format
104             : (defined $feed->{format} ? $feed->{format} : $self->default_format);
105             my @format;
106             if (ref $format) {
107             # Docs for this say format should be a string ('RSS 2.0'), but
108             # format can also be XML::Feed style ('RSS', version => '2.0')
109             # since all other attributes are like XML::Feed
110             @format = ref $format eq 'ARRAY'
111             ? @$format
112             : %$format;
113             } else {
114             @format = split /\s+/, $format;
115             }
116             if (scalar(@format) > 1) {
117             splice @format, 1, 0, 'version';
118             }
119              
120             my $xf_feed = XML::Feed->new(@format);
121              
122             my @entries;
123             # Set feed attributes, get entries.
124             if (Scalar::Util::blessed($feed)) {
125             for my $key (@{ $self->xml_feed_attributes }) {
126             if ($feed->can($key)) {
127             $xf_feed->$key( $feed->$key() );
128             }
129             }
130             if ($feed->can('entries')) {
131             # Allow for feed->entries to return either array or arrayref.
132             @entries = ($feed->entries);
133             if (scalar(@entries) == 1 && ref($entries[0]) eq 'ARRAY' && ! Scalar::Util::blessed($entries[0])) {
134             @entries = @{ $entries[0] };
135             }
136             }
137             } else {
138             for my $key (@{ $self->xml_feed_attributes }) {
139             if (exists $feed->{$key}) {
140             $xf_feed->$key( $feed->{$key} );
141             }
142             }
143             }
144             unless (@entries) {
145             @entries = exists $feed->{entries} ? @{ $feed->{entries} } : ();
146             }
147              
148             # Create the entries.
149             for my $entry (@entries) {
150             my $xf_entry = XML::Feed::Entry->new($format[0]);
151              
152             if (Scalar::Util::blessed($entry)) {
153             for my $key (@{ $self->xml_feed_entry_attributes }) {
154             if ($entry->can($key)) {
155             $xf_entry->$key( $entry->$key() );
156             }
157             }
158             } else {
159             for my $key (@{ $self->xml_feed_entry_attributes }) {
160             if (exists $entry->{$key}) {
161             $xf_entry->$key( $entry->{$key} );
162             }
163             }
164             }
165             $xf_feed->add_entry($xf_entry);
166             }
167              
168             return $xf_feed;
169             }
170              
171             # You need to run the feed through _make_feed_recognizable() first.
172             sub _content_type_for_feed {
173             my ($self, $feed) = @_;
174              
175             # Plain string.
176             if (! ref $feed) {
177             return 'text/xml';
178              
179             # Objects...
180             } elsif ($feed->isa('XML::Feed')) {
181             if ($feed->format && lc($feed->format) =~ /atom/i) {
182             return 'application/atom+xml';
183             } elsif ($feed->format && lc($feed->format) =~ /rss/i) {
184             return 'application/rss+xml';
185             } else {
186             return 'text/xml';
187             }
188              
189             } elsif ($feed->isa('XML::RSS')) {
190             return 'application/rss+xml';
191              
192             } elsif ($feed->isa('XML::Atom::SimpleFeed')) {
193             return 'application/atom+xml';
194              
195             } elsif ($feed->isa('XML::Atom::Feed')) {
196             return 'application/atom+xml';
197              
198             } elsif ($feed->isa('XML::Atom::Syndication::Feed')) {
199             return 'application/atom+xml';
200              
201             } else {
202             return 'text/xml';
203             }
204             }
205              
206             =head1 NAME
207              
208             Catalyst::View::XML::Feed - Catalyst view for RSS, Atom, or other XML feeds
209              
210             =head1 SYNOPSIS
211              
212             Create your view, e.g. lib/MyApp/View/Feed.pm
213              
214             package MyApp::View::Feed;
215             use base qw( Catalyst::View::XML::Feed );
216             1;
217              
218             In a controller, set the C<feed> stash variable and forward to your view:
219              
220             sub rss : Local {
221             my ($self, $c) = @_:
222             $c->stash->{feed} = $feed_obj_or_data;
223             $c->forward('View::Feed');
224             }
225              
226              
227             =head1 DESCRIPTION
228              
229             Catalyst::View::XML::Feed is a hassle-free way to serve an RSS, Atom, or other XML feed from your L<Catalyst> application.
230              
231             Your controller should put feed data into C<< $c->stash->{feed} >>.
232              
233             =head1 DATA FORMATS
234              
235             The value in C<< $c->stash->{feed} >> can be an object from any of the
236             popular L<RSS or Atom classes|/"XML::Feed">,
237             a L<plain Perl data structure|/"Plain Perl data">,
238             L<arbitrary custom objects|/"Arbitrary custom objects">, or an
239             L<xml string|/"Plain text">.
240              
241             =head2 Plain Perl data
242              
243             $c->stash->{feed} = {
244             format => 'RSS 1.0',
245             id => $c->req->base,
246             title => 'My Great Site',
247             description => 'Kitten pictures for the masses',
248             link => $c->req->base,
249             modified => DateTime->now,
250              
251             entries => [
252             {
253             id => $c->uri_for('rss', 'kitten_post')->as_string,
254             link => $c->uri_for('rss', 'kitten_post')->as_string,
255             title => 'First post!',
256             modified => DateTime->now,
257             content => 'This is my first post!',
258             },
259             # ... more entries.
260             ],
261             };
262              
263             =over 4
264              
265             =item Keys for feed
266              
267             The C<feed> hash can take any of the following keys. They are identical
268             to those supported by L<XML::Feed>. See L<XML::Feed> for more details.
269              
270             I<Note>: Depending on the feed format you choose, different subsets of
271             attributes might be required. As such, it is recommended that you run the
272             generated XML through a validator such as L<http://validator.w3.org/feed/>
273             to ensure you included all necessary information.
274              
275             =over 4
276              
277             =item format
278              
279             Can be any of: "Atom", "RSS 0.91", "RSS 1.0", "RSS 2.0"
280              
281             =item id
282              
283             =item title
284              
285             =item link
286              
287             =item description
288              
289             =item modified
290              
291             This should be a L<DateTime> object.
292              
293             =item base
294              
295             =item tagline
296              
297             =item author
298              
299             =item language
300              
301             =item copyright
302              
303             =item generator
304              
305             =item self_link
306              
307             =item entries
308              
309             An array ref of L<entries|/"Keys for entries">.
310              
311             =back
312              
313             =item Keys for entries
314              
315             The C<entries> array contains any number of hashrefs, each representing
316             an entry in the feed. Each can contain any of the following keys.
317             They are identical to those of L<XML::Feed::Entry>. See L<XML::Feed::Entry>
318             for details.
319              
320             I<Note>: Depending on the feed format you choose, different subsets of
321             attributes might be required. As such, it is recommended that you run the
322             generated XML through a validator such as L<http://validator.w3.org/feed/>
323             to ensure you included all necessary information.
324              
325             =over 4
326              
327             =item id
328              
329             =item title
330              
331             =item content
332              
333             =item link
334              
335             =item modified
336              
337             This should be a L<DateTime> object.
338              
339             =item issued
340              
341             This should be a L<DateTime> object.
342              
343             =item base
344              
345             =item summary
346              
347             =item category
348              
349             =item tags
350              
351             =item author
352              
353             =back
354              
355             =back
356              
357             =head2 Arbitrary custom objects
358              
359             If you have custom objects that you would like to turn into feed entries,
360             this can be done similar to L<plain Perl data structures|/"Plain Perl data">.
361              
362             For example, if we have a C<DB::BlogPost> L<DBIx::Class> model, we can do the
363             following:
364              
365             $c->stash->{feed} = {
366             format => 'Atom',
367             id => $c->req->base,
368             title => 'My Great Site',
369             description => 'Kitten pictures for the masses',
370             link => $c->req->base,
371             modified => DateTime->now,
372              
373             entries => [ $c->model('DB::BlogPost')->all() ],
374             };
375              
376             The view will go through the L<keys for entries|/"Keys for entries"> fields
377             and, if possible, call a method of the same name on your entry object
378             (e.g. C<< $your_entry->title(); $your_entry->modified(); >>) to get that
379             value for the XML.
380              
381             Any missing fields are simply skipped.
382              
383             If your class's method names do not match up to the C<entries> keys,
384             you can simply alias them by wrapping with another method. For example, if your
385             C<DB::BlogPost> has a C<post_title> field which should be the title
386             for the feed entry, you can add this to BlogPost.pm:
387              
388             sub title { $_[0]->post_title }
389              
390             =head2 XML::Feed
391              
392             An L<XML::Feed> object.
393              
394             $c->stash->{feed} = $xml_feed_obj;
395              
396             =head2 XML::RSS
397              
398             An L<XML::RSS> object.
399              
400             $c->stash->{feed} = $xml_rss_obj;
401              
402             =head2 XML::Atom::SimpleFeed
403              
404             An L<XML::Atom::SimpleFeed> object.
405              
406             $c->stash->{feed} = $xml_atom_simplefeed_obj;
407              
408             =head2 XML::Atom::Feed
409              
410             An L<XML::Atom::Feed> object.
411              
412             $c->stash->{feed} = $xml_atom_feed_obj;
413              
414             =head2 XML::Atom::Syndication::Feed
415              
416             An L<XML::Atom::Syndication::Feed> object.
417              
418             $c->stash->{feed} = $xml_atom_syndication_feed_obj;
419              
420             =head2 Plain text
421              
422             If none of the formats mentioned above are suitable, you may also
423             provide a string containing the XML data.
424              
425             $c->stash->{feed} = $xml_string;
426              
427             =head1 SOURCE REPOSITORY
428              
429             L<http://github.com/mstratman/Catalyst-View-XML-Feed>
430              
431             =head1 AUTHOR
432              
433             Mark A. Stratman E<lt>stratman@gmail.comE<gt>
434              
435             =head1 CONTRIBUTORS
436              
437             Thomas Doran (t0m)
438              
439             =head1 COPYRIGHT & LICENSE
440              
441             Copyright 2011 the above author(s).
442              
443             This sofware is free software, and is licensed under the same terms as perl itself.
444              
445             =cut
446              
447             __PACKAGE__->meta->make_immutable;
448             1;