File Coverage

blib/lib/Spreadsheet/WriteExcel/WebPivot.pm
Criterion Covered Total %
statement 164 203 80.7
branch 19 42 45.2
condition 5 15 33.3
subroutine 19 21 90.4
pod 1 8 12.5
total 208 289 71.9


line stmt bran cond sub pod time code
1             package Spreadsheet::WriteExcel::WebPivot;
2            
3 1     1   56663 use 5.006;
  1         3  
4 1     1   6 use strict;
  1         1  
  1         17  
5 1     1   4 use warnings;
  1         10  
  1         30  
6 1     1   1295 use DBI;
  1         14867  
  1         46  
7 1     1   456 use FileHandle;
  1         8535  
  1         5  
8 1     1   784 use Switch;
  1         18914  
  1         4  
9 1     1   129143 use POSIX 'strftime';
  1         5653  
  1         7  
10            
11             our(@ISA, @EXPORT);
12             sub makewebpivot;
13            
14 1     1   1210 use Exporter 'import';
  1         2  
  1         1915  
15             @EXPORT = qw(makewebpivot);
16            
17             our $VERSION = '0.01';
18            
19             #-----------------------------------------------------------------------
20             # not needed or even that useful anymore now that we are exporting the
21             # main function
22             #
23             sub new {
24 0     0 0 0 my $class = shift;
25 0 0       0 $class = ref($class) if (ref($class));
26            
27 0         0 my $self = {};
28 0         0 bless($self, $class);
29 0         0 return $self;
30             }
31            
32            
33             # these four variables are for storing large amounts of text for
34             # a home-brew template system. The Template Toolkit is much better
35             # but I wanted to reduce module dependencies
36            
37             my ($pivotcachetext, $pivotcachefooter,$pivothdrtext,$filelisttmpl);
38            
39             #-----------------------------------------------------------------------
40             # this function is an internal function that does the proper xml
41             # escaping for hash references containing
42             # data that needs to be output as xml
43            
44             sub cleanhash4xml {
45 20     20 0 26 my $self = shift; # get class thingy
46 20         21 my $rhsh = shift;
47            
48 20 50       32 return unless(defined($rhsh));
49 20         45 my @keys = keys %$rhsh;
50             # the line below takes care of uninitialized data errors
51 20 50 33     28 map {(!exists($rhsh->{$_}) or !defined($rhsh->{$_})) ? $rhsh->{$_} = ' ': 1} @keys;
  40         112  
52 20         42 map ($rhsh->{$_} =~ s/\&/\&/g, @keys);
53 20         32 map ($rhsh->{$_} =~ s/"/\"/g, @keys);
54 20         31 map ($rhsh->{$_} =~ s/
55 20         32 map ($rhsh->{$_} =~ s/>/\>/g, @keys);
56 20         41 map ($rhsh->{$_} =~ s/[^[:alnum:][:punct:] ]//g, @keys);
57 20 50       34 if (defined($self->{types})) {
58 20         31 foreach my $key (@keys) {
59             #die "dead #".$rhsh->{$key} . "#\n" if( 'Tier' eq $key and !($rhsh->{$key} =~ /\w/));
60 40 50 33     155 if (!defined($rhsh->{$key}) or !($rhsh->{$key} =~ /\w/)) {
61 0 0       0 if ( $self->{types}->{$key} eq 'text' ) {
62 0         0 $rhsh->{$key} = 'none';
63             }
64             else {
65 0         0 $rhsh->{$key} = "0";
66             }
67             }
68             }
69             }
70             }
71            
72            
73             #-----------------------------------------------------------------------
74             sub cleanArray4xml {
75 0     0 0 0 shift; # get class thingy
76 0         0 my $rarr = shift;
77             # the line below takes care of uninitialized data errors
78 0 0       0 map {defined($_) ? $_ : ''} @$rarr;
  0         0  
79 0         0 map (s/\&/\&/g, @$rarr);
80 0         0 map (s/"/\"/g, @$rarr);
81 0         0 map (s/
82 0         0 map (s/>/\>/g, @$rarr);
83 0         0 map (s/[^[:alnum:][:punct:] ]//g, @$rarr);
84             }
85            
86            
87            
88             #-----------------------------------------------------------------------
89             sub getDataTypes {
90 1     1 0 2 my $self = shift;
91 1         1 my $href = shift;
92 1         2 my $rkeys = shift;
93 1         3 my @keys = @$rkeys;
94 1         4 my $type; my $typename;
95 1         0 my @pivotfields;
96 1         1 my $i = 1;
97 1         3 my @dkeys = keys %$href;
98 1         2 my %keysh; @keysh{@keys} = @keys;
  1         4  
99 1 50       2 map { push @keys, $_ unless(exists $keysh{$_}) } @dkeys;
  2         8  
100 1         3 foreach my $key (@keys) {
101 2 50       7 die "$key not defined\n" unless(defined $href->{$key});
102 2         4 switch ($href->{$key}) {
  2         2  
  2         8  
103 2 100       49 case /^\d+$/ {
  1         24  
104 1         2 $type = q(type="int");
105 1         2 $typename = 'int';
106 1         9 }
  0         0  
  0         0  
  0         0  
107 1 50       30 case /^\d+\.\d+$/ {
  0         0  
108 0         0 $type = q(type="float");
109 0         0 $typename = 'float';
110 0         0 }
  0         0  
  0         0  
  0         0  
111 1 50       18 case qr/^\d{4}\-\d{2}\-\d{2}/ {
  0         0  
112 0         0 $type = q(type="dateTime");
113 0         0 $typename = 'dateTime';
114 0         0 }
  0         0  
  0         0  
  0         0  
115             else {
116 1         15 $type = q(maxLength="255");
117 1         7 $typename = 'text';
118             }
119 1         2 }
120 2         8 push @pivotfields, {FIELDNAME=>$key, COLNUM=>$i++, DATATYPE=>$type};
121 2         6 $self->{types}->{$key} = $typename;
122             }
123 1         4 return @pivotfields;
124             }
125            
126             #-----------------------------------------------------------------------
127             # this function sets up the subdirectory required by Excel's web object
128             #
129             sub makepivotdir {
130 1     1 0 2 my $file = shift;
131 1         2 my $title = shift;
132 1         1 my $rkeys = shift;
133 1         2 my $summarytype = shift;
134            
135 1 50       73 mkdir $file . "_files" unless( -d $file . "_files" );
136             # if the summary flag was not set or the directory does not exist
137             # generate table main page ( as opposed to the data page )
138             # based on pivotfields
139 1 50       13 my $fh = FileHandle->new(">$file".".htm")
140             or die "Unable to open $file\n";
141            
142 1         125 printPivotHdr($fh, $title, $file, $rkeys, $summarytype);
143            
144 1         7 $fh->close;
145 1 50       56 $fh->open(">$file".'_files/filelist.xml') or
146             die "Unable to open $file _files/filelist.xml\n";
147 1         60 $filelisttmpl =~ s/CACHENAME/$file/g;
148 1         5 print $fh $filelisttmpl;
149 1         4 $fh->close;
150             }
151            
152             #-----------------------------------------------------------------------
153             # this is an internal function that takes each successive row of data
154             # and puts it in the required format
155             #
156             sub addPivotData {
157 20     20 0 26 my $self = shift;
158 20         23 my $fh = shift;
159 20         24 my $href = shift;
160 20         25 my $datarows = shift;
161 20         23 my $rkeys = shift;
162 20         24 my $i;
163            
164 20         36 $self->cleanhash4xml($href); # takes care of escaping characters.
165 20         37 my @keys = @$rkeys;
166 20         27 my $key1 = $keys[0];
167 20         43 my $keyN = $keys[$#keys];
168 20         36 my @dkeys = keys %$href;
169 20         24 my %keysh; @keysh{@keys} = @keys;
  20         40  
170 20 50       29 map { push @keys, $_ unless(exists $keysh{$_}) } @dkeys;
  40         89  
171             #print "keys: @keys\n";
172 20         24 my @datacolumns;
173 20         36 for ($i=1; my $key = shift @keys; $i++) {
174 40         111 push @datacolumns, qq(Col$i="$href->{$key}");
175             }
176 20         43 print $fh " \n";
177 20         55 return $i; # return the column count
178             }
179            
180            
181             #-----------------------------------------------------------------------
182             # this is the top level function. The only one called directly by the
183             # user.
184             #
185             sub makewebpivot {
186             #my $self = shift;
187 1     1 1 124 my $self = bless({},'Spreadsheet::WriteExcel::WebPivot');
188 1         2 my $dbh = shift; my $query = shift;
  1         2  
189 1         2 my $rquerykeys = shift;
190 1         2 my $summarytype = shift;
191 1         2 my $file = shift;
192 1         3 my $title = shift;
193            
194             # the line below allows us to pass in a reference to
195             # an array of hash refs and the code will pretend it is a
196             # DBI object and fetch each hashref in the array.
197 1 50       10 $dbh = Spreadsheet::WriteExcel::WebPivot::FakeDBI->new($dbh) if( ref($dbh) eq 'ARRAY' );
198            
199 1         17 $self->{SummaryType} = $summarytype;
200            
201 1         2 my @datarows; my @queries = ();
  1         3  
202 1 50       4 if( 'ARRAY' eq ref($query) ) {
203 0         0 @queries = @$query;
204 0         0 $query = shift @queries;
205             }
206 1         3 my $sth = $dbh->prepare($query);
207 1         3 $sth->execute;
208            
209 1         5 makepivotdir($file,$title,$rquerykeys,$summarytype);
210            
211 1         40 my $fh = FileHandle->new(">$file"."_files/$file".'_1234_cachedata001.xml');
212 1 50       90 die "Unable to open cache\n" unless($fh);
213            
214 1         3 my $href = $sth->fetchrow_hashref;
215 1         4 my @pivotfields = $self->getDataTypes($href,$rquerykeys);
216             {
217 1         1 local $/ = undef; # INPUT SEPARATOR
  1         5  
218 1         2 local $" = "\n"; # OUTPUT SEPARATOR
219 1         3 my @ncolumns = (map { qq( ) }
  2         7  
220             (1..scalar(@pivotfields))
221             );
222 1         1 my @columns;
223 1         2 map { push @columns,
  2         9  
224             qq( ),
225             qq( {DATATYPE}/>),
226             qq( ); } @pivotfields;
227 1         90 my $outtext = eval $pivotcachetext;
228 1         9 print $fh $outtext;
229             }
230 1         5 my $colcount = $self->addPivotData($fh,$href,\@datarows, $rquerykeys);
231            
232             # a bit of code gymnastics here to handle an array of query strings
233             # if there are multiple query strings we run execute each new query
234             # and run the loop again.
235 1   33     1 do {
      33        
236 1         3 while( $href = $sth->fetchrow_hashref ) {
237 19         32 $colcount = $self->addPivotData($fh,$href, \@datarows, $rquerykeys);
238             }
239 1         3 $sth->finish;
240             } while( ($query = shift @queries) && ($sth = $dbh->prepare($query)) && $sth->execute );
241            
242 1         15 print $fh $pivotcachefooter;
243 1         5 $fh->close;
244            
245             #$sth->finish;
246            
247             } # end makewebpivot
248            
249            
250             #-----------------------------------------------------------------------
251             # internal variable initialization
252             #
253            
254             $pivotcachetext = q(qq(
255             xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
256             xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
257             xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
258            
259             1
260            
261            
262             @ncolumns
263            
264            
265             @columns
266            
267            
268             ));
269            
270             $pivotcachefooter = q(
271            
272            
273            
274             );
275            
276             $filelisttmpl = q(
277            
278            
279            
280             PublicationID="CACHENAME"/>
281            
282            
283             );
284            
285             # another "role your own template" function
286             # printPivotHdr creates the file that serves as the header file for Excel XML
287             # web objects
288            
289             sub printPivotHdr {
290 1     1 0 4 my ($fh,$TITLE,$CACHENAME,$rkeys,$SUMMARYTYPE,$NOSUBTOTAL) = @_;
291             #print "Summary Type = $SUMMARYTYPE\n";
292 1 50       2 if($NOSUBTOTAL) {
293 0         0 $NOSUBTOTAL = q(<Subtotal>None</Subtotal> );
294 1         3 } else { $NOSUBTOTAL = ''; }
295            
296 1         2 my @pivotfields = @$rkeys;
297 1         3 my $DATAFIELD = $pivotfields[$#pivotfields];
298 1         1 my @pivotfieldsloop;
299 1         2 my ($POS,$FIELDNAME);
300            
301 1         3 map { $POS++; $FIELDNAME = $_;
  1         3  
  1         2  
302 1         5 push @pivotfieldsloop, qq(
303             <PivotField>
304             <Name>$FIELDNAME</Name>
305             <Orientation>Row</Orientation>
306             $NOSUBTOTAL
307             <Position>$POS</Position>
308             <PivotItem> <Name></Name>
309             <Hidden/> <HideDetail/>
310             </PivotItem> </PivotField>
311             ); } @pivotfields[0..$#pivotfields-1];
312            
313 1         76 my $TODAY = strftime '%Y-%m-%d %H:%M:%S', localtime;
314 1         6 $TODAY =~ s/ /T/;
315 1         15 $pivothdrtext =~ s/CACHENAME/$CACHENAME/gm;
316 1         23 $pivothdrtext =~ s/TITLE/$TITLE/gm;
317 1         16 $pivothdrtext =~ s/TODAY/$TODAY/gm;
318 1         12 $pivothdrtext =~ s/DATAFIELD/$DATAFIELD/gm;
319 1         10 $pivothdrtext =~ s/SUMMARYTYPE/$SUMMARYTYPE/gm;
320 1         20 $pivothdrtext =~ s/PIVOTFIELDSLOOP/@pivotfieldsloop/m;
321 1         16 print $fh $pivothdrtext;
322             #print $fh "@pivotfieldsloop\n";
323             }
324            
325             # I appologize in advance for the big, ugly inlined document that follows
326             # I would have prefered to store this text after the END marker and use
327             # the handle to access it but that doesn't work in this module file.
328            
329             $pivothdrtext = q(
330             xmlns:x="urn:schemas-microsoft-com:office:excel"
331             xmlns="http://www.w3.org/TR/REC-html40">
332            
333             TITLE
334            
335            
336            
337            
338            
339            
340            
341            
343            
345            
346            
347            
348            
349             id="CACHENAME_1234_PivotTable"
350             classid="CLSID:0002E552-0000-0000-C000-000000000046">
351             352             xmlns:o="urn:schemas-microsoft-com:office:office"
353             xmlns:x="urn:schemas-microsoft-com:office:excel"
354             xmlns:html="http://www.w3.org/TR/REC-html40">
355             <WorksheetOptions
356             xmlns="urn:schemas-microsoft-com:office:excel">
357             <Zoom>0</Zoom> <Selected/>
358             <TopRowVisible>2</TopRowVisible>
359             <Panes> <Pane>
360             <Number>3</Number>
361             <RangeSelection>$D:$D</RangeSelection>
362             </Pane> </Panes>
363             <ProtectContents>False</ProtectContents>
364             <ProtectObjects>False</ProtectObjects>
365             <ProtectScenarios>False</ProtectScenarios>
366             </WorksheetOptions> <PivotTable
367             xmlns="urn:schemas-microsoft-com:office:excel">
368             <PTSource>
369             <DataMember>XLDataSource</DataMember>
370             <CacheIndex>1</CacheIndex>
371             <VersionLastRefresh>1</VersionLastRefresh>
372             <RefreshName>perlpivot</RefreshName>
373             <CacheFile HRef="CACHENAME_files/CACHENAME_1234_cachedata001.xml"/>
374             <RefreshDate>TODAY</RefreshDate>
375             <RefreshDateCopy>TODAY</RefreshDateCopy>
376             </PTSource>
377             <Name> TITLE </Name>
378             <DataMember>XLDataSource</DataMember>
379             <ImmediateItemsOnDrop/>
380             <ShowPageMultipleItemLabel/>
381             <Location>$A$1:$D$5</Location>
382             <VersionLastUpdate>1</VersionLastUpdate>
383             <DefaultVersion>1</DefaultVersion>
384             <PivotField>
385             <Name>DATAFIELD</Name>
386             </PivotField>
387             PIVOTFIELDSLOOP
388             <PivotField> <DataField/>
389             <Name>Data</Name>
390             <Orientation>Row</Orientation>
391             <Position>-1</Position>
392             </PivotField>
393             <PivotField>
394             <Name>SUMMARYTYPE of DATAFIELD</Name>
395             <ParentField>DATAFIELD</ParentField>
396             <NumberFormat>#,##0</NumberFormat>
397             <Orientation>Data</Orientation>
398             <Function>SUMMARYTYPE</Function>
399             <Position>1</Position>
400             </PivotField> <PTFormat
401             Style='mso-number-format:"\#\,\#\#0"'>
402             <PTRule>
403             <RuleType>DataOnly</RuleType>
404             </PTRule> </PTFormat> <PTFormat
405             Style='mso-number-format:"\#\,\#\#0"'>
406             <PTRule>
407             <RuleType>Blanks</RuleType>
408             </PTRule> </PTFormat>
409             </PivotTable> </xml><![endif]-->">

410             style='margin-top:100;font-family:Arial;font-size:8.0pt'>To use this Web
411             page interactively, you must have Microsoft Internet Explorer 4.01 Service
412             Pack 1 (SP1) or later and the Microsoft Office XP Web Components.

413            

See the

414             href="http://office.microsoft.com/office/redirect/10/MSOWCPub.asp?HelpLCID=1033">Microsoft
415             Office Web site for more information.

416            
417            
418            
419            
420            
421            
422            
423             );
424            
425             package Spreadsheet::WriteExcel::WebPivot::FakeDBI;
426            
427             # the constructor
428             sub new {
429 1     1   2 my $class = shift;
430 1         2 my $arg = shift;
431 1 50 33     8 if( defined($arg) and ref($arg) eq 'ARRAY' ) {
432 1         3 bless($arg); # I don't expect anyone to ever inherit from this
433             } else {
434 0         0 $arg = [];
435 0         0 bless($arg);
436             }
437 1         2 return $arg;
438             }
439            
440             sub prepare {
441 1     1   2 my $self = shift;
442 1         2 return $self;
443             }
444            
445       1     sub execute {
446             }
447            
448       1     sub finish {
449             }
450            
451             sub fetchrow_hashref {
452 21     21   30 my $self = shift;
453 21         52 return shift @$self;
454             }
455            
456             # Autoload methods go after =cut, and are processed by the autosplit program.
457            
458             1;
459            
460             __END__