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