File Coverage

blib/lib/Excel/Grinder.pm
Criterion Covered Total %
statement 72 75 96.0
branch 13 24 54.1
condition 5 12 41.6
subroutine 8 8 100.0
pod 3 3 100.0
total 101 122 82.7


line stmt bran cond sub pod time code
1             package Excel::Grinder;
2              
3             our $VERSION = "1.2";
4              
5             # time to grow up
6 1     1   737 use strict;
  1         2  
  1         30  
7 1     1   4 use warnings;
  1         2  
  1         22  
8 1     1   4 use Carp;
  1         2  
  1         67  
9              
10             # this stands on the feet of giants
11 1     1   1550 use Excel::Writer::XLSX;
  1         221086  
  1         53  
12 1     1   521 use Spreadsheet::XLSX;
  1         65253  
  1         767  
13              
14             # OO out of habit
15             sub new {
16 1     1 1 545 my ($class, $default_directory) = @_;
17            
18 1         3 my $path_separator = '/';
19 1         3 my $temp_directory = '/tmp';
20              
21 1 50       7 if( $^O eq "MSWin32" ){
22 0         0 $path_separator = '\\';
23 0         0 $temp_directory = $ENV{'temp'};
24             }
25             # default the default directory to /tmp/excel_grinder
26 1   33     12 $default_directory ||= $temp_directory . $path_separator . 'excel_grinder';
27            
28             # make sure that directory exists
29 1 50       233 mkdir $default_directory if !(-d $default_directory);
30            
31             # if it still does exist, bail out
32 1 50       18 croak "Error: $default_directory does not exist and cannot be auto-created." if !(-d $default_directory);
33            
34             # become!
35 1         5 my $self = bless {
36             'default_directory' => $default_directory,
37             'path_separator' => $path_separator,
38             }, $class;
39            
40 1         5 return $self;
41             }
42              
43             # method to convert a three-level array into a nice excel file
44             sub write_excel {
45             # required arguments are (1) the filename and (2) the data structure to turn into an XLSX file
46 1     1 1 433 my ($self, %args) = @_;
47             # looks like:
48             # 'filename' => 'some_file.xlsx', # will be saved under /opt/majestica/tmp/DATABASE_NAME/some_file.xlsx; required
49             # 'the_data' => @$three_level_arrayref, # worksheets->rows->columns; see below; required
50             # 'headings_in_data' => 1, # if filled, first row of each worksheet will be captialized; optional
51             # 'worksheet_names' => ['Names','of','Worksheets'], # if filled, will be the names to give the worksheets
52              
53 1         3 my ($tmp_dir, $item, $col, @bits, $workbook, $worksheet_data, $worksheet, $n, $row_array, $row_upper, $worksheet_name);
54              
55             # ensure path separator is escaped properly
56 1         5 my $separator_regex = qq(\\$self->{path_separator});
57              
58             # fail without a filename
59 1 50       4 croak 'Error: Filename required for write_excel()' if !$args{filename};
60              
61             # the data structure must be an array of arrays of arrays
62             # three levels: worksheets, rows, columns
63 1 50       3 croak 'Error: Must send a three-level arrayref (workbook->rows->columns) to write_excel()' if !$args{the_data}[0][0][0];
64              
65             # place into default_directory unless they specified a directory
66              
67 1 50       15 $args{filename} = $self->{default_directory}.$self->{path_separator}.$args{filename} if $args{filename} !~ m#$separator_regex#;
68 1 50       7 $args{filename} .= '.xlsx' if $args{filename} !~ /\.xlsx$/;
69              
70             # start our workbook
71 1         7 $workbook = Excel::Writer::XLSX->new( $args{filename} );
72              
73             # Set the format for dates.
74 1         739 my $date_format = $workbook->add_format( num_format => 'mm/dd/yy' );
75              
76             # start adding worksheets
77 1         112 foreach $worksheet_data (@{ $args{the_data} }) {
  1         3  
78 2         3 $worksheet_name = shift @{ $args{worksheet_names} }; # if it's there
  2         5  
79 2         4 $worksheet_name =~ s/[^0-9a-z\-\s]//gi; # clean it up
80 2         12 $worksheet = $workbook->add_worksheet($worksheet_name);
81            
82             # go thru each row...
83 2         659 $n = 0;
84 2         4 foreach $row_array (@$worksheet_data) {
85              
86             # do they want the first row to the headings?
87 9 100 66     42 if ($args{headings_in_data} && $n == 0) { # uppercase the first row
88 2         5 @$row_upper = map { uc($_) } @$row_array;
  6         27  
89 2         16 $row_array = $row_upper;
90             }
91            
92             # now each column...
93 9         13 $col = 0;
94 9         15 foreach $item (@$row_array) {
95             # dates are no funzies
96 27 50       1556 if ($item =~ /^(\d{4})-(\d{2})-(\d{2})$/) { # special routine for dates
97 0         0 $worksheet->write_date_time( $n, $col++, $1.'-'.$2.'-'.$3.'T', $date_format );
98             } else {
99 27         61 $worksheet->write( $n, $col++, $item );
100             }
101              
102             }
103 9         665 $n++;
104             }
105             }
106              
107             # that's not so hard, now is it?
108 1         8 return $args{filename};
109             }
110              
111             # method to import an excel file into a nice three-level array
112             sub read_excel {
113             # require argument is the filename or full path to the excel xlsx file
114             # if it's just a filename, look in the default directory
115 1     1 1 39253 my ($self,$filename) = @_;
116              
117             # ensure path separator is escaped properly
118 1         3 my $separator_regex = qq(\\$self->{path_separator});
119              
120 1 50       16 $filename = $self->{default_directory}.$self->{path_separator}.$filename if $filename !~ m#$separator_regex#;
121 1 50       7 $filename .= '.xlsx' if $filename !~ /\.xlsx$/;
122              
123             # gotta exist, after all that
124 1 50       17 croak 'Error: Must send a valid full file path to an XLSX file to read_excel()' if !(-e "$filename");
125            
126 1         3 my ($excel, $sheet_num, $sheet, $row_num, $row, @the_data, $cell, $col);
127              
128             # again, stand on the shoulders of giants
129 1         8 $excel = Spreadsheet::XLSX->new($filename);
130              
131             # read it in, sheet by sheet
132 1         17281 $sheet_num = 0;
133 1         2 foreach $sheet (@{$excel->{Worksheet}}) {
  1         3  
134              
135             # set the max = 0 if there is one or none rows
136 2   33     7 $sheet->{MaxRow} ||= $sheet->{MinRow};
137              
138             # same for the columns
139 2   33     5 $sheet->{MaxCol} ||= $sheet->{MinCol};
140              
141             # cycle through each row
142 2         3 $row_num = 0;
143 2         4 foreach $row ($sheet->{MinRow} .. $sheet->{MaxRow}) {
144             # go through each available column
145 9         17 foreach $col ($sheet->{MinCol} .. $sheet->{MaxCol}) {
146              
147             # get ahold of the actual cell object
148 27         34 $cell = $sheet->{Cells}[$row][$col];
149            
150             # next if !$cell; # skip if blank
151              
152             # add it to our nice array
153 27         29 push (@{ $the_data[$sheet_num][$row] }, $cell->{Val} );
  27         57  
154             }
155             # advance
156 9         10 $row_num++;
157             }
158 2         3 $sheet_num++;
159             }
160              
161             # send it back
162 1         30 return \@the_data;
163             }
164              
165             1;
166              
167             __END__