File Coverage

blib/lib/XML/RSS/LibXML.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package XML::RSS::LibXML;
2 29     29   588112 use strict;
  29         79  
  29         1310  
3 29     29   165 use warnings;
  29         59  
  29         1079  
4 29     29   173 use base qw(Class::Accessor::Fast);
  29         71  
  29         32693  
5 29     29   134274 use Carp;
  29         82  
  29         3822  
6 29     29   31905 use UNIVERSAL::require;
  29         65451  
  29         314  
7 29     29   50042 use XML::LibXML;
  0            
  0            
8             use XML::LibXML::XPathContext;
9             use XML::RSS::LibXML::Namespaces qw(NS_RSS10);
10              
11             our $VERSION = '0.3105';
12              
13             __PACKAGE__->mk_accessors($_) for qw(impl encoding strict namespaces modules output stylesheets _internal num_items);
14              
15             sub new
16             {
17             my $class = shift;
18             my %args = @_;
19              
20             my $impl = $class->create_impl($args{version});
21             my $self = bless {
22             impl => $impl,
23             version => $args{version},
24             base => $args{base},
25             encoding => $args{encoding} || 'UTF-8',
26             strict => exists $args{strict} ? $args{strict} : 0,
27             namespaces => {},
28             modules => {},
29             _internal => {},
30             stylesheets => $args{stylesheet} ? (ref ($args{stylesheet}) eq 'ARRAY' ? $args{stylesheet} : [ $args{stylesheet} ]) : [],
31             num_items => 0,
32             libxml_opts => $args{libxml_opts} || {
33             recover => 1,
34             load_ext_dtd => 0
35             },
36             }, $class;
37              
38             $self->impl->reset($self);
39             return $self;
40             }
41              
42             {
43             # Proxy methods
44             foreach my $method (qw(reset channel image add_item textinput skipDays skipHours)) {
45             no strict 'refs';
46             *{$method} = sub { my $self = shift; $self->impl->$method($self, @_) };
47             }
48             }
49              
50             sub internal
51             {
52             my $self = shift;
53             my $name = shift;
54              
55             my $value = $self->{_internal}{$name};
56             if (@_) {
57             $self->{_internal}{$name} = $_[0];
58             }
59             return $value;
60             }
61              
62             sub version
63             {
64             my $self = shift;
65             my $version = $self->{version};
66             if (@_) {
67             $self->{version} = $_[0];
68             $self->internal('version', $_[0]);
69             }
70             return $version;
71             }
72              
73             sub base
74             {
75             my $self = shift;
76             my $base = $self->{base};
77             if (@_) {
78             $self->{base} = $_[0];
79             $self->internal('base', $_[0]);
80             }
81             return $base;
82             }
83              
84             sub add_module
85             {
86             my $self = shift;
87             my %args = @_;
88              
89             if ($args{prefix} eq '#default') {
90             # no op
91             } else {
92             $args{prefix} =~ /^[a-zA-Z_][a-zA-Z0-9.\-_]*$/
93             or croak "a namespace prefix should look like [a-z_][a-z0-9.\\-_]*";
94             }
95              
96             $args{uri}
97             or croak "a URI must be provided in a namespace declaration";
98              
99             $self->namespaces->{$args{prefix}} = $args{uri};
100             $self->modules->{$args{uri}} = $args{prefix};
101             }
102              
103             sub items
104             {
105             my $self = shift;
106             my $items = $self->{items};
107             $items ?
108             (wantarray ? @$items : $items) :
109             (wantarray ? () : undef);
110             }
111              
112             sub create_impl
113             {
114             my $self = shift;
115             my $version = shift;
116             my $module = "Null";
117             if ($version) {
118             $module = $version;
119             $module =~ s/\./_/g;
120             $module = "V$module";
121             }
122              
123             my $pkg;
124             REQUIRE: {
125             $pkg = "XML::RSS::LibXML::$module";
126             eval {
127             $pkg->require or die;
128             };
129             if (my $e = $@) {
130             if ($e =~ /Can't locate/) {
131             $module = "V1_0";
132             $version = '1.0';
133             redo REQUIRE;
134             }
135             }
136             }
137             return $pkg->new;
138             }
139              
140             sub create_libxml
141             {
142             my $self = shift;
143             my $p = XML::LibXML->new;
144             my $opts = $self->{libxml_opts} || {};
145             while (my($key, $value) = each %$opts) {
146             $p->$key($value);
147             }
148              
149             return $p;
150             }
151              
152             sub parse
153             {
154             my $self = shift;
155             $self->reset();
156             my $p = $self->create_libxml;
157             my $dom = $p->parse_string($_[0]);
158             $self->parse_dom($dom);
159             $self;
160             }
161              
162             sub parsefile
163             {
164             my $self = shift;
165             $self->reset();
166             my $p = $self->create_libxml;
167             my $dom = $p->parse_file($_[0]);
168             $self->parse_dom($dom);
169             $self;
170             }
171              
172             sub parse_dom
173             {
174             my $self = shift;
175             my $dom = shift;
176             my $version = $self->guess_version_from_dom($dom);
177             my $impl = $self->create_impl($version);
178             $self->impl($impl);
179             $self->impl->parse_dom($self, $dom);
180             $self;
181             }
182              
183             sub get_namespaces
184             {
185             my $self = shift;
186             my $node = shift;
187             my %h = map {
188             (($_->getLocalName() || '#default') => $_->getData)
189             } $node->getNamespaces();
190              
191             if ($h{rdf} && ! $h{'#default'}) {
192             $h{'#default'} = NS_RSS10;
193             }
194              
195             return wantarray ? %h : \%h;
196             }
197              
198             sub create_xpath_context
199             {
200             my $self = shift;
201             my $namespaces = shift || {};
202             my $xc = XML::LibXML::XPathContext->new;
203             foreach my $prefix (keys %$namespaces) {
204             my $namespace = $namespaces->{$prefix};
205             $xc->registerNs($prefix, $namespace);
206             }
207             return $xc;
208             }
209              
210             sub guess_version_from_dom
211             {
212             my $self = shift;
213             my $dom = shift;
214             my $root = $dom->documentElement();
215             my $namespaces = $self->get_namespaces($root);
216             # Check if we have non-default RSS namespace
217             my $rss10_prefix = 'rss10';
218             while (my($prefix, $uri) = each %$namespaces) {
219             if ($uri eq NS_RSS10) {
220             $rss10_prefix = $prefix;
221             last;
222             }
223             }
224              
225             if ($rss10_prefix && $rss10_prefix eq '#default') {
226             $rss10_prefix = 'rss10';
227             $namespaces->{$rss10_prefix} = NS_RSS10;
228             $root->setNamespace(NS_RSS10, $rss10_prefix, 0);
229             }
230              
231             keys %{$namespaces}; # reset iterator
232              
233             my $xc = $self->create_xpath_context(
234             # use the minimum required to guess
235             $namespaces
236             );
237              
238             my $version = 'UNKNOWN';
239              
240             # Test starting from the most likely candidate
241             if (eval { $xc->findnodes('/rdf:RDF', $dom) }) {
242             # 1.0 or 0.9.
243             # Wrap up in evail, because we may not have registered rss10
244             # namespace prefix
245             if (eval { $xc->findnodes("/rdf:RDF/$rss10_prefix:channel", $dom) }) {
246             $version = '1.0';
247             } else {
248             $version = '0.9';
249             }
250             } elsif (eval { $xc->findnodes('/rss', $dom) }) {
251             # 0.91 or 2.0 -ish
252             $version = $xc->findvalue('/rss/@version', $dom);
253             } else {
254             die "Failed to guess version";
255             }
256             $version = "$1.0" if $version =~ /^(\d)$/;
257             return $version;
258             }
259              
260             sub as_string
261             {
262             my $self = shift;
263             my $format = @_ ? $_[0] : 1;
264             my $impl = $self->create_impl($self->output || $self->version);
265             $self->impl($impl);
266             $self->impl->as_string($self, $format);
267             }
268              
269             sub save
270             {
271             my $self = shift;
272             my $file = shift;
273            
274             open(OUT, ">$file") or Carp::croak("Cannot open file $file for write: $!");
275             print OUT $self->as_string;
276             close(OUT);
277             }
278              
279             1;
280              
281             __END__