File Coverage

blib/lib/Data/Presenter/Combo.pm
Criterion Covered Total %
statement 122 122 100.0
branch 18 18 100.0
condition 3 3 100.0
subroutine 8 8 100.0
pod n/a
total 151 151 100.0


line stmt bran cond sub pod time code
1             package Data::Presenter::Combo;
2             $VERSION = 1.03; # 02-10-2008
3             @ISA = qw(Data::Presenter);
4 4     4   14391 use strict;
  4         11  
  4         197  
5 4     4   25 use warnings;
  4         9  
  4         168  
6 4     4   24 use Carp;
  4         9  
  4         322  
7 4     4   25 use Data::Dumper;
  4         13  
  4         5928  
8              
9             sub _init {
10 9     9   21 my ($self, $source) = @_;
11 9         29 my @objects = @$source;
12 9 100       306 croak "Not enough sources to create a Combo data source: $!"
13             if scalar(@objects) < 2;
14              
15             # Designate the first object named as the 'base'
16 8         22 my $base = shift @objects;
17            
18             # Merge the second object into the first and repeat the process as many
19             # times as needed, with the result of the merging as the new base
20 8         44 while (scalar(@objects)) {
21 10         19 my $sec = shift @objects;
22 10         78 $base = $self->_merge_into_base($base, $sec);
23             }
24 6         27 return $base;
25             }
26              
27             ################################################################################
28             ##### &_merge_into_base: Called from within &_init
29             ##### Iteratively merges the data structures in the Data::Presenter
30             ##### objects passed as arguments
31             ################################################################################
32              
33             sub _merge_into_base {
34 10     10   22 my ($self, $base, $sec) = @_;
35              
36             # for readability
37 10         129 my %basehash = %$base;
38 10         101 my %sechash = %$sec;
39 10         25 my @basefields = @{$basehash{fields}};
  10         47  
40 10         16 my @secfields = @{$sechash{fields}};
  10         43  
41 10         22 my @baseparameters = @{$basehash{parameters}};
  10         34  
42 10         17 my @secparameters = @{$sechash{parameters}};
  10         25  
43 10         24 my $baseindex = $base->{index};
44 10         22 my $secindex = $sec->{index};
45 10         16 my @baseindexparams = @{$baseparameters[$baseindex]};
  10         28  
46 10         17 my @secindexparams = @{$secparameters[$secindex]};
  10         30  
47            
48             # Verify that all sources share a commonly named, identically specified
49             # index field
50 10 100       397 croak "All data sources must have an identically named index field\n in the configuration file: $!"
51             unless ($basefields[$baseindex] eq $secfields[$secindex]);
52 9         39 for (my $i=0; $i
53 33 100       336 croak "All data sources must have identically specified parameters\n for the index field in the configuration file: $!"
54             unless ($baseindexparams[$i] eq $secindexparams[$i]);
55             }
56              
57             # Build the new Combo::[subclass] object's 'fields' entry
58 8         94 my $augmented_fields_ref = _augment_fields(\@basefields, \@secfields);
59 8         17 my $newbasefieldsref = ${$augmented_fields_ref}{newfields};
  8         20  
60 8         15 my $secfieldspresentref = ${$augmented_fields_ref}{secfieldspresent};
  8         18  
61 8         14 my $secfieldsneededref = ${$augmented_fields_ref}{secfieldsneeded};
  8         20  
62              
63             # Build the new Combo::[subclass] object's replacement for %fieldlabels
64 8         13 my %newbasefieldlabels;
65 8         17 for (my $i = 0; $i < scalar(@{$newbasefieldsref}); $i++) {
  92         190  
66 84         91 $newbasefieldlabels{${$newbasefieldsref}[$i]} = $i;
  84         185  
67             }
68            
69             # Build the new Combo::[subclass] object's replacement for %fp
70 8         33 my %newbasefp;
71 8         39 for (my $i = 0; $i < scalar(@basefields); $i++) {
72 62         69 $newbasefp{$basefields[$i]} = \@{ $baseparameters[$i] };
  62         174  
73             }
74 8         14 foreach my $el (keys %{$secfieldsneededref}) {
  8         35  
75 22         27 $newbasefp{$secfields[$el]} = \@{ $secparameters[$el] };
  22         65  
76             }
77            
78             # %secpresentflip: Look-up table listing keys in sec also found in base;
79             # value contains index position of that field;
80             # needed because I don't want a key eq '0'.
81 8         23 my %secpresentflip = ();
82              
83 8         24 foreach my $key (keys %$secfieldspresentref) {
84 24         53 $secpresentflip{${$secfieldspresentref}{$key}} = [1, $key];
  24         64  
85             }
86              
87 8         18 my %basefieldlabels; # Example: 'cno' => '2'
88             my %secfieldlabels; # Example: 'cno' => '4'
89 8         29 for (my $i = 0; $i < scalar(@basefields); $i++) {
90 62         164 $basefieldlabels{$basefields[$i]} = $i;
91             }
92 8         30 for (my $i = 0; $i < scalar(@secfields); $i++) {
93 46         120 $secfieldlabels{$secfields[$i]} = $i;
94             }
95 8         20 my %basefields = map {$_,1} @basefields;
  62         135  
96 8         22 my %secfields = map {$_,1} @secfields;
  46         90  
97 8         23 my %sharedfields; # Example: 'cno' => [ 2, 4 ]
98 8         28 foreach my $field (keys %basefields) {
99 62 100       181 $sharedfields{$field} = [
100             $basefieldlabels{$field},
101             $secfieldlabels{$field}
102             ] if ($secfields{$field});
103             }
104              
105             # %newbase will become the hash of arrays blessed into the
106             # Combo::[subclass] object;
107             # a reference to it will be _merge_into_base's return value.
108             # Since its population is iterative, I have to start with an
109             # empty hash reference.
110 8         27 my $newbaseref = {};
111              
112             # pass variables to $self->_merge_engine();
113 8         150 $newbaseref = $self->_merge_engine(
114             {
115             base => $base,
116             basefieldlabels => \%basefieldlabels,
117             secondary => $sec,
118             secfieldlabels => \%secfieldlabels,
119             secpresentflip => \%secpresentflip,
120             secfieldsneeded => $secfieldsneededref,
121             newbase => $newbaseref,
122             newbasefields => $newbasefieldsref,
123             newfp => \%newbasefp,
124             newfieldlabels => \%newbasefieldlabels,
125             sharedfields => \%sharedfields,
126             }
127             );
128            
129             # populate the Combo::[subclass] object's data structure
130 8         74 my %newbase = %$newbaseref;
131 8         22 $newbase{'fields'} = $newbasefieldsref;
132 8         13 $newbase{'index'} = $baseindex;
133 8         169 return \%newbase;
134             }
135              
136             ################################################################################
137             ##### &_augment_fields: called from within &_merge_into_base
138             ##### Prepares the 'fields' entry in the new Combo::[subclass] object
139             ################################################################################
140              
141             sub _augment_fields {
142 8     8   13 my @basefields = @{+shift};
  8         39  
143 8         13 my @secfields = @{+shift};
  8         22  
144 8         25 my %seen = ();
145 8         15 my @additions = ();
146 8         27 my @total = ();
147 8         15 my %secneeded = ();
148 8         18 my %secpresent = ();
149            
150             # Work thru sec's @fields by index
151 8         31 for (my $d = 0; $d < scalar(@secfields); $d++) {
152             # Identify fields in base which are found in sec; store in seen-hash.
153             # Along the way build a look-up table %secpresent whose
154             # value is the name of a field in sec which is *also* found in base,
155             # and whose
156             # key is the index that that value *originally* had in sec's @fields
157 46         74 foreach my $c (@basefields) {
158 214 100       430 if ($c eq $secfields[$d]) {
159 24         41 $seen{$secfields[$d]} = 1;
160             # $secfields[$d] is member of @basefields
161 24         73 $secpresent{$d} = $secfields[$d];
162 24         33 last;
163             }
164             }
165             # If a field in sec is *not* found in base,
166             # add field's name to list of fields to be added
167 46 100       127 push @additions, $secfields[$d] unless ($seen{$secfields[$d]});
168            
169             # Build a look-up table %secneeded whose
170             # value is the name of a field to be added,
171             # and whose key is the index that that value
172             # *originally* had in sec's @fields
173 46         96 foreach my $j (@additions) {
174 42 100       188 $secneeded{$d} = $j if ($j eq $secfields[$d]);
175             }
176             }
177            
178             # Add those fields that need to be added
179 8         29 @total = (@basefields, @additions);
180              
181             # Return references to the combined list of 'fields' and to the
182             # two look-up tables
183 8         52 my $augmented_fields_ref = {
184             newfields => \@total,
185             secfieldspresent => \%secpresent,
186             secfieldsneeded => \%secneeded,
187             };
188 8         88 return $augmented_fields_ref;
189             }
190              
191             sub _extract_rows {
192 5     5   24 my ($self, $column, $relation, $choicesref, $fpref, $flref,
193             $_analyze_relation_subref, $_strip_non_matches_subref) = @_;
194 5         41 my %data = %$self;
195 5         43 my %fp = %$fpref;
196 5         34 my %fieldlabels = %$flref;
197 5         15 my ($inequality_ref, $dataref);
198              
199             # Analysis of $column
200             # DATA MUNGING STARTS HERE
201             # $column = lc($column);
202             # DATA MUNGING ENDS HERE
203 5 100       251 croak "Column (field) name requested does not exist in \@fields: $!"
204             unless (exists $fieldlabels{$column});
205 4         14 my $sortorder = $fp{$column}[1];
206 4         12 my $sorttype = $fp{$column}[2];
207            
208             # Analysis of $relation:
209             # &_analyze_relation passed by reference from Data::Presenter
210 4         22 ($relation, $inequality_ref) =
211             &$_analyze_relation_subref($relation, $sorttype);
212              
213             # Analysis of @choices (partial)
214 4         24 my $choice = '';
215 4         9 my @corrected = ();
216 4         9 my %seen = ();
217 2         157 croak "Too many choices for less than\/greater than comparison: $!"
218 4 100 100     33 if (scalar(@$choicesref) > 1 && ${$inequality_ref}{$relation});
219 3         11 foreach $choice (@$choicesref) {
220             # DATA MUNGING STARTS HERE
221             # $choice = uc($choice); # because all data in 'census.txt' is u.c.
222             # DATA MUNGING ENDS HERE
223 4         8 push(@corrected, $choice);
224 4         16 $seen{$choice} = 1;
225             }
226              
227             # Strip out non-matching rows:
228             # &_strip_non_matches passed by reference from Data::Presenter
229 3         19 $dataref = &$_strip_non_matches_subref(
230             \%data, \%fieldlabels, $column, $relation, \@corrected, \%seen);
231 3         24 return $dataref;
232             }
233              
234             1;
235              
236             ############################## DOCUMENTATION ##############################
237              
238             =head1 NAME
239              
240             Data::Presenter::Combo
241              
242             =head1 VERSION
243              
244             This document refers to version 1.03 of Data::Presenter::Combo, released
245             February 10, 2008.
246              
247             =head1 DESCRIPTION
248              
249             This package is a subclass of, and inherits from, Data::Presenter. Please see the Data::Presenter documentation to learn how to use Data::Presenter::Combo.
250              
251             =head1 AUTHOR
252              
253             James E. Keenan (jkeenan@cpan.org).
254              
255             Creation date: October 28, 2001. Last modification date: February 10, 2008.
256             Copyright (c) 2001-5 James E. Keenan. United States. All rights reserved.
257              
258             All data presented in this documentation or in the sample files in the
259             archive accompanying this documentation are dummy copy. The data was
260             entirely fabricated by the author for heuristic purposes. Any resemblance
261             to any person, living or dead, is coincidental.
262              
263             This is free software which you may distribute under the same terms as
264             Perl itself.
265              
266             =cut
267