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 36     36   348829 use strict;
  36         44  
  36         887  
4 36     36   108 use warnings;
  36         34  
  36         706  
5              
6 36     36   116 use Carp;
  36         38  
  36         2002  
7 36     36   20773 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.59';
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'})) || (lc($attribs{'isPermaLink'}) ne 'false') );
1027              
1028             # beginning of taxo li element in item element
1029             #'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo'
1030             }
1031             elsif (
1032             $self->_current_element eq "item"
1033             && (($el eq "category") ||
1034             (
1035             exists($self->{modules}->{$el_ns})
1036             && ($self->{modules}->{$el_ns} eq "dc")
1037             )
1038             )
1039             ) {
1040             $self->_start_array_element_in_struct($self->_last_item, $el);
1041             }
1042             elsif (
1043             $parser->within_element(
1044             $parser->generate_ns_name("topics", 'http://purl.org/rss/1.0/modules/taxonomy/')
1045             )
1046             && $parser->within_element($parser->generate_ns_name("item", $namespace_map->{'rss10'}))
1047             && $self->_current_element eq 'Bag'
1048             && $el eq 'li'
1049             )
1050             {
1051              
1052             #print "taxo: ", $attribs{'resource'},"\n";
1053             push(@{$self->_last_item->{'taxo'}}, $attribs{'resource'});
1054             $self->{'modules'}->{'http://purl.org/rss/1.0/modules/taxonomy/'} = 'taxo';
1055              
1056             # beginning of taxo li in channel element
1057             }
1058             elsif (
1059             $parser->within_element(
1060             $parser->generate_ns_name("topics", 'http://purl.org/rss/1.0/modules/taxonomy/')
1061             )
1062             && $parser->within_element($parser->generate_ns_name("channel", $namespace_map->{'rss10'}))
1063             && $self->_current_element eq 'Bag'
1064             && $el eq 'li'
1065             )
1066             {
1067             push(@{$self->{'channel'}->{'taxo'}}, $attribs{'resource'});
1068             $self->{'modules'}->{'http://purl.org/rss/1.0/modules/taxonomy/'} = 'taxo';
1069             }
1070              
1071             # beginning of a channel element that stores its info in rdf:resource
1072             elsif ( $parser->namespace($el)
1073             && $self->_is_rdf_resource($el)
1074             && $self->_current_element eq 'channel')
1075             {
1076             my $ns = $parser->namespace($el);
1077              
1078             # Commented out by shlomif - the RSS namespaces are not present
1079             # in the 'rdf_resource_fields' so this condition always evaluates
1080             # to false.
1081             # if ( $ns eq $self->{rss_namespace} ) {
1082             # $self->{channel}->{$el} = $attribs{resource};
1083             # }
1084             # else
1085              
1086             {
1087             $self->{channel}->{$ns}->{$el} = $attribs{resource};
1088              
1089             # add short cut
1090             #
1091             if (exists($self->{modules}->{$ns})) {
1092             $ns = $self->{modules}->{$ns};
1093             $self->{channel}->{$ns}->{$el} = $attribs{resource};
1094             }
1095             }
1096             }
1097             # beginning of an item element that stores its info in rdf:resource
1098             elsif ( $parser->namespace($el)
1099             && $self->_is_rdf_resource($el)
1100             && $self->_current_element eq 'item')
1101             {
1102             my $ns = $parser->namespace($el);
1103              
1104             # Commented out by shlomif - the RSS namespaces are not present
1105             # in the 'rdf_resource_fields' so this condition always evaluates
1106             # to false.
1107             # if ( $ns eq $self->{rss_namespace} ) {
1108             # $self->_last_item->{ $el } = $attribs{resource};
1109             # }
1110             # else
1111             {
1112             $self->_last_item->{$ns}->{$el} = $attribs{resource};
1113              
1114             # add short cut
1115             #
1116             if (exists($self->{modules}->{$ns})) {
1117             $ns = $self->{modules}->{$ns};
1118             $self->_last_item->{$ns}->{$el} = $attribs{resource};
1119             }
1120             }
1121             }
1122             elsif ($self->_should_be_hashref($el) and $self->_current_element eq 'item') {
1123             if (defined $attribs{base}) {
1124             $attribs{'xml:base'} = delete $attribs{base};
1125             }
1126             if (keys(%attribs)) {
1127             if ($el_verdict) {
1128             $self->_last_item->{$el} =
1129             $self->_make_array($el, $self->_last_item->{$el}, \%attribs);
1130             }
1131             else {
1132             $self->_last_item->{$el_ns}->{$el} =
1133             $self->_make_array($el, $self->_last_item->{$el_ns}->{$el}, \%attribs);
1134              
1135             my $prefix = $self->{modules}->{$el_ns};
1136              
1137             if ($prefix) {
1138             $self->_last_item->{$prefix}->{$el} =
1139             $self->_make_array($el, $self->_last_item->{$prefix}->{$el}, \%attribs);
1140             }
1141             }
1142             }
1143             }
1144             elsif ($self->_start_array_element("image", $el)) {
1145             # Do nothing - already done in the predicate.
1146             }
1147             elsif (($el eq "category") &&
1148             (!$parser->within_element("item")) &&
1149             $self->_start_array_element("channel", $el)) {
1150             # Do nothing - already done in the predicate.
1151             }
1152             elsif (($self->_current_element eq 'channel') &&
1153             ($el_verdict))
1154             {
1155             # Make sure an opening tag signifies that the element has been
1156             # encountered.
1157             if ( exists($self->{'channel'}->{$el})
1158             && (!defined($self->{'channel'}->{$el})))
1159             {
1160             $self->{'channel'}->{$el} = "";
1161             }
1162             }
1163             }
1164              
1165             sub _make_array {
1166             my $self = shift;
1167             my $el = shift;
1168             my $old = shift;
1169             my $new = shift;
1170              
1171             if (!$self->_allow_multiple($el)) {
1172             return $new;
1173             }
1174              
1175             if (!defined $old) {
1176             $old = [];
1177             } elsif (ref($old) ne 'ARRAY') {
1178             $old = [$old];
1179             }
1180             push @$old, $new;
1181             return $old;
1182             }
1183              
1184             sub _allow_multiple {
1185             my $self = shift;
1186             my $el = shift;
1187              
1188             $self->{_allow_multiple} ||=
1189             {
1190             map { $_ => 1 }
1191             @{$self->_parse_options->{allow_multiple} || []}
1192             };
1193              
1194             return $self->{_allow_multiple}->{$el};
1195             }
1196              
1197             sub _handle_end {
1198             my ($self, $el) = @_;
1199              
1200             if (defined($self->{_inside_item_elem})
1201             && $self->{_inside_item_elem} == $self->_parser->depth())
1202             {
1203             delete($self->{_inside_item_elem});
1204             }
1205             }
1206              
1207             sub _auto_add_modules {
1208             my $self = shift;
1209              
1210             for my $ns (keys %{$self->{namespaces}}) {
1211              
1212             # skip default namespaces
1213             next
1214             if $ns eq "rdf"
1215             || $ns eq "#default"
1216             || exists $self->{modules}{$self->{namespaces}{$ns}};
1217             $self->add_module(prefix => $ns, uri => $self->{namespaces}{$ns});
1218             }
1219              
1220             $self;
1221             }
1222              
1223             sub _parser {
1224             my $self = shift;
1225              
1226             if (@_) {
1227             $self->{_parser} = shift;
1228             }
1229             return $self->{_parser};
1230             }
1231              
1232             sub _get_parser {
1233             my $self = shift;
1234              
1235             return XML::Parser->new(
1236             Namespaces => 1,
1237             NoExpand => 1,
1238             ParseParamEnt => 0,
1239             Handlers => {
1240             Char => sub {
1241             my ($parser, $cdata) = @_;
1242             $self->_parser($parser);
1243             $self->_handle_char($cdata);
1244             # Detach the parser to avoid reference loops.
1245             $self->_parser(undef);
1246             },
1247             XMLDecl => sub {
1248             my $parser = shift;
1249             $self->_parser($parser);
1250             $self->_handle_dec(@_);
1251             # Detach the parser to avoid reference loops.
1252             $self->_parser(undef);
1253             },
1254             Start => sub {
1255             my $parser = shift;
1256             $self->_parser($parser);
1257             $self->_handle_start(@_);
1258             # Detach the parser to avoid reference loops.
1259             $self->_parser(undef);
1260             },
1261             End => sub {
1262             my $parser = shift;
1263             $self->_parser($parser);
1264             $self->_handle_end(@_);
1265             # Detach the parser to avoid reference loops.
1266             $self->_parser(undef);
1267             },
1268             ExternEnt => sub {
1269             return '';
1270             },
1271             }
1272             );
1273             }
1274              
1275             sub _parse_options {
1276             my $self = shift;
1277              
1278             if (@_) {
1279             $self->{_parse_options} = shift;
1280             }
1281              
1282             return $self->{_parse_options};
1283             }
1284              
1285             sub _empty {}
1286              
1287             sub _generic_parse {
1288             my $self = shift;
1289             my $method = shift;
1290             my $arg = shift;
1291             my $options = shift;
1292              
1293             $self->_reset;
1294              
1295             $self->_parse_options($options || {});
1296              
1297             # Workaround to make sure that if we were defined with version => "2.0"
1298             # then we can still parse 1.0 and 0.9.x feeds correctly.
1299             if ($self->{version} eq "2.0") {
1300             $self->{modules} = +{%{$self->_get_default_modules()}, %{$self->{modules}}};
1301             }
1302              
1303             {
1304             my $parser = $self->_get_parser();
1305              
1306             eval {
1307             $parser->$method($arg);
1308             };
1309              
1310             if ($@)
1311             {
1312             my $err = $@;
1313              
1314             # Cleanup so perl-5.6.2 will be happy.
1315             $parser->setHandlers(
1316             map { ($_ => \&_empty) } (qw(Char XMLDecl Start End))
1317             );
1318             $self->_parser(0);
1319              
1320             undef($parser);
1321              
1322             die $err;
1323             }
1324             }
1325              
1326             $self->_auto_add_modules if $AUTO_ADD;
1327             $self->{version} = $self->{_internal}->{version};
1328              
1329             return $self;
1330             }
1331              
1332             sub parse {
1333             my $self = shift;
1334             my $text_to_parse = shift;
1335             my $options = shift;
1336              
1337             return $self->_generic_parse("parse", $text_to_parse, $options);
1338             }
1339              
1340             sub parsefile {
1341             my $self = shift;
1342             my $file_to_parse = shift;
1343             my $options = shift;
1344              
1345             return $self->_generic_parse("parsefile", $file_to_parse, $options);
1346             }
1347              
1348             sub _untaint {
1349             my $self = shift;
1350              
1351             my $value = shift;
1352              
1353             my ($untainted) = ($value =~ m{(.*)}s);
1354              
1355             return $untainted;
1356             }
1357              
1358             sub _get_save_output_mode {
1359             my $self = shift;
1360              
1361             return (">:encoding(" . $self->_untaint($self->_encoding()) . ")");
1362             }
1363              
1364             sub save {
1365             my ($self, $file) = @_;
1366              
1367             local (*OUT);
1368              
1369             open(OUT, $self->_get_save_output_mode(), "$file")
1370             or croak "Cannot open file $file for write: $!";
1371             print OUT $self->as_string;
1372             close OUT;
1373             }
1374              
1375             sub strict {
1376             my ($self, $value) = @_;
1377             $self->{'strict'} = $value;
1378             }
1379              
1380             sub _handle_accessor {
1381             my $self = shift;
1382             my $name = shift;
1383              
1384             my $type = ref($self);
1385              
1386             croak "Unregistered entity: Can't access $name field in object of class $type"
1387             unless (exists $self->{$name});
1388              
1389             # return reference to RSS structure
1390             if (@_ == 1) {
1391             return $self->{$name}->{$_[0]};
1392              
1393             # we're going to set values here
1394             }
1395             elsif (@_ > 1) {
1396             my %hash = @_;
1397             my $_REQ;
1398              
1399             # make sure we have required elements and correct lengths
1400             if ($self->{'strict'}) {
1401             ($self->{version} eq '0.9')
1402             ? ($_REQ = $_REQ_v0_9)
1403             : ($_REQ = $_REQ_v0_9_1);
1404             }
1405              
1406             # store data in object
1407             foreach my $key (keys(%hash)) {
1408             if ($self->{'strict'}) {
1409             my $req_element = $_REQ->{$name}->{$key};
1410             confess "$key cannot exceed " . $req_element->[1] . " characters in length"
1411             if defined $req_element->[1] && length($hash{$key}) > $req_element->[1];
1412             }
1413             $self->{$name}->{$key} = $hash{$key};
1414             }
1415              
1416             # return value
1417             return $self->{$name};
1418              
1419             # otherwise, just return a reference to the whole thing
1420             }
1421             else {
1422             return $self->{$name};
1423             }
1424              
1425             # make sure we have all required elements
1426             #foreach my $key (keys(%{$_REQ->{$name}})) {
1427             #my $element = $_REQ->{$name}->{$key};
1428             #croak "$key is required in $name"
1429             #if ($element->[0] == 1) && (!defined($hash{$key}));
1430             #croak "$key cannot exceed ".$element->[1]." characters in length"
1431             #unless length($hash{$key}) <= $element->[1];
1432             #}
1433             }
1434              
1435             sub _modules {
1436             my $self = shift;
1437             return $self->_handle_accessor("modules", @_);;
1438             }
1439              
1440             sub channel {
1441             my $self = shift;
1442              
1443             return $self->_handle_accessor("channel", @_);
1444             }
1445              
1446             sub image {
1447             my $self = shift;
1448              
1449             return $self->_handle_accessor("image", @_);
1450             }
1451              
1452             sub textinput {
1453             my $self = shift;
1454              
1455             return $self->_handle_accessor("textinput", @_);
1456             }
1457              
1458             sub skipDays {
1459             my $self = shift;
1460              
1461             return $self->_handle_accessor("skipDays", @_);
1462             }
1463              
1464             sub skipHours {
1465             my $self = shift;
1466              
1467             return $self->_handle_accessor("skipHours", @_);
1468             }
1469              
1470             ### Read only, scalar accessors
1471              
1472             sub _encode_output {
1473             my $self = shift;
1474              
1475             return $self->{'encode_output'};
1476             }
1477              
1478             sub _encoding {
1479             my $self = shift;
1480              
1481             return $self->{'encoding'};
1482             }
1483              
1484             sub _stylesheet {
1485             my $self = shift;
1486              
1487             return $self->{'stylesheet'};
1488             }
1489              
1490             sub _get_items {
1491             my $self = shift;
1492              
1493             return $self->{items};
1494             }
1495              
1496             1;
1497             __END__