File Coverage

blib/lib/XML/Feed.pm
Criterion Covered Total %
statement 53 116 45.6
branch 11 44 25.0
condition 2 5 40.0
subroutine 13 21 61.9
pod 8 9 88.8
total 87 195 44.6


line stmt bran cond sub pod time code
1             package XML::Feed;
2 28     28   722918 use strict;
  28         134  
  28         1682  
3 28     28   407 use warnings;
  28         55  
  28         1370  
4              
5 28     28   184 use base qw( Class::ErrorHandler );
  28         68  
  28         32546  
6 28     28   41906 use Feed::Find;
  28         2422904  
  28         1178  
7 28     28   39633 use URI::Fetch;
  28         3131049  
  28         1106  
8 28     28   277 use LWP::UserAgent;
  28         58  
  28         760  
9 28     28   178 use Carp;
  28         80  
  28         2613  
10 28         276 use Module::Pluggable search_path => "XML::Feed::Format",
11             require => 1,
12 28     28   36914 sub_name => 'formatters';
  28         364283  
13              
14             our $VERSION = '0.52';
15             our $MULTIPLE_ENCLOSURES = 0;
16             our @formatters;
17             BEGIN {
18 28     28   4210 @formatters = __PACKAGE__->formatters;
19             }
20              
21             sub new {
22 7     7 1 1381 my $class = shift;
23 7   100     38 my $format = shift || 'Atom';
24 7         27 my $format_class = 'XML::Feed::Format::' . $format;
25 7     7   1078 eval "use $format_class";
  7         196  
  0            
  0            
26 7 50       3134 Carp::croak("Unsupported format $format: $@") if $@;
27 0         0 my $feed = bless {}, join('::', __PACKAGE__, "Format", $format);
28 0 0       0 $feed->init_empty(@_) or return $class->error($feed->errstr);
29 0         0 $feed;
30             }
31              
32 0     0 0 0 sub init_empty { 1 }
33              
34             sub parse {
35 17     17 1 4126 my $class = shift;
36 17         62 my($stream, $specified_format) = @_;
37 17 50       88 return $class->error("Stream parameter is required") unless $stream;
38 17         1583 my $feed = bless {}, $class;
39 17         56 my $xml = '';
40 17 50       241 if (UNIVERSAL::isa($stream, 'URI')) {
    50          
    50          
41 0         0 my $ua = LWP::UserAgent->new;
42 0         0 $ua->agent(__PACKAGE__ . "/$VERSION");
43 0         0 $ua->env_proxy; # force allowing of proxies
44 0 0       0 my $res = URI::Fetch->fetch($stream, UserAgent => $ua)
45             or return $class->error(URI::Fetch->errstr);
46 0 0       0 return $class->error("This feed has been permanently removed")
47             if $res->status == URI::Fetch::URI_GONE();
48 0         0 $xml = $res->content;
49             } elsif (ref($stream) eq 'SCALAR') {
50 0         0 $xml = $$stream;
51             } elsif (ref($stream)) {
52 0         0 while (read($stream, my($chunk), 8192)) {
53 0         0 $xml .= $chunk;
54             }
55             } else {
56 17 50       1145 open my $fh, $stream
57             or return $class->error("Can't open $stream: $!");
58 17         822 while (read $fh, my($chunk), 8192) {
59 18         152 $xml .= $chunk;
60             }
61 17         235 close $fh;
62             }
63 17 50       74 return $class->error("Can't get feed XML content from $stream")
64             unless $xml;
65 17         32 my $format;
66 17 50       60 if ($specified_format) {
67 0         0 $format = $specified_format;
68             } else {
69 17 50       124 $format = $feed->identify_format(\$xml) or return $class->error($feed->errstr);
70             }
71              
72 0         0 my $format_class = join '::', __PACKAGE__, "Format", $format;
73 0         0 eval "use $format_class";
74 0 0       0 return $class->error("Unsupported format $format: $@") if $@;
75 0         0 bless $feed, $format_class;
76 0 0       0 $feed->init_string(\$xml) or return $class->error($feed->errstr);
77 0         0 $feed;
78             }
79              
80             sub identify_format {
81 17     17 1 45 my $feed = shift;
82 17         33 my($xml) = @_;
83 17         60 foreach my $class (@formatters) {
84 17         180 my ($name) = ($class =~ m!([^:]+)$!);
85             # TODO ugly
86 17         60 my $tmp = $$xml;
87 17 50       36 return $name if eval { $class->identify(\$tmp) };
  17         545  
88 17 50       230 return $feed->error($@) if $@;
89             }
90 0         0 return $feed->error("Cannot detect feed type");
91             }
92              
93             sub _get_first_tag {
94 0     0   0 my $class = shift;
95 0         0 my ($xml) = @_;
96              
97              
98             ## Auto-detect feed type based on first element. This is prone
99             ## to breakage, but then again we don't want to parse the whole
100             ## feed ourselves.
101 0         0 my $tag;
102 0         0 while ($$xml =~ /<(\S+)/sg) {
103 0         0 (my $t = $1) =~ tr/a-zA-Z0-9:\-\?!//cd;
104 0         0 my $first = substr $t, 0, 1;
105 0 0 0     0 $tag = $t, last unless $first eq '?' || $first eq '!';
106             }
107 0 0       0 die ("Cannot find first element") unless $tag;
108 0         0 $tag =~ s/^.*://;
109 0         0 return $tag;
110             }
111              
112             sub find_feeds {
113 0     0 1 0 my $class = shift;
114 0         0 my($uri) = @_;
115 0 0       0 my @feeds = Feed::Find->find($uri)
116             or return $class->error(Feed::Find->errstr);
117 0         0 @feeds;
118             }
119              
120             sub convert {
121 0     0 1 0 my $feed = shift;
122 0         0 my($format) = @_;
123 0         0 my $new = XML::Feed->new($format);
124 0         0 for my $field (qw( title link description language author copyright modified generator )) {
125 0         0 my $val = $feed->$field();
126 0 0       0 next unless defined $val;
127 0         0 $new->$field($val);
128             }
129 0         0 for my $entry ($feed->entries) {
130 0         0 $new->add_entry($entry->convert($format));
131             }
132 0         0 $new;
133             }
134              
135             sub splice {
136 0     0 1 0 my $feed = shift;
137 0         0 my($other) = @_;
138 0         0 my %ids = map { $_->id => 1 } $feed->entries;
  0         0  
139 0         0 for my $entry ($other->entries) {
140 0 0       0 $feed->add_entry($entry) unless $ids{$entry->id}++;
141             }
142             }
143              
144             sub _convert_entry {
145 0     0   0 my $feed = shift;
146 0         0 my $entry = shift;
147 0         0 my $feed_format = ref($feed); $feed_format =~ s!^XML::Feed::Format::!!;
  0         0  
148 0         0 my $entry_format = ref($entry); $entry_format =~ s!^XML::Feed::Entry::Format::!!;
  0         0  
149 0 0       0 return $entry if $entry_format eq $feed_format;
150 0         0 return $entry->convert($feed_format);
151             }
152              
153             sub base;
154             sub format;
155             sub title;
156             sub link;
157             sub self_link;
158             sub description;
159             sub language;
160             sub author;
161             sub copyright;
162             sub modified;
163             sub generator;
164             sub add_entry;
165             sub entries;
166             sub as_xml;
167             sub id;
168             sub image;
169              
170 0     0 1 0 sub tagline { shift->description(@_) }
171 0     0 1 0 sub items { $_[0]->entries }
172              
173             # RFC 5005
174             sub first_link;
175             sub last_link;
176             sub previous_link;
177             sub next_link;
178             sub current_link;
179             sub prev_archive_link;
180             sub next_archive_link;
181              
182             1;
183             __END__