File Coverage

lib/XML/FeedWriter/Base.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package XML::FeedWriter::Base;
2            
3 1     1   6 use strict;
  1         1  
  1         26  
4 1     1   5 use warnings;
  1         1  
  1         18  
5 1     1   4 use Carp;
  1         1  
  1         51  
6 1     1   4 use base qw( Class::Accessor::Fast Class::Data::Inheritable );
  1         1  
  1         817  
7 1     1   11674 use Encode;
  1         12664  
  1         95  
8 1     1   583 use DateTimeX::Web;
  0            
  0            
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__