File Coverage

blib/lib/XML/RSSLite.pm
Criterion Covered Total %
statement 95 164 57.9
branch 38 94 40.4
condition 7 23 30.4
subroutine 11 13 84.6
pod 2 4 50.0
total 153 298 51.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2             package XML::RSSLite;
3 2     2   726 use strict;
  2         8  
  2         86  
4 2     2   12 use vars qw($VERSION);
  2         3  
  2         134  
5              
6             $VERSION = 0.17;
7              
8             sub import{
9 2     2   10 no strict 'refs';
  2         3  
  2         3995  
10 2     2   15 shift;
11 2         4 my $pkg = scalar caller();
12 2         13 *{"${pkg}::parseRSS"} = \&parseRSS;
  2         13  
13 2 50       3311 *{"${pkg}::parseXML"} = \&parseXML if grep($_ eq 'parseXML', @_);
  0         0  
14             }
15              
16              
17             sub parseRSS {
18 3     3 1 260 my($rr, $cref, $strip) = @_;
19              
20 3 50       9 die "$rr is not a hash reference" unless ref($rr) eq 'HASH';
21 3 50       6 die "$cref is not a scalar reference" unless ref($cref) eq 'SCALAR';
22              
23             # Gotta have some content to parse
24 3 50       7 return unless $$cref;
25              
26 3         6 preprocess($cref, $strip);
27             {
28 3 100       4 _parseRSS($rr, $cref), last if index(${$cref}, '
  3         4  
  3         15  
29 1 50       2 _parseRDF($rr, $cref), last if index(${$cref}, '
  1         5  
30 0 0       0 _parseSN( $rr, $cref), last if index(${$cref}, '
  0         0  
31 0 0       0 _parseWL( $rr, $cref), last if index(${$cref}, '
  0         0  
32 0         0 die "Content must be RSS|RDF|ScriptingNews|Weblog|reasonably close";
33             }
34 3         8 postprocess($rr);
35             }
36              
37             sub preprocess {
38 3     3 0 5 my($cref, $strip) = @_;
39 3         30 $$cref =~ y/\r\n/\n/s;
40              
41 3 100       9 if( ! defined($strip) ){
    50          
42 2         15 $$cref =~ y{\n\t ~0-9\-+!@#$%^&*()_=a-zA-Z[]\\;':",./<>?}{ }cs;
43             }
44             elsif($strip eq '1' ){
45 1         64 $$cref =~ s/[[:^print:]]/ /g;
46             }
47              
48             #XXX $$cref =~ s/&(?!0[a-zA-Z0-9]+|#\d+);/amp/gs;
49             #XXX Do we wish to (re)allow escaped HTML?!
50 3         917 $$cref =~ s{(?:<|<)/?(?:b|i|h[1-6]|p|center|quote|strong)(?:>|>)}{}gsi;
51             }
52              
53             sub _parseRSS {
54 2     2   6 parseXML($_[0], $_[1], 'channel', 0);
55 2         7 $_[0]->{'items'} = $_[0]->{'item'};
56             }
57              
58             sub _parseRDF {
59 1     1   3 my ($rr, $cref) = @_;
60              
61 1         3 $rr->{'items'} = [];
62 1         1 my $item;
63              
64 1         4 parseXML($_[0], $_[1], 'rdf:RDF', 0);
65              
66             # Alias RDF to RSS
67 1 50       3 if( exists($rr->{'item'}) ){
68 1         3 $rr->{'items'} = $rr->{'item'};
69             }
70             else{
71 0   0     0 my $li = $_[0]->{'rdf:li'} || $_[0]->{'rdf:Seq'}->{'rdf:li'};
72 0         0 foreach $item ( @{$li} ){
  0         0  
73 0         0 my %ia;
74 0 0       0 if (exists $item->{'dc:description'}) {
75 0         0 $ia{'description'} = $item->{'dc:description'};
76             }
77 0 0       0 if (exists $item->{'dc:title'}) {
78 0         0 $ia{'title'} = $item->{'dc:title'};
79             }
80 0 0       0 if (exists $item->{'dc:identifier'}) {
81 0         0 $ia{'link'} = delete($item->{'dc:identifier'});
82             }
83            
84 0         0 push(@{$rr->{'items'}}, \%ia);
  0         0  
85             }
86             }
87             }
88              
89             sub _parseSN {
90 0     0   0 my ($rr, $cref) = @_;
91            
92 0         0 $rr->{'items'} = ();
93 0         0 my $item;
94              
95 0         0 parseXML($rr, $cref, 'channel', 0);
96            
97             # Alias SN to RSS terms
98 0         0 foreach $item ( @{$_[0]->{'rdf:li'}} ){
  0         0  
99 0         0 my %ia;
100 0 0       0 if (exists $item->{'text'}) {
101 0         0 $ia{'description'} = $item->{'text'};
102             }
103 0 0       0 if (exists $item->{'linetext'}) {
104 0         0 $ia{'title'} = $item->{'linetext'};
105             }
106 0 0       0 if (exists $item->{'url'}) {
107 0         0 $ia{'link'} = $item->{'url'};
108             }
109              
110 0         0 push(@{$rr->{'items'}}, \%ia);
  0         0  
111             }
112             }
113              
114              
115             sub _parseWL {
116 0     0   0 my ($rr, $cref) = @_;
117              
118 0         0 $rr->{'items'} = ();
119 0         0 my $item;
120              
121             #XXX is this the right tag to parse for?
122 0         0 parseXML($rr, $cref, 'channel', 0);
123            
124             # Alias WL to RSS
125 0         0 foreach $item ( @{$_[0]->{'rdf:li'}} ){
  0         0  
126 0         0 my %ia;
127 0 0       0 if (exists $item->{'url'}) {
128 0         0 $ia{'link'} = delete($item->{'url'});
129             }
130              
131 0         0 push(@{$rr->{'items'}}, \%ia);
  0         0  
132             }
133             }
134              
135              
136             sub postprocess {
137 3     3 0 5 my $rr = shift;
138              
139             #XXX Not much to do, what about un-munging URL's in source, etc.?!
140 3 50       6 return unless defined($rr->{'items'});
141 3 50       8 $rr->{'items'} = [$rr->{'items'}] unless ref($rr->{'items'}) eq 'ARRAY';
142              
143 3         5 foreach my $i (@{$rr->{'items'}}) {
  3         6  
144 32 50       52 $i->{description} = $i->{description}->{'<>'} if ref($i->{description});
145              
146             # Put stuff into the right name if necessary
147 32 50       45 if( not $i->{'link'} ){
148 0 0       0 if( defined($i->{'url'}) ){
    0          
149 0         0 $i->{'link'} = delete($i->{'url'}); }
150             # See if you can use misplaced url in title for empty links
151             elsif( exists($i->{'title'}) ){
152             # The next case would trap this, but try to short-circuit the gathering
153 0 0       0 if ($i->{'title'} =~ /^(?:https?|ftp):/) {
    0          
154 0         0 $i->{'link'} = $i->{'title'};
155             }
156             elsif ($i->{'title'} =~ /"((?:https?|ftp).*?)"/) {
157 0         0 $i->{'link'} = $1;
158 0         0 $i->{'title'} =~ s/<.*?>//;
159             }
160             else {
161 0         0 next;
162             }
163             }
164             }
165            
166             # Trim whitespace
167 32         44 $i->{'link'} =~ s/\s+$//;
168 32         41 $i->{'link'} =~ s/^\s+//;
169              
170             # Make sure you've got an http/ftp link
171 32 50 33     112 if( exists( $i->{'link'}) && $i->{'link'} !~ m{^(https?|ftp)://}i) {
172             ## Rip link out of anchor tag
173 0 0 0     0 if( ref($i->{'link'}) && $i->{'link'}->{a}->{href} ){
    0 0        
174 0         0 $i->{'link'} = $i->{'link'}->{a}->{href} }
175             ## Smells like a relative url
176             elsif( $i->{'link'} =~ m{^[#/]} and $rr->{'link'} =~ m{^https?://} ){
177 0 0       0 if (substr($i->{'link'}, 0, 1) ne '/') {
178 0         0 $i->{'link'} = '/' . $i->{'link'};
179             }
180 0         0 $i->{'link'} = $rr->{'link'} . $i->{'link'};
181             }
182             else {
183 0         0 next;
184             }
185             }
186            
187             #If we don't have a title, use the link
188 32 50       45 unless( defined($i->{'title'}) ){
189 0         0 $i->{'title'} = $i->{'link'};
190             }
191            
192 32 50       45 if( exists($i->{'link'}) ){
193             #XXX # Fix pre-process munging
194             # $i->{'link'} =~ s/&/&/gi;
195 32         47 $i->{'link'} =~ s/ /%20/g;
196             }
197             }
198             }
199              
200             sub parseXML{
201 3     3 1 6 my($hash, $xml, $tag, $comments) = @_;
202 3         4 my($begin, $end, @comments);
203 3         5 local $_;
204              
205             #Kill comments
206 3   33     4 while( ($begin = index(${$xml}, '') for @comments;
  0         0  
223              
224             #Expose comments if requested
225 0 0       0 do{ push(@$comments, $_->[1]) for @comments } if ref($comments) eq 'ARRAY';
  0         0  
226             }
227             }
228              
229             sub _parseXML{
230 43     43   67 my($hash, $xml, $tag, $index) = @_;
231 43         66 my($begin, $end);
232              
233             #Find topTag and set pos to start matching from there
234 43         43 ${$xml} =~ /<$tag(?:>|\s)/g;
  43         256  
235 43   50     68 ($begin, $end) = (0, pos(${$xml})||0);
236              
237             #Match either or , optional attributes, stash tag name
238 43         55 while( ${$xml} =~ m%<([^\s>]+)(?:\s+[^>]*?)?(?:/|>.*?%sg ){
  233         1083  
239              
240             #Save the tag name, we'll need it
241 190   33     423 $tag = $1 || $2;
242              
243             #Save the new beginning and end
244 190         200 ($begin, $end) = ($end, pos(${$xml}));
  190         270  
245              
246             #Get the bit we just matched.
247 190         212 my $str = substr(${$xml}, $begin, $end-$begin);
  190         334  
248            
249             #Extract the actual attributes and contents of the tag
250 190 100       2848 $str =~ m%<\Q$tag\E\s*([^>]*?)?>(.*?)%s ||
251             #XXX pointed out by hv
252             # $str =~ s%^.*?<$tag\s*([^>]*?)?>(.*?)%<$tag>$2%s ||
253             $str =~ m%<\Q$tag\E\s*([^>]*?)?\s*/>%;
254 190         507 my($attr, $content) = ($1, $2);
255              
256             #Did we get attributes? clean them up and chuck them in a hash.
257 190 100       290 if( $attr ){
258 43         70 ($_, $attr) = ($attr, {});
259 43         340 $attr->{$1} = $3 while m/([^\s=]+)\s*=\s*(['"]?)([^\2>]*?)(?:\2|$)/g;
260             }
261              
262 190         197 my $inhash;
263             #Recurse if contents has more tags, replace contents with reference we get
264 190 100 100     448 if( $content && index($content, '<') > -1 ){
265 40         112 _parseXML($inhash={}, \$str, $tag);
266             #Was there any data in the contents? We should extract that...
267 40 50       115 if( $str =~ />[^><]+
268             #The odd RE above shortcircuits unnecessary entry
269              
270             #Clean whitespace between tags
271             #$str =~ s%(?<=>)?\s*(?=<)%%g; #XXX ~same speed, wacko warning
272             #$str =~ s%(>?)\s*<%$1<%g;
273             #XXX #$str =~ s%(?:^|(?<=>))\s*(?:(?=<)|\z)%%g
274              
275 40         46 my $qr = qr{@{[join('|', map { quotemeta } keys %{$inhash})]}};
  40         41  
  119         866  
  40         101  
276 40         1143 $content =~ s%<($qr)\s*(?:[^>]*?)?(?:/|>.*?%%sg;
277              
278 40 50       137 $inhash->{'<>'} = $content if $content =~ /\S/;
279             }
280             }
281              
282 190 100       323 if( ref($inhash) ){
    100          
283             #We have attributes? Then we should merge them.
284 40 100       63 if( ref($attr) ){
285 22         23 for( keys %{$attr} ){
  22         52  
286             $inhash->{$_} = exists($inhash->{$_}) ?
287             (ref($inhash->{$_}) eq 'ARRAY' ?
288 0         0 [@{$inhash->{$_}}, $attr->{$_}] :
289 22 0       54 [ $inhash->{$_}, $attr->{$_}] ) : $attr->{$_};
    50          
290             }
291             }
292             }
293             elsif( ref($attr) ){
294 21         22 $inhash = $attr;
295             }
296             else{
297             #Otherwise save our content
298 129         204 $inhash = $content;
299             }
300            
301             $hash->{$tag} = exists($hash->{$tag}) ?
302             (ref($hash->{$tag}) eq 'ARRAY' ?
303 44         147 [@{$hash->{$tag}}, $inhash] :
304 190 100       445 [ $hash->{$tag}, $inhash] ) : $inhash;
    100          
305             }
306             }
307              
308             1;
309             __END__