diff --git a/lib/XML/RSS/Creator.pm b/lib/XML/RSS/Creator.pm
new file mode 100644
index 000000000..1736e4715
--- /dev/null
+++ b/lib/XML/RSS/Creator.pm
@@ -0,0 +1,1501 @@
+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) ."$prefix:$el>\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) ."$prefix:$el>\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) ."$prefix:$el>\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) ."$prefix:$el>\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 .= "encode($e->{$_}) . qq!"!} keys(%$e))
+ . ' />' . "\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;
+ $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;