File Coverage

blib/lib/XML/Atom/Feed.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             # $Id$
2              
3             package XML::Atom::Feed;
4 8     8   260898 use strict;
  8         23  
  8         351  
5 8     8   49 use base qw( XML::Atom::Thing );
  8         15  
  8         12597  
6              
7             use XML::Atom;
8             use XML::Atom::Entry;
9             BEGIN {
10             if (LIBXML) {
11             *entries = \&entries_libxml;
12             *add_entry = \&add_entry_libxml;
13             } else {
14             *entries = \&entries_xpath;
15             *add_entry = \&add_entry_xpath;
16             }
17             }
18              
19             sub init {
20             my $atom = shift;
21             my %param = @_ == 1 ? (Stream => $_[0]) : @_;
22             if (UNIVERSAL::isa($param{Stream}, 'URI')) {
23             my @feeds = __PACKAGE__->find_feeds($param{Stream});
24             return $atom->error("Can't find Atom file") unless @feeds;
25             my $ua = LWP::UserAgent->new;
26             my $req = HTTP::Request->new(GET => $feeds[0]);
27             my $res = $ua->request($req);
28             if ($res->is_success) {
29             $param{Stream} = \$res->content;
30             }
31             }
32             $atom->SUPER::init(%param);
33             }
34              
35             sub find_feeds {
36             my $class = shift;
37             my($uri) = @_;
38             my $ua = LWP::UserAgent->new;
39             my $req = HTTP::Request->new(GET => $uri);
40             my $res = $ua->request($req);
41             return unless $res->is_success;
42             my @feeds;
43             if ($res->content_type eq 'text/html' || $res->content_type eq 'application/xhtml+xml') {
44             my $base_uri = $uri;
45             my $find_links = sub {
46             my($tag, $attr) = @_;
47             if ($tag eq 'link') {
48             return unless $attr->{rel};
49             my %rel = map { $_ => 1 } split /\s+/, lc($attr->{rel});
50             (my $type = lc $attr->{type}) =~ s/^\s*//;
51             $type =~ s/\s*$//;
52             push @feeds, URI->new_abs($attr->{href}, $base_uri)->as_string
53             if $rel{alternate} &&
54             $type eq 'application/atom+xml';
55             } elsif ($tag eq 'base') {
56             $base_uri = $attr->{href};
57             }
58             };
59             require HTML::Parser;
60             my $p = HTML::Parser->new(api_version => 3,
61             start_h => [ $find_links, "tagname, attr" ]);
62             $p->parse($res->content);
63             } else {
64             @feeds = ($uri);
65             }
66             @feeds;
67             }
68              
69             sub element_name { 'feed' }
70             *language = \⟨ # legacy
71              
72              
73             sub version {
74             my $feed = shift;
75             my $elem = $feed->elem;
76             if (@_) {
77             $elem->setAttribute('version', $_[0]);
78             }
79             $elem->getAttribute('version') || $feed->SUPER::version(@_);
80             }
81              
82             sub entries_libxml {
83             my $feed = shift;
84             my @res = $feed->elem->getElementsByTagNameNS($feed->ns, 'entry') or return;
85             my @entries;
86             for my $res (@res) {
87             my $entry = XML::Atom::Entry->new(Elem => $res->cloneNode(1));
88             push @entries, $entry;
89             }
90             @entries;
91             }
92              
93             sub entries_xpath {
94             my $feed = shift;
95             my $set = $feed->elem->find("descendant-or-self::*[local-name()='entry' and namespace-uri()='" . $feed->ns . "']");
96             my @entries;
97             for my $elem ($set->get_nodelist) {
98             ## Delete the link to the parent (feed) element, and append
99             ## the default Atom namespace.
100             $elem->del_parent_link;
101             my $ns = XML::XPath::Node::Namespace->new('#default' => $feed->ns);
102             $elem->appendNamespace($ns);
103             my $entry = XML::Atom::Entry->new(Elem => $elem);
104             push @entries, $entry;
105             }
106             @entries;
107             }
108              
109             sub add_entry_libxml {
110             my $feed = shift;
111             my($entry, $opt) = @_;
112             $opt ||= {};
113             # When doing an insert, we try to insert before the first so
114             # that we don't screw up any preamble. If there are no existing
115             # 's, then fall back to appending, which should be
116             # semantically identical.
117             my ($first_entry) =
118             $feed->elem->getChildrenByTagNameNS($entry->ns, 'entry');
119             if ($opt->{mode} && $opt->{mode} eq 'insert' && $first_entry) {
120             $feed->elem->insertBefore($entry->elem, $first_entry);
121             } else {
122             $feed->elem->appendChild($entry->elem);
123             }
124             }
125              
126             sub add_entry_xpath {
127             my $feed = shift;
128             my($entry, $opt) = @_;
129             $opt ||= {};
130             my $set = $feed->elem->find("*[local-name()='entry' and namespace-uri()='" . $entry->ns . "']");
131             my $first_entry = $set ? ($set->get_nodelist)[0] : undef;
132             if ($opt->{mode} && $opt->{mode} eq 'insert' && $first_entry) {
133             $feed->elem->insertBefore($entry->elem, $first_entry);
134             } else {
135             $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__