line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package RapidApp::Spreadsheet::ExcelTableWriter; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 ExcelTableWriter |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Convenience object for writing a table into an Excel worksheet. |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
ExcelTableWriter does not manage the excel file, and instead takes parameters of the |
8
|
|
|
|
|
|
|
workbook and worksheet objects to use. This allows quite a bit of flexibility. |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
my $xls= Excel::Writer::XLSX->new($fh); |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
my $tw= RapidApp::Spreadsheet::ExcelTableWriter->new( |
13
|
|
|
|
|
|
|
wbook => $xls, |
14
|
|
|
|
|
|
|
wsheet => $xls->add_worksheet("MyData"), |
15
|
|
|
|
|
|
|
columns => [ 'Foo', 'Bar', 'Baz' ] |
16
|
|
|
|
|
|
|
); |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
my $tw= RapidApp::Spreadsheet::ExcelTableWriter->new( |
19
|
|
|
|
|
|
|
wbook => $xls, |
20
|
|
|
|
|
|
|
wsheet => $xls->add_worksheet("MyData"), |
21
|
|
|
|
|
|
|
columns => [ |
22
|
|
|
|
|
|
|
{ name => 'foo_1', label => 'Foo', isString => 0 }, |
23
|
|
|
|
|
|
|
{ name => 'bar', label => 'Bar', format => $xls->add_format(bold => 1) }, |
24
|
|
|
|
|
|
|
{ name => 'baz', label => 'BAAAAZZZZZ!' }, |
25
|
|
|
|
|
|
|
], |
26
|
|
|
|
|
|
|
headerFormat => $xls->add_format(bold => 1, underline => 1, italic => 1), |
27
|
|
|
|
|
|
|
); |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$tw->writePreamble("Some descriptive text at the top of the file"); |
30
|
|
|
|
|
|
|
$tw->writePreamble; |
31
|
|
|
|
|
|
|
$tw->writeHeaders; # optional so long as writeRow gets called |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
$tw->writeRow(1, 'John Doe', '1234 Reading Rd'); |
34
|
|
|
|
|
|
|
$tw->writeRow( [ 2, 'Bob Smith', '1234 Eagle Circle'); |
35
|
|
|
|
|
|
|
$tw->writeRow( { foo_1 => 3, bar => 'Rand AlThor', baz => 'Royal Palace, Cairhien' } ); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
$tw->autosizeColumns; |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=cut |
40
|
|
|
|
|
|
|
|
41
|
4
|
|
|
4
|
|
28
|
use strict; |
|
4
|
|
|
|
|
12
|
|
|
4
|
|
|
|
|
122
|
|
42
|
4
|
|
|
4
|
|
21
|
use warnings; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
100
|
|
43
|
4
|
|
|
4
|
|
26
|
use Moose; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
54
|
|
44
|
|
|
|
|
|
|
|
45
|
4
|
|
|
4
|
|
27812
|
use Spreadsheet::ParseExcel; |
|
4
|
|
|
|
|
148647
|
|
|
4
|
|
|
|
|
141
|
|
46
|
4
|
|
|
4
|
|
1899
|
use RapidApp::Spreadsheet::ExcelTableWriter::ColDef; |
|
4
|
|
|
|
|
15
|
|
|
4
|
|
|
|
|
980
|
|
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
has 'wbook' => ( is => 'ro', isa => 'Excel::Writer::XLSX::Workbook', required => 1 ); |
49
|
|
|
|
|
|
|
has 'wsheets' => ( is => 'ro', isa => 'ArrayRef', required => 1 ); |
50
|
|
|
|
|
|
|
has 'columns' => ( is => 'rw', isa => 'ArrayRef', required => 1 ); |
51
|
|
|
|
|
|
|
has 'rowStart' => ( is => 'rw', isa => 'Int', required => 1, default => 0 ); |
52
|
|
|
|
|
|
|
has 'colStart' => ( is => 'rw', isa => 'Int', required => 1, default => 0 ); |
53
|
|
|
|
|
|
|
has 'headerFormat' => ( is => 'rw', lazy_build => 1 ); |
54
|
|
|
|
|
|
|
has 'ignoreUnknownRowKeys' => ( is => 'rw', isa => 'Bool', default => 0 ); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub _build_headerFormat { |
57
|
0
|
|
|
0
|
|
|
my $self= shift; |
58
|
0
|
|
|
|
|
|
return $self->wbook->add_format(bold => 1, bottom => 1); |
59
|
|
|
|
|
|
|
} |
60
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
sub colCount { |
62
|
0
|
|
|
0
|
0
|
|
my $self= shift; |
63
|
0
|
|
|
|
|
|
return scalar(@{$self->columns}); |
|
0
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
around 'BUILDARGS' => sub { |
67
|
|
|
|
|
|
|
my $orig= shift; |
68
|
|
|
|
|
|
|
my $class= shift; |
69
|
|
|
|
|
|
|
my $args= $class->$orig(@_); |
70
|
|
|
|
|
|
|
if (defined $args->{wsheet}) { |
71
|
|
|
|
|
|
|
$args->{wsheets}= [ $args->{wsheet} ]; |
72
|
|
|
|
|
|
|
delete $args->{wsheet}; |
73
|
|
|
|
|
|
|
} |
74
|
|
|
|
|
|
|
return $args; |
75
|
|
|
|
|
|
|
}; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
sub numWsRequired($) { |
78
|
0
|
|
|
0
|
0
|
|
my ($unused, $numCols)= @_; |
79
|
4
|
|
|
4
|
|
37
|
use integer; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
40
|
|
80
|
0
|
|
|
|
|
|
return ($numCols+255) / 256; |
81
|
|
|
|
|
|
|
} |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub BUILD { |
84
|
0
|
|
|
0
|
0
|
|
my $self= shift; |
85
|
|
|
|
|
|
|
|
86
|
0
|
|
|
|
|
|
my $numWsNeeded= $self->numWsRequired(scalar(@{$self->columns})); |
|
0
|
|
|
|
|
|
|
87
|
0
|
|
|
|
|
|
$numWsNeeded <= scalar(@{$self->wsheets}) |
88
|
0
|
0
|
|
|
|
|
or die "Not enough worksheets allocated for ExcelTableWriter (got ".scalar(@{$self->wsheets}).", require $numWsNeeded)"; |
|
0
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
|
90
|
0
|
|
|
|
|
|
for (my $i= 0; $i < scalar(@{$self->columns}); $i++) { |
|
0
|
|
|
|
|
|
|
91
|
0
|
|
|
|
|
|
my $val= $self->columns->[$i]; |
92
|
|
|
|
|
|
|
# convert hashes into the proper object |
93
|
0
|
0
|
|
|
|
|
ref $val eq 'HASH' and |
94
|
|
|
|
|
|
|
$self->columns->[$i]= RapidApp::Spreadsheet::ExcelTableWriter::ColDef->new($val); |
95
|
|
|
|
|
|
|
# convert scalars into names (and labels) |
96
|
0
|
0
|
|
|
|
|
ref $val eq '' and |
97
|
|
|
|
|
|
|
$self->columns->[$i]= RapidApp::Spreadsheet::ExcelTableWriter::ColDef->new(name => $val); |
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head2 curRow |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
Returns the next row that will be written by a call to writePreamble, writeHeadrs, or writeRow. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
This value is read-only |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub curRow { |
110
|
0
|
|
|
0
|
1
|
|
my $self= shift; |
111
|
0
|
0
|
|
|
|
|
defined $self->{_curRow} and return $self->{_curRow}; |
112
|
0
|
|
|
|
|
|
return $self->rowStart; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
has '_documentStarted' => ( is => 'rw' ); |
116
|
|
|
|
|
|
|
has '_dataStarted' => ( is => 'rw' ); |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head2 excelColIdxToLetter |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
print RapidApp::Spreadsheet::ExcelTableWriter->excelColIdxToLetter(35); |
121
|
|
|
|
|
|
|
# prints AM |
122
|
|
|
|
|
|
|
print $tableWriter->excelColIdxToLetter(0); |
123
|
|
|
|
|
|
|
# prints A |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=cut |
126
|
|
|
|
|
|
|
|
127
|
4
|
|
|
4
|
|
1028
|
use Spreadsheet::ParseExcel::Utility 'int2col'; |
|
4
|
|
|
|
|
16
|
|
|
4
|
|
|
|
|
475
|
|
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub excelColIdxToLetter($) { |
130
|
0
|
|
|
0
|
1
|
|
my ($ignored, $colNum)= @_; |
131
|
0
|
|
|
|
|
|
return int2col($colNum); |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
sub sheetForCol { |
135
|
0
|
|
|
0
|
0
|
|
my ($self, $colIdx)= @_; |
136
|
4
|
|
|
4
|
|
35
|
use integer; |
|
4
|
|
|
|
|
8
|
|
|
4
|
|
|
|
|
17
|
|
137
|
0
|
|
|
|
|
|
$colIdx+= $self->colStart; |
138
|
0
|
|
|
|
|
|
return $self->wsheets->[$colIdx / 256], $colIdx%256; |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub _applyColumnFormats { |
142
|
0
|
|
|
0
|
|
|
my $self= shift; |
143
|
|
|
|
|
|
|
|
144
|
0
|
|
|
|
|
|
for (my $i=0; $i < $self->colCount; $i++) { |
145
|
0
|
|
|
|
|
|
my $fmt= $self->columns->[$i]->format; |
146
|
0
|
0
|
|
|
|
|
my $wid= $self->columns->[$i]->width eq 'auto'? undef : $self->columns->[$i]->width; |
147
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
|
my ($wsheet, $sheetCol)= $self->sheetForCol($i); |
149
|
0
|
|
|
|
|
|
$wsheet->set_column($sheetCol, $sheetCol, $wid, $fmt); |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub prepareDocument { |
154
|
0
|
|
|
0
|
0
|
|
my $self= shift; |
155
|
0
|
0
|
|
|
|
|
!$self->_documentStarted or die 'column formats can only be applied before the first "write"'; |
156
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
|
$self->_applyColumnFormats(); |
158
|
0
|
|
|
|
|
|
$self->_documentStarted(1); |
159
|
|
|
|
|
|
|
} |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head2 writePreamble |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
writePreamble writes each of its arguments into an Excel cell from left to right, and then |
164
|
|
|
|
|
|
|
increments the current row. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
The only purpose of this routine is to conveniently increment the starting row while writing |
167
|
|
|
|
|
|
|
various bits of text at the start of the worksheet. |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
=cut |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub writePreamble { |
172
|
0
|
|
|
0
|
1
|
|
my ($self, @args)= @_; |
173
|
0
|
0
|
|
|
|
|
!$self->_dataStarted or die 'Preamble must come before headers and data'; |
174
|
|
|
|
|
|
|
|
175
|
0
|
0
|
|
|
|
|
$self->_documentStarted or $self->prepareDocument; |
176
|
0
|
|
|
|
|
|
for (my $i=0; $i < scalar(@args); $i++) { |
177
|
0
|
|
|
|
|
|
my ($ws, $wsCol)= $self->sheetForCol($i); |
178
|
0
|
|
|
|
|
|
$ws->write($self->curRow, $wsCol, $args[$i]); |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
|
$self->{_curRow}++; |
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
=head2 writeHeaders |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
writeHeaders takes no parameters and returns nothing. It simply writes out the column header row |
186
|
|
|
|
|
|
|
in the current headerFormat, and changes the state of the object to "writing rows". |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
writeheaders can only be called once. No more writePreamble calls can be made after writeHeaders. |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
=cut |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub writeHeaders { |
193
|
0
|
|
|
0
|
1
|
|
my $self= shift; |
194
|
0
|
0
|
|
|
|
|
!$self->_dataStarted or die 'Headers cannot be written twice'; |
195
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
$self->_documentStarted or $self->prepareDocument; |
197
|
0
|
|
|
|
|
|
for (my $i=0; $i < $self->colCount; $i++) { |
198
|
0
|
|
|
|
|
|
my ($ws, $wsCol)= $self->sheetForCol($i); |
199
|
0
|
|
|
|
|
|
$ws->write_string($self->curRow, $wsCol, $self->columns->[$i]->label, $self->headerFormat); |
200
|
0
|
|
|
|
|
|
$self->columns->[$i]->updateWidest(length($self->columns->[$i]->label)*1.2); |
201
|
|
|
|
|
|
|
} |
202
|
0
|
|
|
|
|
|
$self->_dataStarted(1); |
203
|
0
|
|
|
|
|
|
$self->{_curRow}++; |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=head2 writeRow |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
$tableWriter->writeRow( \@rowdata ); |
212
|
|
|
|
|
|
|
$tableWriter->writeRow( { col1_name => col1_val, col2_name => col2_val ... } ); |
213
|
|
|
|
|
|
|
$tableWriter->writeRow( @rowData ); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=over |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=item Arguments: \@rowdata or \%rowhash or @rowdata |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=item Returns: true |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=back |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
The most optimal parameter is an array of elements in the same order as the columns were defined. |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Alternatively, a hash can be used, with the name of the columns as keys. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
If the first parameter is not a array/hash reference, the argument array is treated as the data array. |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
=cut |
230
|
|
|
|
|
|
|
our $writeRowFormat; #<-- quick/dirty global var for 'format' (see $workbook->add_format in Excel::Writer::XLSX) |
231
|
|
|
|
|
|
|
sub writeRow { |
232
|
0
|
|
|
0
|
1
|
|
my $self= shift; |
233
|
0
|
|
|
|
|
|
my $rowData; |
234
|
0
|
0
|
|
|
|
|
if (ref $_[0] eq 'ARRAY') { |
|
|
0
|
|
|
|
|
|
235
|
0
|
|
|
|
|
|
$rowData= $_[0]; |
236
|
|
|
|
|
|
|
} elsif (ref $_[0] eq 'HASH') { |
237
|
0
|
|
|
|
|
|
$rowData= $self->rowHashToArray($_[0]); |
238
|
|
|
|
|
|
|
} else { |
239
|
0
|
|
|
|
|
|
$rowData= [ @_ ]; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
0
|
0
|
|
|
|
|
$self->_dataStarted or $self->writeHeaders; |
243
|
|
|
|
|
|
|
|
244
|
0
|
|
|
|
|
|
for (my $i=0; $i < $self->colCount; $i++) { |
245
|
0
|
|
|
|
|
|
my ($ws, $wsCol)= $self->sheetForCol($i); |
246
|
|
|
|
|
|
|
|
247
|
0
|
|
|
|
|
|
my @args = ($self->curRow, $wsCol, $rowData->[$i]); |
248
|
0
|
0
|
|
|
|
|
push @args, $writeRowFormat if ($writeRowFormat); |
249
|
|
|
|
|
|
|
|
250
|
0
|
|
|
|
|
|
$ws->write(@args); |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# -- this logic is dumb and doesn't work right. 'write' already does smart setting of the |
253
|
|
|
|
|
|
|
# type. (commented out by HV on 2012-05-26) |
254
|
|
|
|
|
|
|
#if ($self->columns->[$i]->isString) { |
255
|
|
|
|
|
|
|
# $ws->write_string(@args); |
256
|
|
|
|
|
|
|
#} else { |
257
|
|
|
|
|
|
|
# $ws->write(@args); |
258
|
|
|
|
|
|
|
#} |
259
|
|
|
|
|
|
|
# -- |
260
|
|
|
|
|
|
|
|
261
|
0
|
0
|
|
|
|
|
$self->columns->[$i]->updateWidest(length $rowData->[$i]) if (defined $rowData->[$i]); |
262
|
|
|
|
|
|
|
} |
263
|
0
|
|
|
|
|
|
$self->{_curRow}++; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
sub rowHashToArray { |
267
|
0
|
|
|
0
|
0
|
|
my ($self, $hash)= @_; |
268
|
0
|
|
|
|
|
|
my $result= []; |
269
|
0
|
|
|
|
|
|
my $seen= 0; |
270
|
0
|
|
|
|
|
|
for my $col (@{$self->columns}) { |
|
0
|
|
|
|
|
|
|
271
|
0
|
0
|
|
|
|
|
exists $hash->{$col->name} and $seen++; |
272
|
0
|
|
|
|
|
|
push @$result, $hash->{$col->name}; |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
# elaborate error check, to be helpful.... |
276
|
0
|
0
|
0
|
|
|
|
if (!$self->ignoreUnknownRowKeys && scalar(keys(%$hash)) != $seen) { |
277
|
0
|
|
|
|
|
|
my %tmphash= %$hash; |
278
|
0
|
|
|
|
|
|
map { delete $tmphash{$_->name} } @{$self->columns}; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
279
|
0
|
|
|
|
|
|
warn "Unused keys in row hash: ".join(',',keys(%tmphash)); |
280
|
|
|
|
|
|
|
} |
281
|
0
|
|
|
|
|
|
return $result; |
282
|
|
|
|
|
|
|
} |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=head2 autosizeColumns |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
$tableWriter->writeRow |
287
|
|
|
|
|
|
|
$tableWriter->writeRow |
288
|
|
|
|
|
|
|
$tableWriter->writeRow |
289
|
|
|
|
|
|
|
... |
290
|
|
|
|
|
|
|
$tableWriter->autosizeColumns |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=over |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=item Arguments: none |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=item Returns: none |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
AutosizeColumns should be called after all data has been written. As each row is written, a |
299
|
|
|
|
|
|
|
max width is updated per column. Calling autosizeColumns sets the excel column width to these |
300
|
|
|
|
|
|
|
maximum values. |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
=back |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=cut |
305
|
|
|
|
|
|
|
sub autosizeColumns { |
306
|
0
|
|
|
0
|
1
|
|
my $self= shift; |
307
|
0
|
|
|
|
|
|
for (my $i=0; $i < $self->colCount; $i++) { |
308
|
0
|
0
|
|
|
|
|
if ($self->columns->[$i]->width eq 'auto') { |
309
|
0
|
|
|
|
|
|
my ($ws, $wsCol)= $self->sheetForCol($i); |
310
|
0
|
|
|
|
|
|
$ws->set_column($wsCol, $wsCol, $self->columns->[$i]->widest+.5); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
1; |