1# $Id$ 2# 3# Copyright (c) 2005-2007 Daisuke Maki <daisuke@endeworks.jp> 4# All rights reserved. 5 6package XML::RSS::LibXML::V2_0; 7use strict; 8use warnings; 9use base qw(XML::RSS::LibXML::ImplBase); 10use DateTime::Format::W3CDTF; 11use DateTime::Format::Mail; 12 13my %DcElements = ( 14 (map { ("dc:$_" => [ { module => 'dc', element => $_ } ]) } 15 qw(language rights date publisher creator title subject description contributer type format identifier source relation coverage)), 16); 17 18my %SynElements = ( 19 (map { ("syn:$_" => [ { module => 'syn', element => $_ } ]) } 20 qw(updateBase updateFrequency updatePeriod)), 21); 22my $format_dates = sub { 23 my $v = eval { 24 DateTime::Format::Mail->format_datetime( 25 DateTime::Format::W3CDTF->parse_datetime($_[0]) 26 ); 27 }; 28 if ($v && ! $@) { 29 $_[0] = $v; 30 } 31}; 32 33my %ChannelElements = ( 34 %DcElements, 35 %SynElements, 36 (map { ($_ => [ $_ ]) } qw(title link description)), 37 language => [ { module => 'dc', element => 'language' }, 'language' ], 38 copyright => [ { module => 'dc', element => 'rights' }, 'copyright' ], 39 pubDate => { 40 candidates => [ 'pubDate', { module => 'dc', element => 'date' } ], 41 callback => $format_dates, 42 }, 43 lastBuildDate => { 44 candidates => [ { module => 'dc', element => 'date' }, 'lastBuildDate' ], 45 callback => $format_dates, 46 }, 47 docs => [ 'docs' ], 48 managingEditor => [ { module => 'dc', element => 'publisher' }, 'managingEditor' ], 49 webMaster => [ { module => 'dc', element => 'creator' }, 'webMaster' ], 50 category => [ { module => 'dc', element => 'category' }, 'category' ], 51 generator => [ { module => 'dc', element => 'generator' }, 'generator' ], 52 ttl => [ { module => 'dc', element => 'ttl' }, 'ttl' ], 53); 54 55my %ItemElements = ( 56 %DcElements, 57 enclosure => ['enclosure'], 58 map { ($_ => [$_]) } 59 qw(title link description author category comments pubDate) 60); 61 62my %ImageElements = ( 63 (map { ($_ => [$_]) } qw(title url link width height description)), 64 %DcElements, 65); 66 67my %TextInputElements = ( 68 (map { ($_ => [$_]) } qw(title link description name)), 69 %DcElements 70); 71 72sub definition 73{ 74 return +{ 75 channel => { 76 title => '', 77 'link' => '', 78 description => '', 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 => bless ({ 96 title => undef, 97 url => undef, 98 'link' => undef, 99 width => undef, 100 height => undef, 101 description => undef, 102 }, 'XML::RSS::LibXML::ElementSpec'), 103 skipDays => bless ({ 104 day => undef, 105 }, 'XML::RSS::LibXML::ElementSpec'), 106 skipHours => bless ({ 107 hour => undef, 108 }, 'XML::RSS::LibXML::ElementSpec'), 109 textinput => bless ({ 110 title => undef, 111 description => undef, 112 name => undef, 113 'link' => undef, 114 }, 'XML::RSS::LibXML::ElementSpec'), 115 }; 116} 117 118sub parse_dom 119{ 120 my $self = shift; 121 my $c = shift; 122 my $dom = shift; 123 124 $c->reset; 125 $c->version('2.0'); 126 $c->encoding($dom->encoding); 127 $self->parse_base($c, $dom); 128 $self->parse_namespaces($c, $dom); 129 $self->parse_channel($c, $dom); 130 $self->parse_items($c, $dom); 131 $self->parse_misc_simple($c, $dom); 132} 133 134 135sub parse_channel 136{ 137 my ($self, $c, $dom) = @_; 138 139 my $xc = $c->create_xpath_context($c->{namespaces}); 140 141 my ($root) = $xc->findnodes('/rss/channel', $dom); 142 my %h = $self->parse_children($c, $root, './*[name() != "item"]'); 143 144 foreach my $type (qw(day hour)) { 145 my $field = 'skip' . ucfirst($type) . 's'; 146 if (my $skip = delete $h{$field}) { 147 if (ref $skip ne 'HASH') { 148# warn "field $field has invalid entry (does this RSS validate?)"; 149 } elsif (! UNIVERSAL::isa($skip, 'XML::RSS::LibXML::ElementSpec')) { 150 $c->$field(UNIVERSAL::isa($skip, 'XML::RSS::LibXML::MagicElement') ? $skip : %$skip); 151 } 152 } 153 } 154 155 foreach my $field (qw(textinput image)) { 156 if (my $v = $h{$field}) { 157 if (ref $v ne 'HASH') { 158# warn "field $field has invalid entry (does this RSS validate?)"; 159 } elsif (! UNIVERSAL::isa($v, 'XML::RSS::LibXML::ElementSpec')) { 160 $c->$field(UNIVERSAL::isa($v, 'XML::RSS::LibXML::MagicElement') ? $v : %$v); 161 } 162 } 163 } 164 $c->channel(%h); 165} 166 167sub parse_items 168{ 169 my ($self, $c, $dom) = @_; 170 my @items; 171 my $version = $c->version; 172 my $xc = $c->create_xpath_context($c->{namespaces}); 173 my $xpath = '/rss/channel/item'; 174 foreach my $item ($xc->findnodes($xpath, $dom)) { 175 my $i = $self->parse_children($c, $item); 176 $self->add_item($c, $i); 177 } 178} 179 180sub parse_misc_simple 181{ 182 my ($self, $c, $dom) = @_; 183 184 my $xc = $c->create_xpath_context($c->{namespaces}); 185 foreach my $node ($xc->findnodes('/rss/*[name() != "channel" and name() != "item"]', $dom)) { 186 my $h = $self->parse_children($c, $node); 187 my $name = $node->localname; 188 my $prefix = $node->getPrefix(); 189 190 $name = 'textinput' if $name eq 'textInput'; 191 192 if ($prefix) { 193 $c->{$prefix} ||= {}; 194 $self->store_element($c->{$prefix}, $name, $h); 195 196 # XML::RSS requires us to allow access to elements both from 197 # the prefix and the namespace 198 $c->{$c->{namespaces}{$prefix}} ||= {}; 199 $self->store_element($c->{$c->{namespaces}{$prefix}}, $name, $h); 200 } else { 201 $self->store_element($c, $name, $h); 202 } 203 } 204} 205 206sub create_dom 207{ 208 my ($self, $c) = @_; 209 210 my $dom = $self->SUPER::create_dom($c); 211 my $root = $dom->getDocumentElement(); 212 my $xc = $c->create_xpath_context(scalar $c->namespaces); 213 my($channel) = $xc->findnodes('/rss/channel', $dom); 214 215 if (my $image = $c->image) { 216 my $inode = $dom->createElement('image'); 217 $self->create_element_from_spec($image, $dom, $inode, \%ImageElements); 218 $self->create_extra_modules($image, $dom, $inode, $c->namespaces); 219 $channel->appendChild($inode); 220 } 221 222 if (my $textinput = $c->textinput) { 223 my $inode = $dom->createElement('textInput'); 224 $self->create_element_from_spec($textinput, $dom, $inode, \%TextInputElements); 225 $self->create_extra_modules($textinput, $dom, $inode, $c->namespaces); 226 $channel->appendChild($inode); 227 } 228 229 return $dom; 230} 231 232sub create_rootelement 233{ 234 my ($self, $c, $dom) = @_; 235 my $root = $dom->createElement('rss'); 236 $root->setAttribute(version => '2.0'); 237 if (my $base = $c->base) { 238 $root->setAttribute('xml:base' => $base); 239 } 240 $dom->setDocumentElement($root); 241} 242 243sub create_channel 244{ 245 my ($self, $c, $dom) = @_; 246 247 my $root = $dom->getDocumentElement(); 248 my $channel = $dom->createElement('channel'); 249 250 $self->create_element_from_spec($c->channel, $dom, $channel, \%ChannelElements); 251 252 foreach my $type (qw(day hour)) { 253 my $field = 'skip' . ucfirst($type) . 's'; 254 my $skip = $c->$field; 255 if ($skip && defined $skip->{$type}) { 256 my $sd = $dom->createElement($field); 257 my $d = $dom->createElement($type); 258 $d->appendChild($dom->createTextNode($skip->{$type})); 259 $sd->appendChild($d); 260 $channel->appendChild($sd); 261 } 262 } 263 $root->appendChild($channel); 264} 265 266sub create_items 267{ 268 my ($self, $c, $dom) = @_; 269 270 my ($channel) = $dom->findnodes('/rss/channel'); 271 foreach my $i ($c->items) { 272 my $item = $dom->createElement('item'); 273 $self->create_element_from_spec($i, $dom, $item, \%ItemElements); 274 $self->create_extra_modules($i, $dom, $item, $c->namespaces); 275 my $guid = $i->{guid}; 276 if (defined $guid) { 277 my $guid_element = $dom->createElement('guid'); 278 if (eval { $guid->isa('XML::RSS::LibXML::MagicElement') }) { 279 my $isperma = 'true'; 280 if (! $guid->{isPermaLink} || $guid->{isPermaLink} ne 'true') { 281 $isperma = 'false'; 282 } 283 $guid_element->setAttribute(isPermaLink => $isperma); 284 $guid_element->appendChild($dom->createTextNode($guid->toString)); 285 } else { 286 $guid_element->setAttribute(isPermaLink => "false"); 287 $guid_element->appendChild($dom->createTextNode($guid)); 288 } 289 $item->appendChild($guid_element); 290 } 291 292 $channel->appendChild($item); 293 } 294} 295 2961; 297