package XML::RSS::Creator; use strict; use Carp; use vars qw($VERSION $AUTOLOAD @ISA); $VERSION = '1.06'; my %v0_9_ok_fields = ( channel => { title => '', description => '', link => '', }, image => { title => '', url => '', link => '' }, textinput => { title => '', description => '', name => '', link => '' }, items => [], num_items => 0, version => '', encoding => '' ); my %v0_9_1_ok_fields = ( channel => { title => '', copyright => '', description => '', docs => '', language => '', lastBuildDate => '', link => '', managingEditor => '', pubDate => '', rating => '', webMaster => '' }, image => { title => '', url => '', link => '', width => '', height => '', description => '' }, skipDays => { day => '' }, skipHours => { hour => '' }, textinput => { title => '', description => '', name => '', link => '' }, items => [], num_items => 0, version => '', encoding => '', category => '' ); my %v1_0_ok_fields = ( channel => { title => '', description => '', link => '', }, image => { title => '', url => '', link => '' }, textinput => { title => '', description => '', name => '', link => '' }, skipDays => { day => '' }, skipHours => { hour => '' }, items => [], num_items => 0, version => '', encoding => '', output => '', ); my %v2_0_ok_fields = ( channel => { title => '', link => '', description => '', language => '', copyright => '', managingEditor => '', webMaster => '', pubDate => '', lastBuildDate => '', category => '', generator => '', docs => '', cloud => '', ttl => '', image => '', textinput => '', skipHours => '', skipDays => '', }, image => { title => '', url => '', link => '', width => '', height => '', description => '' }, skipDays => { day => '' }, skipHours => { hour => '' }, textinput => { title => '', description => '', name => '', link => '' }, items => [], num_items => 0, version => '', encoding => '', category => '', cloud => '', ttl => '' ); my %languages = ( 'af' => 'Afrikaans', 'sq' => 'Albanian', 'eu' => 'Basque', 'be' => 'Belarusian', 'bg' => 'Bulgarian', 'ca' => 'Catalan', 'zh-cn' => 'Chinese (Simplified)', 'zh-tw' => 'Chinese (Traditional)', 'hr' => 'Croatian', 'cs' => 'Czech', 'da' => 'Danish', 'nl' => 'Dutch', 'nl-be' => 'Dutch (Belgium)', 'nl-nl' => 'Dutch (Netherlands)', 'en' => 'English', 'en-au' => 'English (Australia)', 'en-bz' => 'English (Belize)', 'en-ca' => 'English (Canada)', 'en-ie' => 'English (Ireland)', 'en-jm' => 'English (Jamaica)', 'en-nz' => 'English (New Zealand)', 'en-ph' => 'English (Phillipines)', 'en-za' => 'English (South Africa)', 'en-tt' => 'English (Trinidad)', 'en-gb' => 'English (United Kingdom)', 'en-us' => 'English (United States)', 'en-zw' => 'English (Zimbabwe)', 'fo' => 'Faeroese', 'fi' => 'Finnish', 'fr' => 'French', 'fr-be' => 'French (Belgium)', 'fr-ca' => 'French (Canada)', 'fr-fr' => 'French (France)', 'fr-lu' => 'French (Luxembourg)', 'fr-mc' => 'French (Monaco)', 'fr-ch' => 'French (Switzerland)', 'gl' => 'Galician', 'gd' => 'Gaelic', 'de' => 'German', 'de-at' => 'German (Austria)', 'de-de' => 'German (Germany)', 'de-li' => 'German (Liechtenstein)', 'de-lu' => 'German (Luxembourg)', 'el' => 'Greek', 'hu' => 'Hungarian', 'is' => 'Icelandic', 'in' => 'Indonesian', 'ga' => 'Irish', 'it' => 'Italian', 'it-it' => 'Italian (Italy)', 'it-ch' => 'Italian (Switzerland)', 'ja' => 'Japanese', 'ko' => 'Korean', 'mk' => 'Macedonian', 'no' => 'Norwegian', 'pl' => 'Polish', 'pt' => 'Portuguese', 'pt-br' => 'Portuguese (Brazil)', 'pt-pt' => 'Portuguese (Portugal)', 'ro' => 'Romanian', 'ro-mo' => 'Romanian (Moldova)', 'ro-ro' => 'Romanian (Romania)', 'ru' => 'Russian', 'ru-mo' => 'Russian (Moldova)', 'ru-ru' => 'Russian (Russia)', 'sr' => 'Serbian', 'sk' => 'Slovak', 'sl' => 'Slovenian', 'es' => 'Spanish', 'es-ar' => 'Spanish (Argentina)', 'es-bo' => 'Spanish (Bolivia)', 'es-cl' => 'Spanish (Chile)', 'es-co' => 'Spanish (Colombia)', 'es-cr' => 'Spanish (Costa Rica)', 'es-do' => 'Spanish (Dominican Republic)', 'es-ec' => 'Spanish (Ecuador)', 'es-sv' => 'Spanish (El Salvador)', 'es-gt' => 'Spanish (Guatemala)', 'es-hn' => 'Spanish (Honduras)', 'es-mx' => 'Spanish (Mexico)', 'es-ni' => 'Spanish (Nicaragua)', 'es-pa' => 'Spanish (Panama)', 'es-py' => 'Spanish (Paraguay)', 'es-pe' => 'Spanish (Peru)', 'es-pr' => 'Spanish (Puerto Rico)', 'es-es' => 'Spanish (Spain)', 'es-uy' => 'Spanish (Uruguay)', 'es-ve' => 'Spanish (Venezuela)', 'sv' => 'Swedish', 'sv-fi' => 'Swedish (Finland)', 'sv-se' => 'Swedish (Sweden)', 'tr' => 'Turkish', 'uk' => 'Ukranian' ); # define required elements for RSS 0.9 my $_REQ_v0_9 = { channel => { title => [1,40], description => [1,500], link => [1,500] }, image => { title => [1,40], url => [1,500], link => [1,500] }, item => { title => [1,100], link => [1,500] }, textinput => { title => [1,40], description => [1,100], name => [1,500], link => [1,500] } }; # define required elements for RSS 0.91 my $_REQ_v0_9_1 = { channel => { title => [1,100], description => [1,500], link => [1,500], language => [1,5], rating => [0,500], copyright => [0,100], pubDate => [0,100], lastBuildDate => [0,100], docs => [0,500], managingEditor => [0,100], webMaster => [0,100], }, image => { title => [1,100], url => [1,500], link => [0,500], width => [0,144], height => [0,400], description => [0,500] }, item => { title => [1,100], link => [1,500], description => [0,500] }, textinput => { title => [1,100], description => [1,500], name => [1,20], link => [1,500] }, skipHours => { hour => [1,23] }, skipDays => { day => [1,10] } }; # define required elements for RSS 2.0 my $_REQ_v2_0 = { channel => { title => [1,100], description => [1,500], link => [1,500], language => [0,5], rating => [0,500], copyright => [0,100], pubDate => [0,100], lastBuildDate => [0,100], docs => [0,500], managingEditor => [0,100], webMaster => [0,100], }, image => { title => [1,100], url => [1,500], link => [0,500], width => [0,144], height => [0,400], description => [0,500] }, item => { title => [1,100], link => [1,500], description => [0,500] }, textinput => { title => [1,100], description => [1,500], name => [1,20], link => [1,500] }, skipHours => { hour => [1,23] }, skipDays => { day => [1,10] } }; my $modules = { 'http://purl.org/rss/1.0/modules/syndication/' => 'syn', 'http://purl.org/dc/elements/1.1/' => 'dc', 'http://purl.org/rss/1.0/modules/taxonomy/' => 'taxo', 'http://webns.net/mvcb/' => 'admin' }; my %syn_ok_fields = ( updateBase => '', updateFrequency => '', updatePeriod => '', ); my %dc_ok_fields = ( title => '', creator => '', subject => '', description => '', publisher => '', contributor => '', date => '', type => '', format => '', identifier => '', source => '', language => '', relation => '', coverage => '', rights => '', ); my %rdf_resource_fields = ( 'http://webns.net/mvcb/' => { generatorAgent => 1, errorReportsTo => 1 }, 'http://purl.org/rss/1.0/modules/annotate/' => { reference => 1 }, 'http://my.theinfo.org/changed/1.0/rss/' => { server => 1 } ); sub new { my $class = shift; my $self={}; bless $self, $class; $self->_initialize(@_); return $self; } sub _initialize { my $self = shift; my %hash = @_; # internal hash $self->{_internal} = {}; # init num of items to 0 $self->{num_items} = 0; # adhere to Netscape limits; no by default $self->{'strict'} = 0; # initialize items $self->{items} = []; # namespaces $self->{namespaces} = {}; $self->{rss_namespace} = ''; # modules $self->{modules} = $modules; # encode output from as_string? (exists($hash{encode_output})) ? ($self->{encode_output} = $hash{encode_output}) : ($self->{encode_output} = 1); #get version info (exists($hash{version})) ? ($self->{version} = $hash{version}) : ($self->{version} = '1.0'); # set default output (exists($hash{output})) ? ($self->{output} = $hash{output}) : ($self->{output} = ""); # encoding (exists($hash{encoding})) ? ($self->{encoding} = $hash{encoding}) : ($self->{encoding} = 'UTF-8'); # initialize RSS data structure # RSS version 0.9 if ($self->{version} eq '0.9') { # Copy the hashes instead of using them directly to avoid # problems with multiple XML::RSS objects being used concurrently foreach my $i (qw(channel image textinput)) { my %template=%{$v0_9_ok_fields{$i}}; $self->{$i} = \%template; } # RSS version 0.91 } elsif ($self->{version} eq '0.91') { foreach my $i (qw(channel image textinput skipDays skipHours)) { my %template=%{$v0_9_1_ok_fields{$i}}; $self->{$i} = \%template; } # RSS version 2.0 } elsif ($self->{version} eq '2.0') { $self->{namespaces}->{'blogChannel'} = "http://backend.userland.com/blogChannelModule"; foreach my $i (qw(channel image textinput skipDays skipHours)) { my %template=%{ $v2_0_ok_fields{$i} }; $self->{$i} = \%template; } # RSS version 1.0 #} elsif ($self->{version} eq '1.0') { } else { foreach my $i (qw(channel image textinput)) { #foreach my $i (keys(%v1_0_ok_fields)) { my %template=%{$v1_0_ok_fields{$i}}; $self->{$i} = \%template; } } } sub _auto_add_modules { my $self = shift; for my $ns (keys %{$self->{namespaces}}) { # skip default namespaces next if $ns eq "rdf" || $ns eq "#default" || exists $self->{modules}{ $self->{namespaces}{$ns} }; $self->add_module(prefix => $ns, uri => $self->{namespaces}{$ns}) } $self; } sub add_module { my $self = shift; my $hash = {@_}; $hash->{prefix} =~ /^[a-z_][a-z0-9.-_]*$/ or croak "a namespace prefix should look like [a-z_][a-z0-9.-_]*"; $hash->{uri} or croak "a URI must be provided in a namespace declaration"; $self->{modules}->{$hash->{uri}} = $hash->{prefix}; } sub add_item { my $self = shift; my $hash = {@_}; # strict Netscape Netcenter length checks if ($self->{'strict'}) { # make sure we have a title and link croak "title and link elements are required" unless ($hash->{title} && $hash->{'link'}); # check string lengths croak "title cannot exceed 100 characters in length" if (length($hash->{title}) > 100); croak "link cannot exceed 500 characters in length" if (length($hash->{'link'}) > 500); croak "description cannot exceed 500 characters in length" if (exists($hash->{description}) && length($hash->{description}) > 500); # make sure there aren't already 15 items croak "total items cannot exceed 15 " if (@{$self->{items}} >= 15); } # add the item to the list if (defined($hash->{mode}) && $hash->{mode} eq 'insert') { unshift (@{$self->{items}}, $hash); } else { push (@{$self->{items}}, $hash); } # return reference to the list of items return $self->{items}; } sub as_rss_0_9 { my $self = shift; my $output; # XML declaration my $encoding = exists $$self{encoding} ? qq| encoding="$$self{encoding}"| : ''; $output .= qq|\n\n|; # RDF root element $output .= 'encode($self->{channel}->{title}) .''."\n"; $output .= ''. $self->encode($self->{channel}->{'link'}) .''."\n"; $output .= ''. $self->encode($self->{channel}->{description}) .''."\n"; $output .= ''."\n\n"; ################# # image element # ################# if ($self->{image}->{url}) { $output .= ''."\n"; # title $output .= ''. $self->encode($self->{image}->{title}) .''."\n"; # url $output .= ''. $self->encode($self->{image}->{url}) .''."\n"; # link $output .= ''. $self->encode($self->{image}->{'link'}) .''."\n" if $self->{image}->{link}; # end image element $output .= ''."\n\n"; } ################ # item element # ################ foreach my $item (@{$self->{items}}) { if ($item->{title}) { $output .= ''."\n"; $output .= ''. $self->encode($item->{title}) .''."\n"; $output .= ''. $self->encode($item->{'link'}) .''."\n"; # end image element $output .= ''."\n\n"; } } ##################### # textinput element # ##################### if ($self->{textinput}->{'link'}) { $output .= ''."\n"; $output .= ''. $self->encode($self->{textinput}->{title}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{description}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{name}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{'link'}) .''."\n"; $output .= ''."\n\n"; } $output .= ''; return $output; } sub as_rss_0_9_1 { my $self = shift; my $output; # XML declaration $output .= '{encoding}.'"?>'."\n\n"; # DOCTYPE $output .= ''."\n\n"; # RSS root element $output .= ''."\n\n"; ################### # Channel Element # ################### $output .= ''."\n"; $output .= ''. $self->encode($self->{channel}->{title}) .''."\n"; $output .= ''. $self->encode($self->{channel}->{'link'}) .''."\n"; $output .= ''. $self->encode($self->{channel}->{description}) .''."\n"; # language if ($self->{channel}->{'dc'}->{'language'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'language'}) .''."\n"; } elsif ($self->{channel}->{language}) { $output .= ''. $self->encode($self->{channel}->{language}).''."\n"; } # PICS rating $output .= ''. $self->encode($self->{channel}->{rating}) .''."\n" if $self->{channel}->{rating}; # copyright if ($self->{channel}->{'dc'}->{'rights'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'rights'}) .''."\n"; } elsif ($self->{channel}->{copyright}) { $output .= ''. $self->encode($self->{channel}->{copyright}) .''."\n"; } # publication date if ($self->{channel}->{pubDate}) { $output .= ''. $self->encode($self->{channel}->{pubDate}) .''."\n"; } elsif ($self->{channel}->{'dc'}->{'date'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'date'}) .''."\n"; } # last build date if ($self->{channel}->{lastBuildDate}) { $output .= ''. $self->encode($self->{channel}->{lastBuildDate}) .''."\n"; } elsif ($self->{channel}->{'dc'}->{'date'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'date'}) .''."\n"; } # external CDF URL $output .= ''. $self->encode($self->{channel}->{docs}) .''."\n" if $self->{channel}->{docs}; # managing editor if ($self->{channel}->{'dc'}->{'publisher'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'publisher'}) .''."\n"; } elsif ($self->{channel}->{managingEditor}) { $output .= ''. $self->encode($self->{channel}->{managingEditor}) .''."\n"; } # webmaster if ($self->{channel}->{'dc'}->{'creator'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'creator'}) .''."\n"; } elsif ($self->{channel}->{webMaster}) { $output .= ''. $self->encode($self->{channel}->{webMaster}) .''."\n"; } $output .= "\n"; ################# # image element # ################# if ($self->{image}->{url}) { $output .= ''."\n"; # title $output .= ''. $self->encode($self->{image}->{title}) .''."\n"; # url $output .= ''. $self->encode($self->{image}->{url}) .''."\n"; # link $output .= ''. $self->encode($self->{image}->{'link'}) .''."\n" if $self->{image}->{link}; # image width $output .= ''. $self->encode($self->{image}->{width}) .''."\n" if $self->{image}->{width}; # image height $output .= ''. $self->encode($self->{image}->{height}) .''."\n" if $self->{image}->{height}; # description $output .= ''. $self->encode($self->{image}->{description}) .''."\n" if $self->{image}->{description}; # end image element $output .= ''."\n\n"; } ################ # item element # ################ foreach my $item (@{$self->{items}}) { if ($item->{title}) { $output .= ''."\n"; $output .= ''. $self->encode($item->{title}) .''."\n"; $output .= ''. $self->encode($item->{'link'}) .''."\n"; $output .= ''. $self->encode($item->{description}) .''."\n" if $item->{description}; # end image element $output .= ''."\n\n"; } } ##################### # textinput element # ##################### if ($self->{textinput}->{'link'}) { $output .= ''."\n"; $output .= ''. $self->encode($self->{textinput}->{title}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{description}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{name}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{'link'}) .''."\n"; $output .= ''."\n\n"; } ##################### # skipHours element # ##################### if ($self->{skipHours}->{hour}) { $output .= ''."\n"; $output .= ''. $self->encode($self->{skipHours}->{hour}) .''."\n"; $output .= ''."\n\n"; } #################### # skipDays element # #################### if ($self->{skipDays}->{day}) { $output .= ''."\n"; $output .= ''. $self->encode($self->{skipDays}->{day}) .''."\n"; $output .= ''."\n\n"; } # end channel element $output .= ''."\n"; $output .= ''; return $output; } sub as_rss_1_0 { my $self = shift; my $output; # XML declaration $output .= '{encoding}.'"?>'."\n\n"; # RDF namespaces declaration $output .="{modules}}) { $output.=" xmlns:$v=\"$k\"\n"; } $output .=">"."\n\n"; ################### # Channel Element # ################### unless ( defined($self->{channel}->{'about'}) ) { $output .= ''."\n"; } else { $output .= ''."\n"; } # title $output .= ''. $self->encode($self->{channel}->{title}) .''."\n"; # link $output .= ''. $self->encode($self->{channel}->{'link'}) .''."\n"; # description $output .= ''. $self->encode($self->{channel}->{description}) .''."\n"; # additional elements for RSS 0.91 # language if ($self->{channel}->{'dc'}->{'language'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'language'}) .''."\n"; } elsif ($self->{channel}->{language}) { $output .= ''. $self->encode($self->{channel}->{language}) .''."\n"; } # PICS rating - Dublin Core has not decided how to incorporate PICS ratings yet #$$output .= ''.$self->{channel}->{rating}.''."\n" #$if $self->{channel}->{rating}; # copyright if ($self->{channel}->{'dc'}->{'rights'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'rights'}) .''."\n"; } elsif ($self->{channel}->{copyright}) { $output .= ''. $self->encode($self->{channel}->{copyright}) .''."\n"; } # publication date if ($self->{channel}->{'dc'}->{'date'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'date'}) .''."\n"; } elsif ($self->{channel}->{pubDate}) { $output .= ''. $self->encode($self->{channel}->{pubDate}) .''."\n"; } elsif ($self->{channel}->{lastBuildDate}) { $output .= ''. $self->encode($self->{channel}->{lastBuildDate}) .''."\n"; } # external CDF URL #$output .= ''.$self->{channel}->{docs}.''."\n" #if $self->{channel}->{docs}; # managing editor if ($self->{channel}->{'dc'}->{'publisher'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'publisher'}) .''."\n"; } elsif ($self->{channel}->{managingEditor}) { $output .= ''. $self->encode($self->{channel}->{managingEditor}) .''."\n"; } # webmaster if ($self->{channel}->{'dc'}->{'creator'}) { $output .= ''. $self->encode($self->{channel}->{'dc'}->{'creator'}) .''."\n"; } elsif ($self->{channel}->{webMaster}) { $output .= ''. $self->encode($self->{channel}->{webMaster}) .''."\n"; } # Dublin Core module foreach my $dc ( keys %dc_ok_fields ) { next if ($dc eq 'language' || $dc eq 'creator' || $dc eq 'publisher' || $dc eq 'rights' || $dc eq 'date'); $self->{channel}->{dc}->{$dc} and $output .= "". $self->encode($self->{channel}->{dc}->{$dc}) ."\n"; } # Syndication module foreach my $syn ( keys %syn_ok_fields ) { $self->{channel}->{syn}->{$syn} and $output .= "". $self->encode($self->{channel}->{syn}->{$syn}) ."\n"; } # Taxonomy module if (exists($self->{'channel'}->{'taxo'}) && $self->{'channel'}->{'taxo'}) { $output .= "\n \n"; foreach my $taxo (@{$self->{'channel'}->{'taxo'}}) { $output.= " encode($taxo) . "\" />\n"; } $output .= " \n\n"; } # Ad-hoc modules while ( my($url, $prefix) = each %{$self->{modules}} ) { next if $prefix =~ /^(dc|syn|taxo)$/; while ( my($el, $value) = each %{$self->{channel}->{$prefix}} ) { if ( exists( $rdf_resource_fields{ $url } ) and exists( $rdf_resource_fields{ $url }{ $el }) ) { $output .= qq!<$prefix:$el rdf:resource="! . $self->encode($value) . qq!" />\n!; } else { $output .= "<$prefix:$el>". $self->encode($value) ."\n"; } } } # Seq items $output .= "\n \n"; foreach my $item (@{$self->{items}}) { my $about = ( defined($item->{'about'}) ) ? $item->{'about'} : $item->{'link'}; $output .= ' '."\n"; } $output .= " \n\n"; $self->{image}->{url} and $output .= ''."\n"; $self->{textinput}->{'link'} and $output .= ''."\n"; # end channel element $output .= ''."\n\n"; ################# # image element # ################# if ($self->{image}->{url}) { $output .= ''."\n"; # title $output .= ''. $self->encode($self->{image}->{title}) .''."\n"; # url $output .= ''. $self->encode($self->{image}->{url}) .''."\n"; # link $output .= ''. $self->encode($self->{image}->{'link'}) .''."\n" if $self->{image}->{link}; # image width #$output .= ''.$self->{image}->{width}.''."\n" # if $self->{image}->{width}; # image height #$output .= ''.$self->{image}->{height}.''."\n" # if $self->{image}->{height}; # description #$output .= ''.$self->{image}->{description}.''."\n" # if $self->{image}->{description}; # Dublin Core Modules foreach my $dc ( keys %dc_ok_fields ) { $self->{image}->{dc}->{$dc} and $output .= "". $self->encode($self->{image}->{dc}->{$dc}) ."\n"; } # Ad-hoc modules for images while ( my($url, $prefix) = each %{$self->{modules}} ) { next if $prefix =~ /^(dc|syn|taxo)$/; while ( my($el, $value) = each %{$self->{image}->{$prefix}} ) { if ( exists( $rdf_resource_fields{ $url } ) and exists( $rdf_resource_fields{ $url }{ $el }) ) { $output .= qq!<$prefix:$el rdf:resource="! . $self->encode($value) . qq!" />\n!; } else { $output .= "<$prefix:$el>". $self->encode($value) ."\n"; } } } # end image element $output .= ''."\n\n"; } # end if ($self->{image}->{url}) { ################ # item element # ################ foreach my $item (@{$self->{items}}) { if ($item->{title}) { my $about = ( defined($item->{'about'}) ) ? $item->{'about'} : $item->{'link'}; $output .= 'encode($item->{title}) .''."\n"; $output .= ''. $self->encode($item->{'link'}) .''."\n"; $item->{description} and $output .= ''. $self->encode($item->{description}) .''."\n"; # Dublin Core module foreach my $dc ( keys %dc_ok_fields ) { $item->{dc}->{$dc} and $output .= "". $self->encode($item->{dc}->{$dc}) ."\n"; } # Taxonomy module if (exists($item->{'taxo'}) && $item->{'taxo'}) { $output .= "\n \n"; foreach my $taxo (@{$item->{'taxo'}}) { $output.= " \n"; } $output .= " \n\n"; } # Ad-hoc modules while ( my($url, $prefix) = each %{$self->{modules}} ) { next if $prefix =~ /^(dc|syn|taxo)$/; while ( my($el, $value) = each %{$item->{$prefix}} ) { if ( exists( $rdf_resource_fields{ $url } ) and exists( $rdf_resource_fields{ $url }{ $el }) ) { $output .= qq!<$prefix:$el rdf:resource="! . $self->encode($value) . qq!" />\n!; } else { $output .= "<$prefix:$el>". $self->encode($value) ."\n"; } } } # end item element $output .= ''."\n\n"; } } # end foreach my $item (@{$self->{items}}) { ##################### # textinput element # ##################### if ($self->{textinput}->{'link'}) { $output .= ''."\n"; $output .= ''. $self->encode($self->{textinput}->{title}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{description}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{name}) .''."\n"; $output .= ''. $self->encode($self->{textinput}->{'link'}) .''."\n"; # Dublin Core module foreach my $dc ( keys %dc_ok_fields ) { $self->{textinput}->{dc}->{$dc} and $output .= "". $self->encode($self->{textinput}->{dc}->{$dc}) ."\n"; } # Ad-hoc modules while ( my($url, $prefix) = each %{$self->{modules}} ) { next if $prefix =~ /^(dc|syn|taxo)$/; while ( my($el, $value) = each %{$self->{textinput}->{$prefix}} ) { $output .= "<$prefix:$el>". $self->encode($value) ."\n"; } } $output .= ''."\n\n"; } $output .= ''; } sub as_rss_2_0 { my $self = shift; my $output; # XML declaration $output .= '{encoding}.'"?>'."\n\n"; # DOCTYPE # $output .= ''."\n\n"; # RSS root element # $output .= ''."\n\n"; $output .= '' . "\n\n"; ################### # Channel Element # ################### $output .= ''."\n"; $output .= ''.$self->encode($self->{channel}->{title}).''."\n"; $output .= ''.$self->encode($self->{channel}->{'link'}).''."\n"; $output .= ''.$self->encode($self->{channel}->{description}).''."\n"; # language if ($self->{channel}->{'dc'}->{'language'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'language'}).''."\n"; } elsif ($self->{channel}->{language}) { $output .= ''.$self->encode($self->{channel}->{language}).''."\n"; } # PICS rating # Not supported by RSS 2.0 # $output .= ''.$self->{channel}->{rating}.''."\n" # if $self->{channel}->{rating}; # copyright if ($self->{channel}->{'dc'}->{'rights'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'rights'}).''."\n"; } elsif ($self->{channel}->{copyright}) { $output .= ''.$self->encode($self->{channel}->{copyright}).''."\n"; } # publication date if ($self->{channel}->{pubDate}) { $output .= ''.$self->encode($self->{channel}->{pubDate}).''."\n"; } elsif ($self->{channel}->{'dc'}->{'date'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'date'}).''."\n"; } # last build date if ($self->{channel}->{'dc'}->{'date'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{lastBuildDate}).''."\n"; } elsif ($self->{channel}->{lastBuildDate}) { $output .= ''.$self->encode($self->{channel}->{lastBuildDate}).''."\n"; } # external CDF URL $output .= ''.$self->encode($self->{channel}->{docs}).''."\n" if $self->{channel}->{docs}; # managing editor if ($self->{channel}->{'dc'}->{'publisher'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'publisher'}).''."\n"; } elsif ($self->{channel}->{managingEditor}) { $output .= ''.$self->encode($self->{channel}->{managingEditor}).''."\n"; } # webmaster if ($self->{channel}->{'dc'}->{'creator'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'creator'}).''."\n"; } elsif ($self->{channel}->{webMaster}) { $output .= ''.$self->encode($self->{channel}->{webMaster}).''."\n"; } # category if ($self->{channel}->{'dc'}->{'category'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'category'}).''."\n"; } elsif ($self->{channel}->{category}) { $output .= ''.$self->encode($self->{channel}->{generator}).''."\n"; } # generator if ($self->{channel}->{'dc'}->{'generator'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'generator'}).''."\n"; } elsif ($self->{channel}->{generator}) { $output .= ''.$self->encode($self->{channel}->{generator}).''."\n"; } # Insert cloud support here # ttl if ($self->{channel}->{'dc'}->{'ttl'}) { $output .= ''.$self->encode($self->{channel}->{'dc'}->{'ttl'}).''."\n"; } elsif ($self->{channel}->{ttl}) { $output .= ''.$self->encode($self->{channel}->{ttl}).''."\n"; } $output .= "\n"; ################# # image element # ################# if ($self->{image}->{url}) { $output .= ''."\n"; # title $output .= ''.$self->encode($self->{image}->{title}).''."\n"; # url $output .= ''.$self->encode($self->{image}->{url}).''."\n"; # link $output .= ''.$self->encode($self->{image}->{'link'}).''."\n" if $self->{image}->{link}; # image width $output .= ''.$self->encode($self->{image}->{width}).''."\n" if $self->{image}->{width}; # image height $output .= ''.$self->encode($self->{image}->{height}).''."\n" if $self->{image}->{height}; # description $output .= ''.$self->encode($self->{image}->{description}).''."\n" if $self->{image}->{description}; # end image element $output .= ''."\n\n"; } ################ # item element # ################ foreach my $item (@{$self->{items}}) { if ($item->{title}) { $output .= ''."\n"; $output .= ''.$self->encode($item->{title}).''."\n" if $item->{title}; $output .= ''.$self->encode($item->{'link'}).''."\n" if $item->{link}; $output .= ''.$self->encode($item->{description}).''."\n" if $item->{description}; $output .= ''.$self->encode($item->{author}).''."\n" if $item->{author}; $output .= ''.$self->encode($item->{category}).''."\n" if $item->{category}; $output .= ''.$self->encode($item->{comments}).''."\n" if $item->{comments}; # The unique identifier. Use 'permaLink' for an external # identifier, or 'guid' for a internal string. # (I call it permaLink in the hash for purposes of clarity.) if ($item->{permaLink}) { $output .= ''.$self->encode($item->{permaLink}).''."\n"; } elsif ($item->{guid}) { $output .= ''.$self->encode($item->{guid}).''."\n"; } $output .= ''.$self->encode($item->{pubDate}).''."\n" if $item->{pubDate}; $output .= ''.$item->{source}.''."\n" if $item->{source} && $item->{sourceUrl}; if (my $e = $item->{enclosure}) { $output .= "' . "\n"; } # end image element $output .= ''."\n\n"; } } ##################### # textinput element # ##################### if ($self->{textinput}->{'link'}) { $output .= ''."\n"; $output .= ''.$self->encode($self->{textinput}->{title}).''."\n"; $output .= ''.$self->encode($self->{textinput}->{description}).''."\n"; $output .= ''.$self->encode($self->{textinput}->{name}).''."\n"; $output .= ''.$self->encode($self->{textinput}->{'link'}).''."\n"; $output .= ''."\n\n"; } ##################### # skipHours element # ##################### if ($self->{skipHours}->{hour}) { $output .= ''."\n"; $output .= ''.$self->encode($self->{skipHours}->{hour}).''."\n"; $output .= ''."\n\n"; } #################### # skipDays element # #################### if ($self->{skipDays}->{day}) { $output .= ''."\n"; $output .= ''.$self->encode($self->{skipDays}->{day}).''."\n"; $output .= ''."\n\n"; } # end channel element $output .= ''."\n"; $output .= ''; return $output; } sub as_string { my $self = shift; my $version = ($self->{output} =~ /\d/) ? $self->{output} : $self->{version}; my $output; ########### # RSS 0.9 # ########### if ($version eq '0.9') { $output = &as_rss_0_9($self); ############ # RSS 0.91 # ############ } elsif ($version eq '0.91') { $output = &as_rss_0_9_1($self); ########### # RSS 2.0 # ########### } elsif ($version eq '2.0') { $output = &as_rss_2_0($self); ########### # RSS 1.0 # ########### } else { $output = &as_rss_1_0($self); } return $output; } sub AUTOLOAD { my $self = shift; my $type = ref($self) || croak "$self is not an object\n"; my $name = $AUTOLOAD; $name =~ s/.*://; return if $name eq 'DESTROY'; croak "Unregistered entity: Can't access $name field in object of class $type" unless (exists $self->{$name}); # return reference to RSS structure if (@_ == 1) { return $self->{$name}->{$_[0]} if defined $self->{$name}->{$_[0]}; # we're going to set values here } elsif (@_ > 1) { my %hash = @_; my $_REQ; # make sure we have required elements and correct lengths if ($self->{'strict'}) { ($self->{version} eq '0.9') ? ($_REQ = $_REQ_v0_9) : ($_REQ = $_REQ_v0_9_1); } # store data in object foreach my $key (keys(%hash)) { if ($self->{'strict'}) { my $req_element = $_REQ->{$name}->{$key}; confess "$key cannot exceed " . $req_element->[1] . " characters in length" if defined $req_element->[1] && length($hash{$key}) > $req_element->[1]; } $self->{$name}->{$key} = $hash{$key}; } # return value return $self->{$name}; # otherwise, just return a reference to the whole thing } else { return $self->{$name}; } return 0; # make sure we have all required elements #foreach my $key (keys(%{$_REQ->{$name}})) { #my $element = $_REQ->{$name}->{$key}; #croak "$key is required in $name" #if ($element->[0] == 1) && (!defined($hash{$key})); #croak "$key cannot exceed ".$element->[1]." characters in length" #unless length($hash{$key}) <= $element->[1]; #} } # the code here is a minorly tweaked version of code from # Matts' rssmirror.pl script # my %entity = ( nbsp => " ", iexcl => "¡", cent => "¢", pound => "£", curren => "¤", yen => "¥", brvbar => "¦", sect => "§", uml => "¨", copy => "©", ordf => "ª", laquo => "«", not => "¬", shy => "­", reg => "®", macr => "¯", deg => "°", plusmn => "±", sup2 => "²", sup3 => "³", acute => "´", micro => "µ", para => "¶", middot => "·", cedil => "¸", sup1 => "¹", ordm => "º", raquo => "»", frac14 => "¼", frac12 => "½", frac34 => "¾", iquest => "¿", Agrave => "À", Aacute => "Á", Acirc => "Â", Atilde => "Ã", Auml => "Ä", Aring => "Å", AElig => "Æ", Ccedil => "Ç", Egrave => "È", Eacute => "É", Ecirc => "Ê", Euml => "Ë", Igrave => "Ì", Iacute => "Í", Icirc => "Î", Iuml => "Ï", ETH => "Ð", Ntilde => "Ñ", Ograve => "Ò", Oacute => "Ó", Ocirc => "Ô", Otilde => "Õ", Ouml => "Ö", times => "×", Oslash => "Ø", Ugrave => "Ù", Uacute => "Ú", Ucirc => "Û", Uuml => "Ü", Yacute => "Ý", THORN => "Þ", szlig => "ß", agrave => "à", aacute => "á", acirc => "â", atilde => "ã", auml => "ä", aring => "å", aelig => "æ", ccedil => "ç", egrave => "è", eacute => "é", ecirc => "ê", euml => "ë", igrave => "ì", iacute => "í", icirc => "î", iuml => "ï", eth => "ð", ntilde => "ñ", ograve => "ò", oacute => "ó", ocirc => "ô", otilde => "õ", ouml => "ö", divide => "÷", oslash => "ø", ugrave => "ù", uacute => "ú", ucirc => "û", uuml => "ü", yacute => "ý", thorn => "þ", yuml => "ÿ", ); my $entities = join('|', keys %entity); sub encode { my ($self, $text) = @_; return $text unless $self->{'encode_output'}; my $encoded_text = ''; while ( $text =~ s/(.*?)(\<\!\[CDATA\[.*?\]\]\>)//s ) { $encoded_text .= encode_text($1) . $2; } $encoded_text .= encode_text($text); return $encoded_text; } sub encode_text { my $text = shift; $text =~ s/&(?!(#[0-9]+|#x[0-9a-fA-F]+|\w+);)/&/g; $text =~ s/&($entities);/$entity{$1}/g; $text =~ s//>/g; return $text; } sub strict { my ($self,$value) = @_; $self->{'strict'} = $value; } sub save { my ($self,$file) = @_; open(OUT,">$file") || croak "Cannot open file $file for write: $!"; print OUT $self->as_string; close OUT; } 1;