File Coverage

blib/lib/XML/Atom/Feed.pm
Criterion Covered Total %
statement 39 88 44.3
branch 10 32 31.2
condition 6 22 27.2
subroutine 10 14 71.4
pod 1 8 12.5
total 66 164 40.2


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package XML::Atom::Feed;
4 19     19   767749 use strict;
  19         112  
  19         636  
5 19     19   99 use base qw( XML::Atom::Thing );
  19         52  
  19         5635  
6              
7 19     19   132 use XML::Atom;
  19         39  
  19         1005  
8 19     19   5911 use XML::Atom::Entry;
  19         82  
  19         1616  
9             BEGIN {
10 19     19   64 if (LIBXML) {
11 19         66 *entries = \&entries_libxml;
12 19         18457 *add_entry = \&add_entry_libxml;
13             } else {
14             *entries = \&entries_xpath;
15             *add_entry = \&add_entry_xpath;
16             }
17             }
18              
19             sub init {
20 20     20 0 48 my $atom = shift;
21 20 100       110 my %param = @_ == 1 ? (Stream => $_[0]) : @_;
22 20 50       148 if (UNIVERSAL::isa($param{Stream}, 'URI')) {
23 0         0 my @feeds = __PACKAGE__->find_feeds($param{Stream});
24 0 0       0 return $atom->error("Can't find Atom file") unless @feeds;
25 0         0 my $ua = LWP::UserAgent->new;
26 0         0 my $req = HTTP::Request->new(GET => $feeds[0]);
27 0         0 my $res = $ua->request($req);
28 0 0       0 if ($res->is_success) {
29 0         0 $param{Stream} = \$res->content;
30             }
31             }
32 20         150 $atom->SUPER::init(%param);
33             }
34              
35             sub find_feeds {
36 0     0 1 0 my $class = shift;
37 0         0 my($uri) = @_;
38 0         0 my $ua = LWP::UserAgent->new;
39 0         0 my $req = HTTP::Request->new(GET => $uri);
40 0         0 my $res = $ua->request($req);
41 0 0       0 return unless $res->is_success;
42 0         0 my @feeds;
43 0 0 0     0 if ($res->content_type eq 'text/html' || $res->content_type eq 'application/xhtml+xml') {
44 0         0 my $base_uri = $uri;
45             my $find_links = sub {
46 0     0   0 my($tag, $attr) = @_;
47 0 0       0 if ($tag eq 'link') {
    0          
48 0 0       0 return unless $attr->{rel};
49 0         0 my %rel = map { $_ => 1 } split /\s+/, lc($attr->{rel});
  0         0  
50 0         0 (my $type = lc $attr->{type}) =~ s/^\s*//;
51 0         0 $type =~ s/\s*$//;
52             push @feeds, URI->new_abs($attr->{href}, $base_uri)->as_string
53             if $rel{alternate} &&
54 0 0 0     0 $type eq 'application/atom+xml';
55             } elsif ($tag eq 'base') {
56 0         0 $base_uri = $attr->{href};
57             }
58 0         0 };
59 0         0 require HTML::Parser;
60 0         0 my $p = HTML::Parser->new(api_version => 3,
61             start_h => [ $find_links, "tagname, attr" ]);
62 0         0 $p->parse($res->content);
63             } else {
64 0         0 @feeds = ($uri);
65             }
66 0         0 @feeds;
67             }
68              
69 8     8 0 168 sub element_name { 'feed' }
70             *language = \⟨ # legacy
71              
72              
73             sub version {
74 19     19 0 94 my $feed = shift;
75 19         50 my $elem = $feed->elem;
76 19 100       56 if (@_) {
77 1         4 $elem->setAttribute('version', $_[0]);
78             }
79 19 100       75 $elem->getAttribute('version') || $feed->SUPER::version(@_);
80             }
81              
82             sub entries_libxml {
83 7     7 0 1407 my $feed = shift;
84 7 50       27 my @res = $feed->elem->getElementsByTagNameNS($feed->ns, 'entry') or return;
85 7         1392 my @entries;
86 7         24 for my $res (@res) {
87 61         1974 my $entry = XML::Atom::Entry->new(Elem => $res->cloneNode(1));
88 61         169 push @entries, $entry;
89             }
90 7         53 @entries;
91             }
92              
93             sub entries_xpath {
94 0     0 0 0 my $feed = shift;
95 0         0 my $set = $feed->elem->find("descendant-or-self::*[local-name()='entry' and namespace-uri()='" . $feed->ns . "']");
96 0         0 my @entries;
97 0         0 for my $elem ($set->get_nodelist) {
98             ## Delete the link to the parent (feed) element, and append
99             ## the default Atom namespace.
100 0         0 $elem->del_parent_link;
101 0         0 my $ns = XML::XPath::Node::Namespace->new('#default' => $feed->ns);
102 0         0 $elem->appendNamespace($ns);
103 0         0 my $entry = XML::Atom::Entry->new(Elem => $elem);
104 0         0 push @entries, $entry;
105             }
106 0         0 @entries;
107             }
108              
109             sub add_entry_libxml {
110 5     5 0 122 my $feed = shift;
111 5         12 my($entry, $opt) = @_;
112 5   100     84 $opt ||= {};
113             # When doing an insert, we try to insert before the first <entry> so
114             # that we don't screw up any preamble. If there are no existing
115             # <entry>'s, then fall back to appending, which should be
116             # semantically identical.
117 5         60 my ($first_entry) =
118             $feed->elem->getChildrenByTagNameNS($entry->ns, 'entry');
119 5 100 66     80 if ($opt->{mode} && $opt->{mode} eq 'insert' && $first_entry) {
      66        
120 1         118 $feed->elem->insertBefore($entry->elem, $first_entry);
121             } else {
122 4         115 $feed->elem->appendChild($entry->elem);
123             }
124             }
125              
126             sub add_entry_xpath {
127 0     0 0   my $feed = shift;
128 0           my($entry, $opt) = @_;
129 0   0       $opt ||= {};
130 0           my $set = $feed->elem->find("*[local-name()='entry' and namespace-uri()='" . $entry->ns . "']");
131 0 0         my $first_entry = $set ? ($set->get_nodelist)[0] : undef;
132 0 0 0       if ($opt->{mode} && $opt->{mode} eq 'insert' && $first_entry) {
      0        
133 0           $feed->elem->insertBefore($entry->elem, $first_entry);
134             } else {
135 0           $feed->elem->appendChild($entry->elem);
136             }
137             }
138              
139             __PACKAGE__->mk_elem_accessors(qw( generator ));
140             __PACKAGE__->mk_xml_attr_accessors(qw( lang base ));
141              
142             __PACKAGE__->_rename_elements('modified' => 'updated');
143             __PACKAGE__->_rename_elements('tagline' => 'subtitle');
144              
145             1;
146             __END__
147              
148             =head1 NAME
149              
150             XML::Atom::Feed - Atom feed
151              
152             =head1 SYNOPSIS
153              
154             use XML::Atom::Feed;
155             use XML::Atom::Entry;
156             my $feed = XML::Atom::Feed->new;
157             $feed->title('My Weblog');
158             $feed->id('tag:example.com,2006:feed-id');
159             my $entry = XML::Atom::Entry->new;
160             $entry->title('First Post');
161             $entry->id('tag:example.com,2006:entry-id');
162             $entry->content('Post Body');
163             $feed->add_entry($entry);
164             $feed->add_entry($entry, { mode => 'insert' });
165              
166             my @entries = $feed->entries;
167             my $xml = $feed->as_xml;
168              
169             ## Get a list of the <link rel="..." /> tags in the feed.
170             my $links = $feed->link;
171              
172             ## Find all of the Atom feeds on a given page, using auto-discovery.
173             my @uris = XML::Atom::Feed->find_feeds('http://www.example.com/');
174              
175             ## Use auto-discovery to load the first Atom feed on a given page.
176             my $feed = XML::Atom::Feed->new(URI->new('http://www.example.com/'));
177              
178             =head1 USAGE
179              
180             =head2 XML::Atom::Feed->new([ $stream ])
181              
182             Creates a new feed object, and if I<$stream> is supplied, fills it with the
183             data specified by I<$stream>.
184              
185             Automatically handles autodiscovery if I<$stream> is a URI (see below).
186              
187             Returns the new I<XML::Atom::Feed> object. On failure, returns C<undef>.
188              
189             I<$stream> can be any one of the following:
190              
191             =over 4
192              
193             =item * Reference to a scalar
194              
195             This is treated as the XML body of the feed.
196              
197             =item * Scalar
198              
199             This is treated as the name of a file containing the feed XML.
200              
201             =item * Filehandle
202              
203             This is treated as an open filehandle from which the feed XML can be read.
204              
205             =item * URI object
206              
207             This is treated as a URI, and the feed XML will be retrieved from the URI.
208              
209             If the content type returned from fetching the content at URI is
210             I<text/html>, this method will automatically try to perform auto-discovery
211             by looking for a I<E<lt>linkE<gt>> tag describing the feed URL. If such
212             a URL is found, the feed XML will be automatically retrieved.
213              
214             If the URI is already of a feed, no auto-discovery is necessary, and the
215             feed XML will be retrieved and parsed as normal.
216              
217             =back
218              
219             =head2 XML::Atom::Feed->find_feeds($uri)
220              
221             Given a URI I<$uri>, use auto-discovery to find all of the Atom feeds linked
222             from that page (using I<E<lt>linkE<gt>> tags).
223              
224             Returns a list of feed URIs.
225              
226             =head2 $feed->link
227              
228             If called in scalar context, returns an I<XML::Atom::Link> object
229             corresponding to the first I<E<lt>linkE<gt>> tag found in the feed.
230              
231             If called in list context, returns a list of I<XML::Atom::Link> objects
232             corresponding to all of the I<E<lt>linkE<gt>> tags found in the feed.
233              
234             =head2 $feed->add_link($link)
235              
236             Adds the link I<$link>, which must be an I<XML::Atom::Link> object, to
237             the feed as a new I<E<lt>linkE<gt>> tag. For example:
238              
239             my $link = XML::Atom::Link->new;
240             $link->type('text/html');
241             $link->rel('alternate');
242             $link->href('http://www.example.com/');
243             $feed->add_link($link);
244              
245             =head2 $feed->add_entry($entry)
246              
247             Adds the entry I<$entry>, which must be an I<XML::Atom::Entry> object,
248             to the feed. If you want to add an entry before existent entries, you can pass optional hash reference containing C<mode> value set to C<insert>.
249              
250             $feed->add_entry($entry, { mode => 'insert' });
251              
252             =head2 $feed->entries
253              
254             Returns list of XML::Atom::Entry objects contained in the feed.
255              
256             =head2 $feed->language
257              
258             Returns the language of the feed, from I<xml:lang>.
259              
260             =head2 $feed->author([ $author ])
261              
262             Returns an I<XML::Atom::Person> object representing the author of the entry,
263             or C<undef> if there is no author information present.
264              
265             If I<$author> is supplied, it should be an I<XML::Atom::Person> object
266             representing the author. For example:
267              
268             my $author = XML::Atom::Person->new;
269             $author->name('Foo Bar');
270             $author->email('foo@bar.com');
271             $feed->author($author);
272              
273             =head2 $feed->id([ $id ])
274              
275             Returns an id for the feed. If I<$id> is supplied, set the id. When
276             generating the new feed, it is your responsibility to generate unique
277             ID for the feed and set to XML::Atom::Feed object. You can use I<http>
278             permalink, I<tag> URI scheme or I<urn:uuid> for handy.
279              
280             =head1 UNICODE FLAGS
281              
282             By default, XML::Atom takes off all the Unicode flag from the feed content. For example,
283              
284             my $title = $feed->title;
285              
286             the variable C<$title> contains UTF-8 bytes without Unicode flag set,
287             even if the feed title contains some multibyte characters.
288              
289             If you don't like this behaviour and wants to handle everything as
290             Unicode characters (rather than UTF-8 bytes), set
291             C<$XML::Atom::ForceUnicode> flag to 1.
292              
293             $XML::Atom::ForceUnicode = 1;
294              
295             then all the data returned from XML::Atom::Feed object and
296             XML::Atom::Entry object etc., will have Unicode flag set.
297              
298             The only exception will be C<< $entry->content->body >>, if content
299             type is not text/* (e.g. image/gif). In that case, the content body is
300             still binary data, without Unicode flag set.
301              
302             =head1 CREATING ATOM 1.0 FEEDS
303              
304             By default, XML::Atom::Feed and other classes (Entry, Link and
305             Content) will create entities using Atom 0.3 namespaces. In order to
306             create 1.0 feed and entry elements, you can set I<Version> as a
307             parameter, like:
308              
309             $feed = XML::Atom::Feed->new(Version => 1.0);
310             $entry = XML::Atom::Entry->new(Version => 1.0);
311              
312             Setting those Version to every element would be sometimes painful. In
313             that case, you can override the default version number by setting
314             C<$XML::Atom::DefaultVersion> global variable to "1.0".
315              
316             use XML::Atom;
317              
318             $XML::Atom::DefaultVersion = "1.0";
319              
320             my $feed = XML::Atom::Feed->new;
321             $feed->title("blah");
322              
323             my $entry = XML::Atom::Entry->new;
324             $feed->add_entry($entry);
325              
326             $feed->version; # 1.0
327              
328             =head1 AUTHOR & COPYRIGHT
329              
330             Please see the I<XML::Atom> manpage for author, copyright, and license
331             information.
332              
333             =cut