File Coverage

blib/lib/Data/Tabular/Dumper.pm
Criterion Covered Total %
statement 160 179 89.3
branch 55 78 70.5
condition 11 15 73.3
subroutine 17 19 89.4
pod 5 7 71.4
total 248 298 83.2


line stmt bran cond sub pod time code
1             # $Id: Dumper.pm 456 2009-04-15 12:20:59Z fil $
2             package Data::Tabular::Dumper;
3              
4 4     4   136953 use strict;
  4         10  
  4         329  
5 4     4   104 use vars qw( $VERSION @ISA @EXPORT_OK );
  4         8  
  4         323  
6              
7 4     4   24 use Carp;
  4         7  
  4         11264  
8              
9             $VERSION="0.08";
10              
11             require Exporter;
12             @ISA = qw( Exporter );
13             @EXPORT_OK = qw( Dump );
14              
15              
16             ###########################################################
17             sub open
18             {
19 14     14 1 1498 my($package, %writers)=@_;
20 14         82 my $self=bless {writers=>{}, fields=>[]}, $package;
21              
22 14         78 $self->{master_key} = delete $writers{master_key};
23 14 100       62 $self->{master_key} = '' unless defined $self->{master_key};
24              
25 14         21 my($object, $one);
26             WRITER:
27 14         39 foreach my $p1 (keys %writers) {
28 23         58 foreach my $p2 ($p1, __PACKAGE__.'::'.$p1) {
29 46 50 66     714 if($p2->can('open') and $p2->can('close') and $p2->can('write')) {
      66        
30 23         39 $package=$p2 ;
31 23         29 eval {
32 23         103 $object=$package->open($writers{$p1});
33             };
34 23 50       83 carp $@ if $@;
35 23 50       67 if($object) {
36 23         64 $self->{writers}{$package}=$object;
37 23         37 $one=1;
38             }
39 23         84 next WRITER;
40             }
41             }
42 0         0 carp "Could not find a valid package for $p1 (".__PACKAGE__."::$p1)";
43             }
44 14 50       54 return unless $one;
45 14         62 return $self;
46             }
47              
48             ###########################################################
49             sub master_key
50             {
51 0     0 1 0 my( $self, $new_master ) = @_;
52 0         0 my $ret = $self->{master_key};
53 0 0       0 $self->{master_key} = $new_master if 2 == @_;
54 0         0 return $ret;
55             }
56              
57             ###########################################################
58             # Perform $name->() on all the writers.
59             sub _doall
60             {
61 16     16   31 my($name)=@_;
62             return sub {
63 73     73   1460 my $self=shift @_;
64 73         76 my $n;
65 73         79 foreach my $o (values %{$self->{writers}}) {
  73         242  
66 130         449 my $code=$o->can($name);
67 130 50       236 if($code) {
68 130         617 $code->($o, @_);
69 130 50       478 $n++ unless $@;
70             } else {
71 0         0 carp "Object $o can not do $name";
72             }
73 130 50       329 carp $@ if $@;
74             }
75 73         177 return $n;
76 16         82 };
77             }
78              
79             ###########################################################
80             *fields=_doall('fields');
81             *write=_doall('write');
82             *page_start=_doall('page_start');
83             *page_end=_doall('page_end');
84              
85             ###########################################################
86             sub close
87             {
88 28     28 1 96 my( $self )= @_;
89              
90 28         42 my @ret;
91 28         45 foreach my $o ( values %{$self->{writers}} ) {
  28         98  
92 46 50       259 next unless $o->can( 'close' );
93 46         167 push @ret, $o->close();
94             }
95 28         452 return @ret;
96             }
97              
98              
99             ###########################################################
100             sub DESTROY
101             {
102 14     14   43122 $_[0]->close;
103             }
104              
105              
106             ###########################################################
107             sub available
108             {
109 4     4 0 2803 my($package)=@_;
110              
111 4         10 my(%res, $yes);
112 4         12 foreach my $p (qw(CSV XML Excel)) {
113 12         23 $yes=0;
114 12 50       59 $yes=1 if exists $INC{"Data/Tabular/Dumper/$p.pm"};
115 12 50       36 unless($yes) {
116 12         52 local $SIG{__DIE__}='DEFAULT';
117 12         37 local $SIG{__WARN__}='IGNORE';
118 12         888 $yes=eval "require Data::Tabular::Dumper::$p; 1;";
119             # warn $@ if $@ and $ENV{PERL_DL_NONLAZY};
120             };
121 12         144 $res{$p}=$yes;
122             }
123 4 50       104 return \%res unless wantarray;
124 0         0 return grep {$res{$_}} keys %res;
  0         0  
125             }
126              
127             ###########################################################
128             sub Dump
129             {
130 0     0 1 0 return __PACKAGE__->dump( @_ );
131             }
132              
133             ###########################################################
134             sub dump
135             {
136 11     11 1 79 my( $self, $data ) = @_;
137              
138 11         16 my $ret;
139 11 50       32 unless( ref $self ) {
140 0         0 require Data::Tabular::Dumper::String;
141 0         0 $self = $self->open( String => \$ret, master_key=>'KEY' );
142             }
143              
144 11         35 my $state = $self->analyse( $data );
145 11 100       32 unless( $state->{pages} ) {
146 6         18 $self->__dump( $state );
147             }
148              
149 11         57 my $q1=1;
150 11         23 foreach my $p ( @{ $state->{pages} } ) {
  11         30  
151 10         24 my $name = "Page $q1";
152 10         13 $q1++;
153 10 100       29 $name = $p->{name} if exists $p->{name};
154 10         27 $self->page_start( $name );
155 10         25 $self->__dump( $p );
156 10         30 $self->page_end( $name );
157             }
158 11         81 return $ret;
159             }
160              
161             ###########################################################
162             sub __dump
163             {
164 16     16   28 my( $self, $data ) = @_;
165              
166 16 100       58 $self->fields( $data->{fields} ) if $data->{fields};
167 16         25 foreach my $d ( @{ $data->{data} } ) {
  16         40  
168 33         77 $self->write( $d->{data} );
169             }
170             }
171              
172              
173             ###########################################################
174             # Convert a 2- or 3-dimensional data structure into something we can
175             # easily use.
176             # Lowest-level structure is {data=>[ ...scalars...], fields=>[ ...names...]}
177             # Other possible : maxdepth, depth (internal use)
178             # We either have an array of those in {data} (2-D)
179             # { data=>[ ...lower-level ], fields=>[....names...] }
180             # Otehr possible keys : name (if it's a part of {pages})
181             # OR we have an array of 2-D structures in {pages}
182             sub analyse
183             {
184 21     21 0 40443 my( $self, $data ) = @_;
185              
186 21         136 my $master = { maxdepth=>0, depth=>0 };
187 21         74 my $state = $self->__analyse( $master, $data);
188              
189 21 100       56 if( $master->{maxdepth} == 4 ) {
190 11         32 $state->{pages} = delete $state->{data};
191             }
192 21 50       57 die "ARG!" if $master->{__fields};
193              
194 21         66 return $state;
195             }
196              
197             ###########################################################
198             # Do the heavy lifting.
199             # Recurse over a data structure
200             sub __analyse
201             {
202 289     289   380 my( $self, $parent, $data ) = @_;
203 289         410 my $r = ref $data;
204 289 100       766 return $data unless $r;
205              
206 109 50       278 die "Only 2-d and 3-d data is supported" if $parent->{depth} > 2;
207              
208              
209 109         321 my $state = { depth=>$parent->{depth}+1 };
210 109         234 $state->{maxdepth} = $state->{depth};
211              
212 109 100       244 if( $r eq 'ARRAY' ) {
    50          
213 53         148 $self->__analyse_array( $parent, $data, $state );
214             }
215             elsif( $r eq 'HASH' ) {
216 56         194 $self->__analyse_hash( $parent, $data, $state );
217             }
218             else {
219 0         0 die "Don't know how to handle $r at level $state->{depth}";
220             }
221              
222 109 100       299 $self->__analyse_rehash( $state, $data, $parent ) if $state->{__fields};
223 109         233 $self->__analyse_depth( $state, $parent );
224              
225 109         190 return $state;
226             }
227              
228             ###########################################################
229             # Turns out $data was a HoH or LoH. So we have to change all the
230             # sub-hashes.
231             sub __analyse_rehash
232             {
233 18     18   31 my( $self, $state, $data, $parent ) = @_;
234             ## If we are here, $data is a LoH...
235 18         31 my @fields = sort keys %{ delete $state->{__fields} };
  18         99  
236              
237             # use Data::Denter;
238             # warn "Rehashing ", Denter $data, $state->{data};
239 18         48 my $first_name;
240 18 100       50 unless( 'ARRAY' eq ref $data ) {
241 7         12 my @names;
242 7 100       58 if( $state->{data}[0]{name} ) { # 3-D
243 4         6 @names = map { $_->{name} } @{ $state->{data} };
  8         21  
  4         10  
244             }
245             else { # HoH
246 3         7 @names = map { $_->{data}[0] } @{ $state->{data} };
  6         22  
  3         11  
247             }
248 7         17 $data = [ map { { %{$data->{$_}} } } @names ];
  14         18  
  14         337  
249 7         15 $first_name = 1;
250              
251 7         18 unshift @fields, 'HONK__TITLE__HONK';
252 7         31 for( my $q=0; $q <= $#$data ; $q++ ) {
253 14         93 $data->[$q]{$fields[0]} = $names[$q];
254             }
255             }
256              
257 18         42 $state->{data} = [];
258 18         76 foreach my $hash ( @$data ) {
259 38         90 push @{ $state->{data} },
  38         174  
260 38         45 { depth=>$parent->{depth}+2, data=>[ @{$hash}{@fields} ] };
261             }
262              
263 18 100       61 $fields[0] = $self->{master_key} if $first_name;
264 18         37 $state->{fields} = \@fields;
265 18         49 return;
266             }
267              
268             ###########################################################
269             # Make sure {maxdepth} of the parent is as big as can be
270             sub __analyse_depth
271             {
272 109     109   147 my( $self, $state, $parent ) = @_;
273 109 100       308 if( $state->{depth} > $parent->{maxdepth} ) {
274 32         62 $parent->{maxdepth} = $state->{depth};
275             }
276              
277 109 100       339 if( $state->{maxdepth} > $parent->{maxdepth} ) {
278 32         70 $parent->{maxdepth} = $state->{maxdepth};
279             }
280             }
281              
282              
283             ###########################################################
284             # Recurse over an arrayref
285             sub __analyse_array
286             {
287 53     53   162 my( $self, $parent, $data, $state ) = @_;
288 53         121 $state->{data} = [];
289              
290 53         96 foreach my $s ( @$data ) {
291 148         313 my $sub = $self->__analyse( $state, $s );
292              
293 148 100       165 if( @{ $state->{data} } ) {
  148 100       395  
294 95         195 my $err = (!!ref $state->{data}[0] ^ !!ref $sub);
295 95 50 66     539 $err = 1 if not $err and
      66        
296             ref $state->{data}[0] and
297             ref $state->{data}[0]{data} ne
298             ref $sub->{data};
299             # $err = 1 if $state->{fields};
300 95 50       205 if( $err ) {
301 0         0 die "Non-uniform data references at a level $state->{depth}";
302             }
303             }
304             elsif( ref $sub ) {
305 25         41 $parent->{maxdepth}++;
306             }
307 148         169 push @{ $state->{data} }, $sub
  148         393  
308             }
309             }
310              
311             ###########################################################
312             # Recurse over a hashref
313             sub __analyse_hash
314             {
315 56     56   78 my( $self, $parent, $data, $state ) = @_;
316 56         108 $state->{data} = [];
317              
318 56 50       132 if( $parent->{fields} ) {
319 0         0 foreach my $k ( @{ $parent->{fields} } ) {
  0         0  
320 0         0 push @{ $state->{data} }, $data->{$k};
  0         0  
321             }
322 0         0 return;
323             }
324              
325 56         217 foreach my $k ( sort keys %$data ) {
326 120         287 my $sub = $self->__analyse( $state, $data->{$k} );
327              
328 120 100       271 unless( ref $sub ) { # $data is a hash
    100          
329 84         176 $parent->{__fields}{$k} = 1;
330             # $fields{$k}=1;
331             }
332             elsif( $sub->{maxdepth}==3 ) { # $data is a HoLoL
333 26         48 $sub->{name} = $k;
334             }
335             else {
336 10         46 my $r = ref $sub->{data};
337 10 50       84 if( $r eq 'ARRAY' ) { # $data is a HoL
    0          
338 10         16 unshift @{$sub->{data}}, $k;
  10         31  
339             }
340             elsif( $r eq 'HASH' ) { # $data is a HoH
341 0         0 $sub->{name}=$k;
342             }
343             }
344 120 100 100     165 if( 0== @{ $state->{data} } and ref $sub ) {
  120         449  
345 18         35 $parent->{maxdepth}++;
346             }
347 120         130 push @{$state->{data}}, $sub;
  120         337  
348             }
349             }
350              
351             1;
352             __END__