| 1 | package XML::FeedWriter::Base; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use warnings; |
|---|
| 5 | use Carp; |
|---|
| 6 | use base qw( Class::Accessor::Fast Class::Data::Inheritable ); |
|---|
| 7 | use Encode; |
|---|
| 8 | use DateTimeX::Web; |
|---|
| 9 | use XML::Writer; |
|---|
| 10 | |
|---|
| 11 | __PACKAGE__->mk_accessors(qw( |
|---|
| 12 | xml dtx |
|---|
| 13 | _closed _output _encoding _use_cdata |
|---|
| 14 | )); |
|---|
| 15 | |
|---|
| 16 | __PACKAGE__->mk_classdata( _alias => {} ); |
|---|
| 17 | __PACKAGE__->mk_classdata( _requires => {} ); |
|---|
| 18 | __PACKAGE__->mk_classdata( _sort_order => {} ); |
|---|
| 19 | |
|---|
| 20 | sub new { |
|---|
| 21 | my ($class, %options) = @_; |
|---|
| 22 | |
|---|
| 23 | delete $options{version}; # this is for XML::FeedWriter only. |
|---|
| 24 | |
|---|
| 25 | my $encoding = delete $options{encoding} || 'utf-8'; |
|---|
| 26 | |
|---|
| 27 | my $output; |
|---|
| 28 | my $self = bless { |
|---|
| 29 | xml => XML::Writer->new( OUTPUT => \$output ), |
|---|
| 30 | dtx => DateTimeX::Web->new, |
|---|
| 31 | _output => \$output, |
|---|
| 32 | _encoding => $encoding, |
|---|
| 33 | _closed => 0, |
|---|
| 34 | }, $class; |
|---|
| 35 | |
|---|
| 36 | $self->_extra_options( \%options ); |
|---|
| 37 | |
|---|
| 38 | my $modules = delete $options{modules} || {}; |
|---|
| 39 | my %channel = $self->_canonize( \%options ); |
|---|
| 40 | |
|---|
| 41 | $self->_validate( channel => \%channel ); |
|---|
| 42 | |
|---|
| 43 | $self->xml->xmlDecl( $self->_encoding ); |
|---|
| 44 | |
|---|
| 45 | $self->_root_element( $modules ); |
|---|
| 46 | |
|---|
| 47 | $self->xml->setDataMode(1); |
|---|
| 48 | $self->xml->setDataIndent(2); |
|---|
| 49 | |
|---|
| 50 | $self->_channel( \%channel ); |
|---|
| 51 | |
|---|
| 52 | return $self; |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | sub _extra_options {} |
|---|
| 56 | sub _root_element {} |
|---|
| 57 | sub _channel {} |
|---|
| 58 | |
|---|
| 59 | sub add_items {} |
|---|
| 60 | |
|---|
| 61 | sub close {} |
|---|
| 62 | |
|---|
| 63 | sub save { |
|---|
| 64 | my ($self, $file) = @_; |
|---|
| 65 | |
|---|
| 66 | $self->close unless $self->_closed; |
|---|
| 67 | |
|---|
| 68 | open my $fh, '>', $file; |
|---|
| 69 | binmode $fh; |
|---|
| 70 | print $fh encode( $self->_encoding, $self->as_string ); |
|---|
| 71 | CORE::close $fh; |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | sub as_string { ${ shift->_output } } |
|---|
| 75 | |
|---|
| 76 | sub _data_element { |
|---|
| 77 | my ($self, $key, $data) = @_; |
|---|
| 78 | |
|---|
| 79 | if ( ref $data eq 'ARRAY' ) { |
|---|
| 80 | $self->xml->dataElement( $key => @{ $data } ); |
|---|
| 81 | } |
|---|
| 82 | elsif ( ref $data eq 'HASH' ) { |
|---|
| 83 | my %attr = %{ $data }; |
|---|
| 84 | my $value = delete $attr{value}; |
|---|
| 85 | $self->xml->dataElement( $key => $value, %attr ); |
|---|
| 86 | } |
|---|
| 87 | else { |
|---|
| 88 | $self->xml->dataElement( $key => $data ); |
|---|
| 89 | } |
|---|
| 90 | } |
|---|
| 91 | |
|---|
| 92 | sub _cdata_element { |
|---|
| 93 | my ($self, $key, $data) = @_; |
|---|
| 94 | |
|---|
| 95 | if ( $self->_use_cdata ) { |
|---|
| 96 | $self->xml->cdataElement( $key => $data ); |
|---|
| 97 | } |
|---|
| 98 | else { |
|---|
| 99 | $self->_data_element( $key => $data ); |
|---|
| 100 | } |
|---|
| 101 | } |
|---|
| 102 | |
|---|
| 103 | sub _datetime_element { |
|---|
| 104 | my ($self, $key, $data) = @_; |
|---|
| 105 | |
|---|
| 106 | my $datetime; |
|---|
| 107 | if ( ref $data eq 'ARRAY' ) { |
|---|
| 108 | $datetime = $self->dtx->for_rss20( @{ $data } ); |
|---|
| 109 | } |
|---|
| 110 | if ( ref $data eq 'HASH' ) { |
|---|
| 111 | $datetime = $self->dtx->for_rss20( %{ $data } ); |
|---|
| 112 | } |
|---|
| 113 | elsif ( ref $data ) { |
|---|
| 114 | $datetime = $self->dtx->for_rss20( $data ); |
|---|
| 115 | } |
|---|
| 116 | elsif ( $data && $data =~ /^\d+$/ ) { |
|---|
| 117 | $datetime = $self->dtx->for_rss20( epoch => $data ); |
|---|
| 118 | } |
|---|
| 119 | else { |
|---|
| 120 | $datetime = $self->dtx->for_rss20; |
|---|
| 121 | } |
|---|
| 122 | |
|---|
| 123 | $self->_data_element( $key => $datetime ); |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | sub _empty_element { |
|---|
| 127 | my ($self, $key, $hashref) = @_; |
|---|
| 128 | |
|---|
| 129 | $self->_validate( $key => $hashref ); |
|---|
| 130 | $self->xml->emptyTag( $key => %{ $hashref } ); |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | sub _element_with_children { |
|---|
| 134 | my ($self, $key, $children) = @_; |
|---|
| 135 | |
|---|
| 136 | $self->_validate( $key => $children ); |
|---|
| 137 | |
|---|
| 138 | $self->xml->startTag($key); |
|---|
| 139 | foreach my $subkey ( $self->_sort_keys( $children ) ) { |
|---|
| 140 | $self->_data_element( $subkey => $children->{$subkey} ); |
|---|
| 141 | } |
|---|
| 142 | $self->xml->endTag($key); |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | sub _duplicable_elements { |
|---|
| 146 | my ($self, $key, $data) = @_; |
|---|
| 147 | |
|---|
| 148 | if ( ref $data eq 'ARRAY' ) { |
|---|
| 149 | foreach my $item ( @{ $data } ) { |
|---|
| 150 | $self->_data_element( $key => $item ); |
|---|
| 151 | } |
|---|
| 152 | } |
|---|
| 153 | else { |
|---|
| 154 | $self->_data_element( $key => $data ); |
|---|
| 155 | } |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | sub _element_with_duplicable_children { |
|---|
| 159 | my ($self, $key, $data, $children_name) = @_; |
|---|
| 160 | my @items = ( ref $data eq 'ARRAY' ) |
|---|
| 161 | ? @{ $data } |
|---|
| 162 | : ( $data ); |
|---|
| 163 | |
|---|
| 164 | $self->xml->startTag($key); |
|---|
| 165 | foreach my $item ( @items ) { |
|---|
| 166 | $self->_data_element( $children_name => $item ); |
|---|
| 167 | } |
|---|
| 168 | $self->xml->endTag($key); |
|---|
| 169 | } |
|---|
| 170 | |
|---|
| 171 | sub _validate { |
|---|
| 172 | my ($self, $type, $hashref) = @_; |
|---|
| 173 | |
|---|
| 174 | foreach my $req ( @{ $self->_requires->{$type} } ) { |
|---|
| 175 | if ( ref $req eq 'ARRAY' ) { |
|---|
| 176 | croak "$type: $req is required" |
|---|
| 177 | unless defined $hashref->{$req->[0]}; |
|---|
| 178 | croak "$type: $req is too long" |
|---|
| 179 | unless length( $hashref->{$req->[0]} ) < $req->[1]; |
|---|
| 180 | } |
|---|
| 181 | else { |
|---|
| 182 | croak "$type: $req is required" |
|---|
| 183 | unless defined $hashref->{$req}; |
|---|
| 184 | } |
|---|
| 185 | } |
|---|
| 186 | } |
|---|
| 187 | sub _canonize { |
|---|
| 188 | my ($self, $hashref) = @_; |
|---|
| 189 | |
|---|
| 190 | my %hash; |
|---|
| 191 | foreach my $key ( keys %{ $hashref } ) { |
|---|
| 192 | $hash{ $self->_alias->{$key} || $key } = $hashref->{$key}; |
|---|
| 193 | } |
|---|
| 194 | return %hash; |
|---|
| 195 | } |
|---|
| 196 | |
|---|
| 197 | sub _sort_keys { |
|---|
| 198 | my ($self, $hashref) = @_; |
|---|
| 199 | |
|---|
| 200 | return map { $_->{key} } |
|---|
| 201 | sort { $b->{order} <=> $a->{order} } |
|---|
| 202 | map { +{ |
|---|
| 203 | key => $_, |
|---|
| 204 | order => $self->_sort_order->{$_} || 0, |
|---|
| 205 | }} |
|---|
| 206 | keys %{ $hashref }; |
|---|
| 207 | } |
|---|
| 208 | |
|---|
| 209 | 1; |
|---|
| 210 | |
|---|
| 211 | __END__ |
|---|
| 212 | |
|---|
| 213 | =head1 NAME |
|---|
| 214 | |
|---|
| 215 | XML::FeedWriter::Base |
|---|
| 216 | |
|---|
| 217 | =head1 DESCRIPTION |
|---|
| 218 | |
|---|
| 219 | This class is a base class for more specific feed writers. See appropriate pods for details. |
|---|
| 220 | |
|---|
| 221 | =head1 METHODS |
|---|
| 222 | |
|---|
| 223 | =head2 new |
|---|
| 224 | =head2 add_items |
|---|
| 225 | =head2 close |
|---|
| 226 | =head2 save |
|---|
| 227 | =head2 as_string |
|---|
| 228 | |
|---|
| 229 | =head1 SEE ALSO |
|---|
| 230 | |
|---|
| 231 | L<XML::FeedWriter>, L<XML::FeedWriter::RSS20> |
|---|
| 232 | |
|---|
| 233 | =head1 AUTHOR |
|---|
| 234 | |
|---|
| 235 | Kenichi Ishigaki, E<lt>ishigaki@cpan.orgE<gt> |
|---|
| 236 | |
|---|
| 237 | =head1 COPYRIGHT AND LICENSE |
|---|
| 238 | |
|---|
| 239 | Copyright (C) 2008 by Kenichi Ishigaki. |
|---|
| 240 | |
|---|
| 241 | This program is free software; you can redistribute it and/or |
|---|
| 242 | modify it under the same terms as Perl itself. |
|---|
| 243 | |
|---|
| 244 | =cut |
|---|