File Coverage

blib/lib/WWW/Noss/FeedReader/Atom.pm
Criterion Covered Total %
statement 125 142 88.0
branch 76 102 74.5
condition 16 35 45.7
subroutine 14 14 100.0
pod 0 1 0.0
total 231 294 78.5


line stmt bran cond sub pod time code
1             package WWW::Noss::FeedReader::Atom;
2 5     5   100 use 5.016;
  5         20  
3 5     5   25 use strict;
  5         8  
  5         108  
4 5     5   30 use warnings;
  5         20  
  5         434  
5             our $VERSION = '2.02';
6              
7 5     5   4319 use WWW::Noss::FeedReader::MediaRSS qw(parse_media_node);
  5         13  
  5         399  
8 5     5   38 use WWW::Noss::TextToHtml qw(text2html unescape_html strip_tags);
  5         8  
  5         336  
9 5     5   3657 use WWW::Noss::Timestamp;
  5         13  
  5         9422  
10              
11             our $NS = 'http://www.w3.org/2005/Atom';
12              
13             sub _name {
14              
15 76     76   110 my ($node) = @_;
16              
17 76         99 my ($email_node, $name_node);
18              
19 76         187 for my $n ($node->childNodes) {
20 532         1656 my $node_name = $n->nodeName;
21 532 100       968 if ($node_name eq 'name') {
    100          
22 76         103 $name_node = $n;
23             } elsif ($node_name eq 'email') {
24 76         134 $email_node = $n;
25             }
26             }
27              
28 76 50       304 my $name = defined $name_node ? $name_node->textContent : '';
29 76 50       1224 my $email = defined $email_node ? $email_node->textContent : '';
30              
31 76 50 33     235 if ($name ne '' and $email ne '') {
    0          
    0          
32 76         210 return sprintf "%s (%s)", $email, $name;
33             } elsif ($email ne '') {
34 0         0 return $email;
35             } elsif ($name ne '') {
36 0         0 return $name;
37             } else {
38 0         0 return undef;
39             }
40              
41             }
42              
43             sub _link {
44              
45 74     74   105 my ($node) = @_;
46              
47 74         134 my $href = $node->getAttribute('href');
48              
49 74 50       509 return undef unless defined $href;
50              
51 74   100     111 my $rel = $node->getAttribute('rel') // 'alternate';
52              
53 74 50       605 return $rel eq 'alternate' ? $href : undef;
54              
55             }
56              
57             sub _time {
58              
59 139     139   186 my ($node) = @_;
60              
61 139         477 return WWW::Noss::Timestamp->rfc3339($node->textContent);
62              
63             }
64              
65             sub _title {
66              
67 74     74   105 my ($node) = @_;
68              
69 74         97 my ($title, $display);
70 74         180 $title = $node->textContent;
71 74   50     169 my $type = $node->getAttribute('type') // 'text';
72 74 50       725 if ($type eq 'html') {
73 0         0 $display = unescape_html(strip_tags($title));
74             } else {
75 74         90 $display = $title;
76             }
77              
78 74         513 $display =~ s/\s+/ /g;
79 74         349 $display =~ s/^\s+|\s+$//g;
80              
81 74 100       242 return wantarray ? ($title, $display) : $display;
82              
83             }
84              
85             sub _content {
86              
87 49     49   71 my ($node) = @_;
88              
89 49   100     100 my $type = $node->getAttribute('type') // 'text';
90 49         409 my $src = $node->getAttribute('src');
91              
92 49 100       424 if ($type eq 'text') {
    100          
    100          
    50          
    50          
    0          
93 13         64 return text2html($node->textContent);
94             } elsif ($type eq 'html') {
95 12         34 return $node->textContent;
96             } elsif ($type eq 'xhtml') {
97 12         27 return join '', map { $_->toString } $node->childNodes;
  12         296  
98             } elsif (defined $src) {
99 0         0 return undef;
100             } elsif ($type =~ /[\+\/]xml$/) {
101 12         28 return join '', map { $_->toString } $node->childNodes;
  12         196  
102             } elsif ($type =~ /^text/) {
103 0         0 return text2html($node->textContent);
104             } else {
105 0         0 return undef;
106             }
107              
108             }
109              
110             sub _summary {
111              
112 12     12   21 my ($node) = @_;
113              
114 12   50     40 my $type = $node->getAttribute('type') // 'text';
115              
116 12 50       157 if ($type eq 'html') {
    50          
    50          
117 0         0 return $node->textContent;
118             } elsif ($type eq 'xhtml') {
119 0         0 return join '', map { $_->toString } $node->childNodes;
  0         0  
120             } elsif ($type eq 'text') {
121 12         51 return text2html($node->textContent);
122             } else {
123 0         0 return undef;
124             }
125              
126             }
127              
128             sub _read_entry {
129              
130 63     63   81 my ($node) = @_;
131              
132 63         427 my $entry = {
133             nossid => undef,
134             status => undef,
135             feed => undef,
136             title => undef,
137             link => undef,
138             author => undef,
139             category => undef,
140             summary => undef,
141             published => undef,
142             updated => undef,
143             uid => undef,
144             };
145              
146 63         105 my $summary;
147              
148 63         118 for my $n ($node->childNodes) {
149              
150 1363         15001 my $name = $n->nodeName;
151              
152 1363 100       4579 if ($name =~ /^media:/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
153 5         20 my $c = parse_media_node($n);
154 5         15 for my $k (keys %$c) {
155 14 100       66 if (ref $c->{ $k } eq 'ARRAY') {
156 2         5 push @{ $entry->{ $k } }, @{ $c->{ $k } };
  2         5  
  2         8  
157             } else {
158 12   66     57 $entry->{ $k } //= $c->{ $k };
159             }
160             }
161             } elsif ($name eq 'id') {
162 63         197 $entry->{ uid } = $n->textContent;
163             } elsif ($name eq 'title') {
164 61         104 @{ $entry }{ qw(title displaytitle) } = _title($n);
  61         196  
165             } elsif ($name eq 'updated') {
166 63         97 $entry->{ updated } = _time($n);
167             } elsif ($name eq 'author') {
168 63         94 $entry->{ author } = _name($n);
169             } elsif ($name eq 'content') {
170 49         100 $entry->{ summary } = _content($n);
171             } elsif ($name eq 'link') {
172 61         104 $entry->{ link } = _link($n);
173             } elsif ($name eq 'summary') {
174 12         21 $summary = _summary($n);
175             } elsif ($name eq 'category') {
176 210         302 my $term = $n->getAttribute('term');
177 210 50       1349 next unless defined $term;
178 210         210 push @{ $entry->{ category } }, $term;
  210         406  
179             } elsif ($name eq 'published') {
180 63         100 $entry->{ published } = _time($n);
181             }
182              
183             }
184              
185 63 100 66     230 if (not defined $entry->{ summary } and defined $summary) {
186 12         924 $entry->{ summary } = $summary;
187             }
188              
189 63   33     3261 $entry->{ title } //= $entry->{ link };
190              
191 63         115 return $entry;
192              
193             }
194              
195             sub read_feed {
196              
197 13     13 0 30 my ($class, $feed, $dom) = @_;
198              
199 13         45 my $channel = {
200             nossname => $feed->name,
201             nosslink => $feed->feed,
202             title => undef,
203             link => undef,
204             description => undef,
205             updated => undef,
206             author => undef,
207             category => undef,
208             generator => undef,
209             image => undef,
210             rights => undef,
211             skiphours => undef,
212             skipdays => undef,
213             };
214              
215 13         24 my $entries = [];
216              
217 13         17 my @entry_nodes;
218              
219 13         59 for my $n ($dom->documentElement->childNodes) {
220              
221 425         1873 my $name = $n->nodeName;
222              
223 425 100       1221 if ($name eq 'entry') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
224 63         74 push @entry_nodes, $n;
225             } elsif ($name eq 'title') {
226 13         36 $channel->{ title } = _title($n);
227             } elsif ($name eq 'link') {
228 13         29 $channel->{ link } = _link($n);
229             } elsif ($name eq 'updated') {
230 13         31 $channel->{ updated } = _time($n);
231             } elsif ($name eq 'author') {
232 13         28 $channel->{ author } = _name($n);
233             } elsif ($name eq 'category') {
234 39         89 my $term = $n->getAttribute('term');
235 39 50       304 next unless defined $term;
236 39         35 push @{ $channel->{ category } }, $term;
  39         93  
237             } elsif ($name eq 'generator') {
238 13         39 $channel->{ generator } = $n->textContent;
239             } elsif ($name eq 'logo') {
240 13         35 $channel->{ image } = $n->textContent;
241             } elsif ($name eq 'rights') {
242 13         31 $channel->{ rights } = $n->textContent;
243             } elsif ($name eq 'subtitle') {
244 13         30 $channel->{ description } = $n->textContent;
245             }
246              
247             }
248              
249 13 50       51 if (not defined $channel->{ title }) {
250 0         0 die sprintf "%s is not a valid Atom feed\n", $feed->name;
251             }
252              
253 13         894 for my $n (@entry_nodes) {
254 63         109 my $e = _read_entry($n);
255 63 50       114 next unless defined $e;
256 63         119 $e->{ feed } = $channel->{ nossname };
257 63         120 push @$entries, $e;
258             }
259              
260 13 50       42 unless (@$entries) {
261 0         0 die sprintf "%s contains no posts\n", $feed->name;
262             }
263              
264             @$entries =
265 63         104 map { $_->[1] }
266             sort {
267 99   33     157 my $at = $a->[1]{ published } // $a->[1]{ updated };
268 99   33     145 my $bt = $b->[1]{ published } // $b->[1]{ updated };
269 99 50 33     217 if (not defined $at and not defined $bt) {
    50          
    50          
270 0         0 $a->[0] <=> $b->[0];
271             } elsif (not defined $at) {
272 0         0 -1;
273             } elsif (not defined $bt) {
274 0         0 1;
275             } else {
276 99         146 $at <=> $bt;
277             }
278             }
279 13         62 map { [ $_, $entries->[$_]] }
  63         138  
280             0 .. $#$entries;
281              
282             $channel->{ updated } //=
283             $entries->[$#$entries]{ updated } //
284 13   0     64 $entries->[$#$entries]{ published };
      33        
285              
286             # So the feed doesn't have an updated field (which is technically required),
287             # and none of its posts have an updated or published field either. The
288             # feed is probably beyond saving.
289 13 50       29 if (not defined $channel->{ updated }) {
290 0         0 die sprintf "%s is not a valid Atom feed\n", $feed->name;
291             }
292              
293 13         39 return ($channel, $entries);
294              
295             }
296              
297             1;
298              
299             # vim: expandtab shiftwidth=4