File Coverage

blib/lib/XML/RSS.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package XML::RSS;
2              
3 33     33   419237 use strict;
  33         70  
  33         1302  
4 33     33   145 use warnings;
  33         44  
  33         950  
5              
6 33     33   175 use Carp;
  33         45  
  33         2378  
7 33     33   24724 use XML::Parser;
  0            
  0            
8              
9             use XML::RSS::Private::Output::Base;
10             use XML::RSS::Private::Output::V0_9;
11             use XML::RSS::Private::Output::V0_91;
12             use XML::RSS::Private::Output::V1_0;
13             use XML::RSS::Private::Output::V2_0;
14              
15             use vars qw($VERSION $AUTOLOAD @ISA $AUTO_ADD);
16              
17             require 5.008;
18              
19             $VERSION = '1.56';
20              
21             $AUTO_ADD = 0;
22              
23             sub _get_ok_fields {
24             return {
25             "0.9" => {
26             channel => {
27             title => undef,
28             description => undef,
29             link => undef,
30             },
31             image => {
32             title => undef,
33             url => undef,
34             link => undef,
35             },
36             textinput => {
37             title => undef,
38             description => undef,
39             name => undef,
40             link => undef,
41             },
42             },
43             "0.91" => {
44             channel => {
45             title => undef,
46             copyright => undef,
47             description => undef,
48             docs => undef,
49             language => undef,
50             lastBuildDate => undef,
51             'link' => undef,
52             managingEditor => undef,
53             pubDate => undef,
54             rating => undef,
55             webMaster => undef,
56             },
57             image => {
58             title => undef,
59             url => undef,
60             'link' => undef,
61             width => undef,
62             height => undef,
63             description => undef,
64             },
65             skipDays => {day => undef,},
66             skipHours => {hour => undef,},
67             textinput => {
68             title => undef,
69             description => undef,
70             name => undef,
71             'link' => undef,
72             },
73             },
74             "2.0" => {
75             channel => {
76             title => undef,
77             'link' => undef,
78             description => undef,
79             language => undef,
80             copyright => undef,
81             managingEditor => undef,
82             webMaster => undef,
83             pubDate => undef,
84             lastBuildDate => undef,
85             category => undef,
86             generator => undef,
87             docs => undef,
88             cloud => '',
89             ttl => undef,
90             image => '',
91             textinput => '',
92             skipHours => '',
93             skipDays => '',
94             },
95             image => {
96             title => undef,
97             url => undef,
98             'link' => undef,
99             width => undef,
100             height => undef,
101             description => undef,
102             },
103             skipDays => {day => undef,},
104             skipHours => {hour => undef,},
105             textinput => {
106             title => undef,
107             description => undef,
108             name => undef,
109             'link' => undef,
110             },
111             },
112             'default' => {
113             channel => {
114             title => undef,
115             description => undef,
116             link => undef,
117             },
118             image => {
119             title => undef,
120             url => undef,
121             link => undef,
122             },
123             textinput => {
124             title => undef,
125             description => undef,
126             name => undef,
127             link => undef,
128             },
129             },
130             };
131             }
132              
133             # define required elements for RSS 0.9
134             my $_REQ_v0_9 = {
135             channel => {
136             "title" => [1, 40],
137             "description" => [1, 500],
138             "link" => [1, 500]
139             },
140             image => {
141             "title" => [1, 40],
142             "url" => [1, 500],
143             "link" => [1, 500]
144             },
145             item => {
146             "title" => [1, 100],
147             "link" => [1, 500]
148             },
149             textinput => {
150             "title" => [1, 40],
151             "description" => [1, 100],
152             "name" => [1, 500],
153             "link" => [1, 500]
154             }
155             };
156              
157             # define required elements for RSS 0.91
158             my $_REQ_v0_9_1 = {
159             channel => {
160             "title" => [1, 100],
161             "description" => [1, 500],
162             "link" => [1, 500],
163             "language" => [1, 5],
164             "rating" => [0, 500],
165             "copyright" => [0, 100],
166             "pubDate" => [0, 100],
167             "lastBuildDate" => [0, 100],
168             "docs" => [0, 500],
169             "managingEditor" => [0, 100],
170             "webMaster" => [0, 100],
171             },
172             image => {
173             "title" => [1, 100],
174             "url" => [1, 500],
175             "link" => [0, 500],
176             "width" => [0, 144],
177             "height" => [0, 400],
178             "description" => [0, 500]
179             },
180             item => {
181             "title" => [1, 100],
182             "link" => [1, 500],
183             "description" => [0, 500]
184             },
185             textinput => {
186             "title" => [1, 100],
187             "description" => [1, 500],
188             "name" => [1, 20],
189             "link" => [1, 500]
190             },
191             skipHours => {"hour" => [1, 23]},
192             skipDays => {"day" => [1, 10]}
193             };
194              
195             # define required elements for RSS 2.0
196             my $_REQ_v2_0 = {
197             channel => {
198             "title" => [1, 100],
199             "description" => [1, 500],
200             "link" => [1, 500],
201             "language" => [0, 5],
202             "rating" => [0, 500],
203             "copyright" => [0, 100],
204             "pubDate" => [0, 100],
205             "lastBuildDate" => [0, 100],
206             "docs" => [0, 500],
207             "managingEditor" => [0, 100],
208             "webMaster" => [0, 100],
209             },
210             image => {
211             "title" => [1, 100],
212             "url" => [1, 500],
213             "link" => [0, 500],
214             "width" => [0, 144],
215             "height" => [0, 400],
216             "description" => [0, 500]
217             },
218             item => {
219             "title" => [1, 100],
220             "link" => [1, 500],
221             "description" => [0, 500]
222             },
223             textinput => {
224             "title" => [1, 100],
225             "description" => [1, 500],
226             "name" => [1, 20],
227             "link" => [1, 500]
228             },
229             skipHours => {"hour" => [1, 23]},
230             skipDays => {"day" => [1, 10]}
231             };
232              
233             my $namespace_map = {
234             rss10 => 'http://purl.org/rss/1.0/',
235             rss09 => 'http://my.netscape.com/rdf/simple/0.9/',
236              
237             # rss091 => 'http://purl.org/rss/1.0/modules/rss091/',
238             rss20 => 'http://backend.userland.com/blogChannelModule',
239             };
240              
241             sub _rdf_resource_fields {
242             return {
243             'http://webns.net/mvcb/' => {
244             'generatorAgent' => 1,
245             'errorReportsTo' => 1
246             },
247             'http://purl.org/rss/1.0/modules/annotate/' => {'reference' => 1},
248             'http://my.theinfo.org/changed/1.0/rss/' => {'server' => 1}
249             };
250             }
251              
252             my %empty_ok_elements = (enclosure => 1);
253             my %hashref_ok_elements = (description => 1);
254              
255             sub _get_default_modules {
256             return {
257             'http://purl.org/rss/1.0/modules/syndication/' => 'syn',
258             'http://purl.org/dc/elements/1.1/' => 'dc',
259             'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo',
260             'http://webns.net/mvcb/' => 'admin',
261             'http://purl.org/rss/1.0/modules/content/' => 'content',
262             };
263             }
264              
265             sub _get_default_rss_2_0_modules {
266             return {'http://backend.userland.com/blogChannelModule' => 'blogChannel',};
267             }
268              
269             sub _get_syn_ok_fields {
270             return [qw(updateBase updateFrequency updatePeriod)];
271             }
272              
273             sub _get_dc_ok_fields {
274             return [qw(
275             contributor
276             coverage
277             creator
278             date
279             description
280             format
281             identifier
282             language
283             publisher
284             relation
285             rights
286             source
287             subject
288             title
289             type
290             )];
291             }
292              
293             sub new {
294             my $class = shift;
295              
296             my $self = {};
297              
298             bless $self, $class;
299              
300             $self->_initialize(@_);
301              
302             return $self;
303             }
304              
305             sub _get_init_default_key_assignments {
306             return [
307             {key => "version", default => '1.0',},
308             {key => "encode_output", default => 1,},
309             {key => "output", default => "",},
310             {key => "encoding", default => "UTF-8",},
311             {key => "encode_cb", default => undef(),},
312             {key => "xml:base", default => undef(),},
313             ];
314             }
315              
316             # This method resets the contents of the instance to an empty one (with no
317             # items, empty keys, etc.). Useful before parsing or during initialization.
318              
319             sub _reset {
320             my $self = shift;
321              
322             # internal hash
323             $self->{_internal} = {};
324              
325             # init num of items to 0
326             $self->{num_items} = 0;
327              
328             # initialize items
329             $self->{items} = [];
330              
331             delete $self->{_allow_multiple};
332              
333             my $ok_fields = $self->_get_ok_fields();
334              
335             my $ver_ok_fields =
336             exists($ok_fields->{$self->{version}})
337             ? $ok_fields->{$self->{version}}
338             : $ok_fields->{default};
339              
340             while (my ($k, $v) = each(%$ver_ok_fields)) {
341             $self->{$k} = +{%{$v}};
342             }
343              
344             return;
345             }
346              
347             sub _initialize {
348             my $self = shift;
349             my %hash = @_;
350              
351             # adhere to Netscape limits; no by default
352             $self->{'strict'} = 0;
353              
354             # namespaces
355             $self->{namespaces} = {};
356             $self->{rss_namespace} = '';
357             foreach my $k (@{$self->_get_init_default_key_assignments()})
358             {
359             my $key = $k->{key};
360             $self->{$key} = exists($hash{$key}) ? $hash{$key} : $k->{default};
361             }
362              
363             # modules
364             $self->{modules} = (
365             ($self->{version} eq "2.0")
366             ? $self->_get_default_rss_2_0_modules()
367             : $self->_get_default_modules()
368             );
369              
370             # stylesheet
371             if (exists($hash{stylesheet})) {
372             $self->{stylesheet} = $hash{stylesheet};
373             }
374              
375             if ($self->{version} eq "2.0") {
376             $self->{namespaces}->{'blogChannel'} = "http://backend.userland.com/blogChannelModule";
377             }
378              
379             $self->_reset;
380              
381             return;
382             }
383              
384             sub add_module {
385             my $self = shift;
386             my $hash = {@_};
387              
388             $hash->{prefix} =~ /^[a-z_][a-z0-9.\-_]*$/i
389             or croak "a namespace prefix should look like [A-Za-z_][A-Za-z0-9.\\-_]*";
390              
391             $hash->{uri}
392             or croak "a URI must be provided in a namespace declaration";
393              
394             $self->{modules}->{$hash->{uri}} = $hash->{prefix};
395             }
396              
397             sub add_item {
398             my $self = shift;
399             my $hash = {@_};
400              
401             # strict Netscape Netcenter length checks
402             if ($self->{'strict'}) {
403              
404             # make sure we have a title and link
405             croak "title and link elements are required"
406             unless ($hash->{title} && $hash->{'link'});
407              
408             # check string lengths
409             croak "title cannot exceed 100 characters in length"
410             if (length($hash->{title}) > 100);
411             croak "link cannot exceed 500 characters in length"
412             if (length($hash->{'link'}) > 500);
413             croak "description cannot exceed 500 characters in length"
414             if (exists($hash->{description})
415             && length($hash->{description}) > 500);
416              
417             # make sure there aren't already 15 items
418             croak "total items cannot exceed 15 " if (@{$self->{items}} >= 15);
419             }
420              
421             # add the item to the list
422             if (defined($hash->{mode}) && $hash->{mode} eq 'insert') {
423             unshift(@{$self->{items}}, $hash);
424             }
425             else {
426             push(@{$self->{items}}, $hash);
427             }
428              
429             # return reference to the list of items
430             return $self->{items};
431             }
432              
433              
434             # $self->_render_complete_rss_output($xml_version)
435             #
436             # This function is the workhorse of the XML output and does all the work of
437             # rendering the RSS, delegating the work to specialised functions.
438             #
439             # It accepts the requested version number as its argument.
440              
441             sub _get_rendering_class {
442             my ($self, $ver) = @_;
443              
444             if ($ver eq "1.0")
445             {
446             return "XML::RSS::Private::Output::V1_0";
447             }
448             elsif ($ver eq "0.9")
449             {
450             return "XML::RSS::Private::Output::V0_9";
451             }
452             elsif ($ver eq "0.91")
453             {
454             return "XML::RSS::Private::Output::V0_91";
455             }
456             else
457             {
458             return "XML::RSS::Private::Output::V2_0";
459             }
460             }
461              
462             sub _get_encode_cb_params
463             {
464             my $self = shift;
465              
466             return
467             defined($self->{encode_cb}) ?
468             ("encode_cb" => $self->{encode_cb}) :
469             ()
470             ;
471             }
472              
473             sub _get_rendering_obj {
474             my ($self, $ver) = @_;
475              
476             return $self->_get_rendering_class($ver)->new(
477             {
478             main => $self,
479             version => $ver,
480             $self->_get_encode_cb_params(),
481             }
482             );
483             }
484              
485             sub _render_complete_rss_output {
486             my ($self, $ver) = @_;
487              
488             return $self->_get_rendering_obj($ver)->_render_complete_rss_output();
489             }
490              
491             sub as_rss_0_9 {
492             return shift->_render_complete_rss_output("0.9");
493             }
494              
495             sub as_rss_0_9_1 {
496             return shift->_render_complete_rss_output("0.91");
497             }
498              
499             sub as_rss_1_0 {
500             return shift->_render_complete_rss_output("1.0");
501             }
502              
503             sub as_rss_2_0 {
504             return shift->_render_complete_rss_output("2.0");
505             }
506              
507              
508              
509             sub _get_output_methods_map {
510             return {
511             '0.9' => "as_rss_0_9",
512             '0.91' => "as_rss_0_9_1",
513             '2.0' => "as_rss_2_0",
514             '1.0' => "as_rss_1_0",
515             };
516             }
517              
518             sub _get_default_output_method {
519             return "as_rss_1_0";
520             }
521              
522             sub _get_output_method {
523             my ($self, $version) = @_;
524              
525             if (my $output_method = $self->_get_output_methods_map()->{$version}) {
526             return $output_method;
527             }
528             else {
529             return $self->_get_default_output_method();
530             }
531             }
532              
533             sub _get_output_version {
534             my $self = shift;
535             return ($self->{output} =~ /\d/) ? $self->{output} : $self->{version};
536             }
537              
538             # This is done to preserve backwards compatibility with older versions
539             # of XML-RSS that had the channel/{link,description,title} as the empty
540             # string by default.
541             sub _output_env {
542             my $self = shift;
543             my $callback = shift;
544              
545             local $self->{channel}->{'link'} = $self->{channel}->{'link'};
546             local $self->{channel}->{'description'} = $self->{channel}->{'description'};
547             local $self->{channel}->{'title'} = $self->{channel}->{'title'};
548              
549             foreach my $field (qw(link description title))
550             {
551             if (!defined($self->{channel}->{$field}))
552             {
553             $self->{channel}->{$field} = '';
554             }
555             }
556              
557             return $callback->();
558             }
559              
560             sub as_string {
561             my $self = shift;
562              
563             my $version = $self->_get_output_version();
564              
565             my $output_method = $self->_get_output_method($version);
566              
567             return $self->_output_env(
568             sub { return $self->$output_method(); }
569             );
570             }
571              
572             # Checks if inside a possibly namespaced element
573             # TODO : After increasing test coverage convert all such conditionals to this
574             # method.
575             sub _my_in_element {
576             my ($self, $elem) = @_;
577              
578             my $parser = $self->_parser;
579              
580             return $parser->within_element($elem)
581             || $parser->within_element(
582             $parser->generate_ns_name($elem, $self->{rss_namespace})
583             );
584             }
585              
586             sub _get_elem_namespace_helper {
587             my ($self, $el) = @_;
588              
589             my $ns = $self->_parser->namespace($el);
590              
591             return (defined($ns) ? $ns : "");
592             }
593              
594             sub _get_elem_namespace {
595             my $self = shift;
596              
597             my ($el) = @_;
598              
599             my $ns = $self->_get_elem_namespace_helper(@_);
600              
601             my $verdict = (!$ns && !$self->{rss_namespace})
602             || ($ns eq $self->{rss_namespace});
603              
604             return ($ns, $verdict);
605             }
606              
607             sub _current_element {
608             my $self = shift;
609              
610             return $self->_parser->current_element;
611             }
612              
613             sub _get_current_namespace {
614             my $self = shift;
615              
616             return $self->_get_elem_namespace($self->_current_element);
617             }
618              
619             sub _is_rdf_resource {
620             my $self = shift;
621             my $el = shift;
622              
623             my $ns = shift;
624             if (!defined($ns))
625             {
626             $ns = $self->_parser->namespace($el);
627             }
628              
629             return (
630             exists($self->_rdf_resource_fields->{ $ns })
631             && exists($self->_rdf_resource_fields->{ $ns }{ $el })
632             );
633             }
634              
635             sub _get_ns_arrayity {
636             my ($self, $ns) = @_;
637              
638             my $is_array =
639             $self->_parse_options()->{'modules_as_arrays'}
640             && (!exists($self->_get_default_modules()->{$ns}))
641             # RDF
642             && ($ns ne "http://www.w3.org/1999/02/22-rdf-syntax-ns#")
643             ;
644              
645             my $default_ref = sub { $is_array ? [] : {} };
646              
647             return ($is_array, $default_ref);
648             }
649              
650             sub _append_text_to_elem_struct {
651             my ($self, $struct, $cdata, $mapping_sub, $is_array_sub) = @_;
652              
653             my $elem = $self->_current_element;
654              
655             my ($ns, $verdict) = $self->_get_current_namespace;
656              
657             # If it's in the default namespace
658             if ($verdict) {
659             $self->_append_struct(
660             $struct,
661             scalar($mapping_sub->($struct, $elem)),
662             scalar($is_array_sub->($struct, $elem)),
663             $cdata
664             );
665             }
666             else {
667             my $prefix = $self->{modules}->{$ns};
668              
669             my ($is_array, $default_ref) = $self->_get_ns_arrayity($ns);
670              
671             $self->_append_struct(
672             ($struct->{$ns} ||= $default_ref->()),
673             $elem,
674             (defined($prefix) && $prefix eq "dc"),
675             $cdata
676             );
677              
678             # If it's in a module namespace, provide a friendlier prefix duplicate
679             if ($prefix) {
680             $self->_append_struct(
681             ($struct->{$prefix} ||= $default_ref->()),
682             $elem,
683             ($prefix eq "dc"),
684             $cdata
685             );
686             }
687             }
688              
689             return;
690             }
691              
692             sub _append_struct {
693             my ($self, $struct, $key, $can_be_array, $cdata) = @_;
694              
695             if (ref($struct) eq 'ARRAY') {
696             $struct->[-1]->{'val'} .= $cdata;
697             return;
698             }
699             elsif (defined $struct->{$key}) {
700             if (ref($struct->{$key}) eq 'HASH') {
701             $struct->{$key}->{content} .= $cdata;
702             return;
703             }
704             elsif ($can_be_array && ref($struct->{$key}) eq 'ARRAY') {
705             $struct->{$key}->[-1] .= $cdata;
706             return;
707             }
708             }
709              
710             $struct->{$key} .= $cdata;
711             return;
712             }
713              
714             sub _return_elem {
715             my ($struct, $elem) = @_;
716             return $elem;
717             }
718              
719             sub _return_elem_is_array {
720             my ($struct, $elem) = @_;
721              
722             # Always return false because no element should be an array.
723             return;
724             }
725              
726             sub _append_text_to_elem {
727             my ($self, $ext_tag, $cdata) = @_;
728              
729             return $self->_append_text_to_elem_struct(
730             $self->$ext_tag(),
731             $cdata,
732             \&_return_elem,
733             \&_return_elem_is_array,
734             );
735             }
736              
737             sub _within_topics {
738             my $self = shift;
739              
740             my $parser = $self->_parser;
741              
742             return $parser->within_element(
743             $parser->generate_ns_name(
744             "topics", 'http://purl.org/rss/1.0/modules/taxonomy/'
745             )
746             );
747             }
748              
749             sub _return_item_elem {
750             my ($item, $elem) = @_;
751             if ($elem eq "guid") {
752             return $item->{isPermaLink} ? "permaLink" : "guid";
753             }
754             else {
755             return $elem;
756             }
757             }
758              
759             sub _return_item_elem_is_array {
760             my ($item, $elem) = @_;
761              
762             return ($elem eq "category");
763             }
764              
765             sub _append_text_to_item {
766             my ($self, $cdata) = @_;
767              
768             if (@{$self->{'items'}} < $self->{num_items}) {
769             push @{$self->{items}}, {};
770             }
771              
772             $self->_append_text_to_elem_struct(
773             $self->_last_item,
774             $cdata,
775             \&_return_item_elem,
776             \&_return_item_elem_is_array
777             );
778             }
779              
780             sub _append_to_array_elem {
781             my ($self, $category, $cdata) = @_;
782              
783             if (! $self->_my_in_element($category))
784             {
785             return;
786             }
787              
788             my $el = $self->_current_element;
789              
790             if (ref($self->{$category}->{$el}) eq "ARRAY") {
791             $self->{$category}->{$el}->[-1] .= $cdata;
792             }
793             else {
794             $self->{$category}->{$el} .= $cdata;
795             }
796              
797             return 1;
798             }
799              
800             sub _handle_char {
801             my ($self, $cdata) = (@_);
802              
803             # image element
804             if ($self->_my_in_element("image")) {
805             $self->_append_text_to_elem("image", $cdata);
806             }
807             # item element
808             elsif (defined($self->{_inside_item_elem})) {
809             return if $self->_within_topics;
810              
811             $self->_append_text_to_item($cdata);
812             }
813             # textinput element
814             elsif (
815             $self->_my_in_element("textinput") || $self->_my_in_element("textInput")
816             )
817             {
818             $self->_append_text_to_elem("textinput", $cdata);
819             }
820             # skipHours element
821             elsif ($self->_append_to_array_elem("skipHours", $cdata)) {
822             # Do nothing - already done in the predicate.
823             }
824             elsif ($self->_append_to_array_elem("skipDays", $cdata)) {
825             # Do nothing - already done in the predicate.
826             }
827             # channel element
828             elsif ($self->_my_in_element("channel")) {
829             if ($self->_within_topics() || $self->_my_in_element("items")) {
830             return;
831             }
832              
833             if ($self->_current_element eq "category") {
834             $self->_append_to_array_elem("channel", $cdata);
835             }
836             else {
837             $self->_append_text_to_elem("channel", $cdata);
838             }
839             }
840             }
841              
842             sub _handle_dec {
843             my ($self, $version, $encoding, $standalone) = (@_);
844             $self->{encoding} = $encoding;
845              
846             #print "ENCODING: $encoding\n";
847             }
848              
849             sub _should_be_hashref {
850             my ($self, $el) = @_;
851              
852             return
853             (
854             $empty_ok_elements{$el}
855             || ($self->_parse_options()->{'hashrefs_instead_of_strings'}
856             && $hashref_ok_elements{$el}
857             )
858             );
859             }
860              
861             sub _start_array_element_in_struct {
862             my ($self, $input_struct, $el, $prefix) = @_;
863              
864             my ($el_ns, $el_verdict) = $self->_get_elem_namespace($el);
865              
866             my ($is_array, $default_ref) = $self->_get_ns_arrayity($el_ns);
867              
868             my @structs = (!$el_verdict)
869             ? (
870             (exists($self->{modules}->{$el_ns})
871             ? ($input_struct->{$self->{modules}->{$el_ns}} ||= $default_ref->())
872             : ()
873             ),
874             ($input_struct->{$el_ns} ||= $default_ref->()),
875             )
876             : ($input_struct)
877             ;
878              
879             foreach my $struct (@structs)
880             {
881             if (ref($struct) eq 'ARRAY') {
882             push @$struct, { el => $el, val => "", };
883             }
884             # If it's an array - append a new empty element because a new one
885             # was started.
886             elsif (ref($struct->{$el}) eq "ARRAY") {
887             push @{$struct->{$el}}, "";
888             }
889             # If it's not an array but still full (i.e: it's only the second
890             # element), then turn it into an array
891             elsif (defined($struct->{$el}) && length($struct->{$el})) {
892             $struct->{$el} = [$struct->{$el}, ""];
893             }
894             # Else - do nothing and let the function append to the new value
895             #
896             }
897             return 1;
898             }
899              
900             sub _start_array_element {
901             my ($self, $cat, $el) = @_;
902              
903             if (!$self->_my_in_element($cat)) {
904             return;
905             }
906              
907             $self->_start_array_element_in_struct($self->{$cat}, $el);
908             return 1;
909             }
910              
911             sub _last_item {
912             my $self = shift;
913              
914             return ($self->{'items'}->[$self->{num_items} - 1] ||= {});
915             }
916              
917             sub _handle_start {
918             my $self = shift;
919             my $el = shift;
920             my %attribs = @_;
921              
922             my $parser = $self->_parser;
923              
924             my ($el_ns, $el_verdict) = $self->_get_elem_namespace($el);
925              
926             if ($el eq "image")
927             {
928             if (exists($attribs{'resource'}))
929             {
930             $self->image("rdf:resource", $attribs{'resource'});
931             }
932             }
933              
934             # beginning of RSS 0.91
935             if ($el eq 'rss') {
936             if (exists($attribs{version})) {
937             $self->{_internal}->{version} = $attribs{version};
938             }
939             else {
940             croak "Malformed RSS: invalid version\n";
941             }
942              
943             # handle xml:base
944             $self->{'xml:base'} = $attribs{'base'} if exists $attribs{'base'};
945              
946             # beginning of RSS 1.0 or RSS 0.9
947             }
948             elsif ($el eq 'RDF') {
949             my @prefixes = $parser->new_ns_prefixes;
950             foreach my $prefix (@prefixes) {
951             my $uri = $parser->expand_ns_prefix($prefix);
952             $self->{namespaces}->{$prefix} = $uri;
953              
954             #print "$prefix = $uri\n";
955             }
956              
957             # removed assumption that RSS is the default namespace - kellan, 11/5/02
958             #
959             foreach my $uri (values %{$self->{namespaces}}) {
960             if ($namespace_map->{'rss10'} eq $uri) {
961             $self->{_internal}->{version} = '1.0';
962             $self->{rss_namespace} = $uri;
963             last;
964             }
965             elsif ($namespace_map->{'rss09'} eq $uri) {
966             $self->{_internal}->{version} = '0.9';
967             $self->{rss_namespace} = $uri;
968             last;
969             }
970             }
971              
972             # failed to match a namespace
973             if (!defined($self->{_internal}->{version})) {
974             croak "Malformed RSS: invalid version\n";
975             }
976              
977             #if ($self->expand_ns_prefix('#default') =~ /\/1.0\//) {
978             # $self->{_internal}->{version} = '1.0';
979             #} elsif ($self->expand_ns_prefix('#default') =~ /\/0.9\//) {
980             # $self->{_internal}->{version} = '0.9';
981             #} else {
982             # croak "Malformed RSS: invalid version\n";
983             #}
984              
985             # handle xml:base
986             $self->{'xml:base'} = $attribs{'base'} if exists $attribs{'base'};
987              
988             # beginning of item element
989             }
990             elsif ($self->_start_array_element("skipHours", $el)) {
991             # Do nothing - already done in the predicate.
992             }
993             elsif ($self->_start_array_element("skipDays", $el)) {
994             # Do nothing - already done in the predicate.
995             }
996             elsif ($el eq 'cloud') {
997             if (keys %attribs) {
998             $self->{channel}{cloud} = \%attribs;
999             }
1000             }
1001             elsif ($el eq 'item') {
1002              
1003             # deal with trouble makers who use mod_content :)
1004              
1005             my ($ns, $verdict) = $self->_get_elem_namespace($el);
1006              
1007             if ($verdict) {
1008              
1009             # Sanity check to make sure we don't have nested elements that
1010             # can confuse the parser.
1011             if (!defined($self->{_inside_item_elem})) {
1012              
1013             # increment item count
1014             $self->{num_items}++;
1015             $self->{_inside_item_elem} = $parser->depth();
1016             }
1017             }
1018             # handle xml:base
1019             $self->_last_item->{'xml:base'} = $attribs{'base'} if exists $attribs{'base'};
1020              
1021              
1022             # guid element is a permanent link unless isPermaLink attribute is set to false
1023             }
1024             elsif ($el eq 'guid') {
1025             $self->_last_item->{'isPermaLink'} =
1026             (exists($attribs{'isPermaLink'}) &&
1027             (lc($attribs{'isPermaLink'}) eq 'true')
1028             );
1029              
1030             # beginning of taxo li element in item element
1031             #'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo'
1032             }
1033             elsif (
1034             $self->_current_element eq "item"
1035             && (($el eq "category") ||
1036             (
1037             exists($self->{modules}->{$el_ns})
1038             && ($self->{modules}->{$el_ns} eq "dc")
1039             )
1040             )
1041             ) {
1042             $self->_start_array_element_in_struct($self->_last_item, $el);
1043             }
1044             elsif (
1045             $parser->within_element(
1046             $parser->generate_ns_name("topics", 'http://purl.org/rss/1.0/modules/taxonomy/')
1047             )
1048             && $parser->within_element($parser->generate_ns_name("item", $namespace_map->{'rss10'}))
1049             && $self->_current_element eq 'Bag'
1050             && $el eq 'li'
1051             )
1052             {
1053              
1054             #print "taxo: ", $attribs{'resource'},"\n";
1055             push(@{$self->_last_item->{'taxo'}}, $attribs{'resource'});
1056             $self->{'modules'}->{'http://purl.org/rss/1.0/modules/taxonomy/'} = 'taxo';
1057              
1058             # beginning of taxo li in channel element
1059             }
1060             elsif (
1061             $parser->within_element(
1062             $parser->generate_ns_name("topics", 'http://purl.org/rss/1.0/modules/taxonomy/')
1063             )
1064             && $parser->within_element($parser->generate_ns_name("channel", $namespace_map->{'rss10'}))
1065             && $self->_current_element eq 'Bag'
1066             && $el eq 'li'
1067             )
1068             {
1069             push(@{$self->{'channel'}->{'taxo'}}, $attribs{'resource'});
1070             $self->{'modules'}->{'http://purl.org/rss/1.0/modules/taxonomy/'} = 'taxo';
1071             }
1072              
1073             # beginning of a channel element that stores its info in rdf:resource
1074             elsif ( $parser->namespace($el)
1075             && $self->_is_rdf_resource($el)
1076             && $self->_current_element eq 'channel')
1077             {
1078             my $ns = $parser->namespace($el);
1079              
1080             # Commented out by shlomif - the RSS namespaces are not present
1081             # in the 'rdf_resource_fields' so this condition always evaluates
1082             # to false.
1083             # if ( $ns eq $self->{rss_namespace} ) {
1084             # $self->{channel}->{$el} = $attribs{resource};
1085             # }
1086             # else
1087              
1088             {
1089             $self->{channel}->{$ns}->{$el} = $attribs{resource};
1090              
1091             # add short cut
1092             #
1093             if (exists($self->{modules}->{$ns})) {
1094             $ns = $self->{modules}->{$ns};
1095             $self->{channel}->{$ns}->{$el} = $attribs{resource};
1096             }
1097             }
1098             }
1099             # beginning of an item element that stores its info in rdf:resource
1100             elsif ( $parser->namespace($el)
1101             && $self->_is_rdf_resource($el)
1102             && $self->_current_element eq 'item')
1103             {
1104             my $ns = $parser->namespace($el);
1105              
1106             # Commented out by shlomif - the RSS namespaces are not present
1107             # in the 'rdf_resource_fields' so this condition always evaluates
1108             # to false.
1109             # if ( $ns eq $self->{rss_namespace} ) {
1110             # $self->_last_item->{ $el } = $attribs{resource};
1111             # }
1112             # else
1113             {
1114             $self->_last_item->{$ns}->{$el} = $attribs{resource};
1115              
1116             # add short cut
1117             #
1118             if (exists($self->{modules}->{$ns})) {
1119             $ns = $self->{modules}->{$ns};
1120             $self->_last_item->{$ns}->{$el} = $attribs{resource};
1121             }
1122             }
1123             }
1124             elsif ($self->_should_be_hashref($el) and $self->_current_element eq 'item') {
1125             if (defined $attribs{base}) {
1126             $attribs{'xml:base'} = delete $attribs{base};
1127             }
1128             if (keys(%attribs)) {
1129             if ($el_verdict) {
1130             $self->_last_item->{$el} =
1131             $self->_make_array($el, $self->_last_item->{$el}, \%attribs);
1132             }
1133             else {
1134             $self->_last_item->{$el_ns}->{$el} =
1135             $self->_make_array($el, $self->_last_item->{$el_ns}->{$el}, \%attribs);
1136              
1137             my $prefix = $self->{modules}->{$el_ns};
1138              
1139             if ($prefix) {
1140             $self->_last_item->{$prefix}->{$el} =
1141             $self->_make_array($el, $self->_last_item->{$prefix}->{$el}, \%attribs);
1142             }
1143             }
1144             }
1145             }
1146             elsif ($self->_start_array_element("image", $el)) {
1147             # Do nothing - already done in the predicate.
1148             }
1149             elsif (($el eq "category") &&
1150             (!$parser->within_element("item")) &&
1151             $self->_start_array_element("channel", $el)) {
1152             # Do nothing - already done in the predicate.
1153             }
1154             elsif (($self->_current_element eq 'channel') &&
1155             ($el_verdict))
1156             {
1157             # Make sure an opening tag signifies that the element has been
1158             # encountered.
1159             if ( exists($self->{'channel'}->{$el})
1160             && (!defined($self->{'channel'}->{$el})))
1161             {
1162             $self->{'channel'}->{$el} = "";
1163             }
1164             }
1165             }
1166              
1167             sub _make_array {
1168             my $self = shift;
1169             my $el = shift;
1170             my $old = shift;
1171             my $new = shift;
1172              
1173             if (!$self->_allow_multiple($el)) {
1174             return $new;
1175             }
1176              
1177             if (!defined $old) {
1178             $old = [];
1179             } elsif (ref($old) ne 'ARRAY') {
1180             $old = [$old];
1181             }
1182             push @$old, $new;
1183             return $old;
1184             }
1185              
1186             sub _allow_multiple {
1187             my $self = shift;
1188             my $el = shift;
1189              
1190             $self->{_allow_multiple} ||=
1191             {
1192             map { $_ => 1 }
1193             @{$self->_parse_options->{allow_multiple} || []}
1194             };
1195              
1196             return $self->{_allow_multiple}->{$el};
1197             }
1198              
1199             sub _handle_end {
1200             my ($self, $el) = @_;
1201              
1202             if (defined($self->{_inside_item_elem})
1203             && $self->{_inside_item_elem} == $self->_parser->depth())
1204             {
1205             delete($self->{_inside_item_elem});
1206             }
1207             }
1208              
1209             sub _auto_add_modules {
1210             my $self = shift;
1211              
1212             for my $ns (keys %{$self->{namespaces}}) {
1213              
1214             # skip default namespaces
1215             next
1216             if $ns eq "rdf"
1217             || $ns eq "#default"
1218             || exists $self->{modules}{$self->{namespaces}{$ns}};
1219             $self->add_module(prefix => $ns, uri => $self->{namespaces}{$ns});
1220             }
1221              
1222             $self;
1223             }
1224              
1225             sub _parser {
1226             my $self = shift;
1227              
1228             if (@_) {
1229             $self->{_parser} = shift;
1230             }
1231             return $self->{_parser};
1232             }
1233              
1234             sub _get_parser {
1235             my $self = shift;
1236              
1237             return XML::Parser->new(
1238             Namespaces => 1,
1239             NoExpand => 1,
1240             ParseParamEnt => 0,
1241             Handlers => {
1242             Char => sub {
1243             my ($parser, $cdata) = @_;
1244             $self->_parser($parser);
1245             $self->_handle_char($cdata);
1246             # Detach the parser to avoid reference loops.
1247             $self->_parser(undef);
1248             },
1249             XMLDecl => sub {
1250             my $parser = shift;
1251             $self->_parser($parser);
1252             $self->_handle_dec(@_);
1253             # Detach the parser to avoid reference loops.
1254             $self->_parser(undef);
1255             },
1256             Start => sub {
1257             my $parser = shift;
1258             $self->_parser($parser);
1259             $self->_handle_start(@_);
1260             # Detach the parser to avoid reference loops.
1261             $self->_parser(undef);
1262             },
1263             End => sub {
1264             my $parser = shift;
1265             $self->_parser($parser);
1266             $self->_handle_end(@_);
1267             # Detach the parser to avoid reference loops.
1268             $self->_parser(undef);
1269             },
1270             ExternEnt => sub {
1271             return '';
1272             },
1273             }
1274             );
1275             }
1276              
1277             sub _parse_options {
1278             my $self = shift;
1279              
1280             if (@_) {
1281             $self->{_parse_options} = shift;
1282             }
1283              
1284             return $self->{_parse_options};
1285             }
1286              
1287             sub _empty {}
1288              
1289             sub _generic_parse {
1290             my $self = shift;
1291             my $method = shift;
1292             my $arg = shift;
1293             my $options = shift;
1294              
1295             $self->_reset;
1296              
1297             $self->_parse_options($options || {});
1298              
1299             # Workaround to make sure that if we were defined with version => "2.0"
1300             # then we can still parse 1.0 and 0.9.x feeds correctly.
1301             if ($self->{version} eq "2.0") {
1302             $self->{modules} = +{%{$self->_get_default_modules()}, %{$self->{modules}}};
1303             }
1304              
1305             {
1306             my $parser = $self->_get_parser();
1307              
1308             eval {
1309             $parser->$method($arg);
1310             };
1311              
1312             if ($@)
1313             {
1314             my $err = $@;
1315              
1316             # Cleanup so perl-5.6.2 will be happy.
1317             $parser->setHandlers(
1318             map { ($_ => \&_empty) } (qw(Char XMLDecl Start End))
1319             );
1320             $self->_parser(0);
1321              
1322             undef($parser);
1323              
1324             die $err;
1325             }
1326             }
1327              
1328             $self->_auto_add_modules if $AUTO_ADD;
1329             $self->{version} = $self->{_internal}->{version};
1330              
1331             return $self;
1332             }
1333              
1334             sub parse {
1335             my $self = shift;
1336             my $text_to_parse = shift;
1337             my $options = shift;
1338              
1339             return $self->_generic_parse("parse", $text_to_parse, $options);
1340             }
1341              
1342             sub parsefile {
1343             my $self = shift;
1344             my $file_to_parse = shift;
1345             my $options = shift;
1346              
1347             return $self->_generic_parse("parsefile", $file_to_parse, $options);
1348             }
1349              
1350             sub _untaint {
1351             my $self = shift;
1352              
1353             my $value = shift;
1354              
1355             my ($untainted) = ($value =~ m{(.*)}s);
1356              
1357             return $untainted;
1358             }
1359              
1360             sub _get_save_output_mode {
1361             my $self = shift;
1362              
1363             return (">:encoding(" . $self->_untaint($self->_encoding()) . ")");
1364             }
1365              
1366             sub save {
1367             my ($self, $file) = @_;
1368              
1369             local (*OUT);
1370              
1371             open(OUT, $self->_get_save_output_mode(), "$file")
1372             or croak "Cannot open file $file for write: $!";
1373             print OUT $self->as_string;
1374             close OUT;
1375             }
1376              
1377             sub strict {
1378             my ($self, $value) = @_;
1379             $self->{'strict'} = $value;
1380             }
1381              
1382             sub _handle_accessor {
1383             my $self = shift;
1384             my $name = shift;
1385              
1386             my $type = ref($self);
1387              
1388             croak "Unregistered entity: Can't access $name field in object of class $type"
1389             unless (exists $self->{$name});
1390              
1391             # return reference to RSS structure
1392             if (@_ == 1) {
1393             return $self->{$name}->{$_[0]};
1394              
1395             # we're going to set values here
1396             }
1397             elsif (@_ > 1) {
1398             my %hash = @_;
1399             my $_REQ;
1400              
1401             # make sure we have required elements and correct lengths
1402             if ($self->{'strict'}) {
1403             ($self->{version} eq '0.9')
1404             ? ($_REQ = $_REQ_v0_9)
1405             : ($_REQ = $_REQ_v0_9_1);
1406             }
1407              
1408             # store data in object
1409             foreach my $key (keys(%hash)) {
1410             if ($self->{'strict'}) {
1411             my $req_element = $_REQ->{$name}->{$key};
1412             confess "$key cannot exceed " . $req_element->[1] . " characters in length"
1413             if defined $req_element->[1] && length($hash{$key}) > $req_element->[1];
1414             }
1415             $self->{$name}->{$key} = $hash{$key};
1416             }
1417              
1418             # return value
1419             return $self->{$name};
1420              
1421             # otherwise, just return a reference to the whole thing
1422             }
1423             else {
1424             return $self->{$name};
1425             }
1426              
1427             # make sure we have all required elements
1428             #foreach my $key (keys(%{$_REQ->{$name}})) {
1429             #my $element = $_REQ->{$name}->{$key};
1430             #croak "$key is required in $name"
1431             #if ($element->[0] == 1) && (!defined($hash{$key}));
1432             #croak "$key cannot exceed ".$element->[1]." characters in length"
1433             #unless length($hash{$key}) <= $element->[1];
1434             #}
1435             }
1436              
1437             sub _modules {
1438             my $self = shift;
1439             return $self->_handle_accessor("modules", @_);;
1440             }
1441              
1442             sub channel {
1443             my $self = shift;
1444              
1445             return $self->_handle_accessor("channel", @_);
1446             }
1447              
1448             sub image {
1449             my $self = shift;
1450              
1451             return $self->_handle_accessor("image", @_);
1452             }
1453              
1454             sub textinput {
1455             my $self = shift;
1456              
1457             return $self->_handle_accessor("textinput", @_);
1458             }
1459              
1460             sub skipDays {
1461             my $self = shift;
1462              
1463             return $self->_handle_accessor("skipDays", @_);
1464             }
1465              
1466             sub skipHours {
1467             my $self = shift;
1468              
1469             return $self->_handle_accessor("skipHours", @_);
1470             }
1471              
1472             ### Read only, scalar accessors
1473              
1474             sub _encode_output {
1475             my $self = shift;
1476              
1477             return $self->{'encode_output'};
1478             }
1479              
1480             sub _encoding {
1481             my $self = shift;
1482              
1483             return $self->{'encoding'};
1484             }
1485              
1486             sub _stylesheet {
1487             my $self = shift;
1488              
1489             return $self->{'stylesheet'};
1490             }
1491              
1492             sub _get_items {
1493             my $self = shift;
1494              
1495             return $self->{items};
1496             }
1497              
1498             1;
1499             __END__