File Coverage

blib/lib/Spreadsheet/GenerateXLSX.pm
Criterion Covered Total %
statement 20 36 55.5
branch 0 4 0.0
condition 0 6 0.0
subroutine 7 8 87.5
pod 0 1 0.0
total 27 55 49.0


line stmt bran cond sub pod time code
1             package Spreadsheet::GenerateXLSX;
2             $Spreadsheet::GenerateXLSX::VERSION = '0.05';
3 1     1   650 use 5.008;
  1         4  
4 1     1   6 use strict;
  1         2  
  1         19  
5 1     1   5 use warnings;
  1         2  
  1         25  
6              
7 1     1   460 use parent 'Exporter';
  1         362  
  1         6  
8              
9 1     1   63 use Carp qw/ croak /;
  1         2  
  1         52  
10 1     1   500 use Ref::Util qw/ is_arrayref is_ref /;
  1         1608  
  1         63  
11 1     1   1466 use Excel::Writer::XLSX;
  1         214986  
  1         704  
12              
13             our @EXPORT_OK = qw/ generate_xlsx /;
14              
15             my $MAX_EXCEL_COLUMN_WIDTH = 80;
16              
17              
18             my $define_formats = sub {
19             my $workbook = shift;
20             my $formats = {};
21              
22             my @common_settings = (
23             size => 12,
24             text_wrap => 1,
25             align => 'left',
26             );
27              
28             $formats->{header} = $workbook->add_format(@common_settings,
29             bold => 1,
30             color => 'black',
31             );
32              
33             $formats->{cell} = $workbook->add_format(@common_settings,
34             bold => 0,
35             color => 'gray',
36             );
37              
38             return $formats;
39             };
40              
41             my $find_sheet_dimensions = sub {
42             my $data = shift;
43             my $nrows = int(@$data);
44             my $n_header_cols = int(@{ $data->[0] });
45             my $ncols = $n_header_cols;
46             my $s = $n_header_cols == 1 ? '' : 's';
47              
48             foreach my $row (@$data) {
49             $ncols = int(@$row) if int(@$row) > $ncols;
50             }
51              
52             # If there are data rows with more columns than there are
53             # header columns, then we let the caller know, because the
54             # auto filters will look a bit goofy
55             if ($ncols > $n_header_cols) {
56             # TODO: this should be a carp, but need to skip a call frame
57             warn "generate_xlsx(): you gave me $n_header_cols header column$s, ",
58             "but at least one row has $ncols columns.\n";
59             }
60              
61             return ($nrows, $ncols);
62             };
63              
64             my $set_column_widths = sub {
65             my ($sheet, $widths_ref) = @_;
66             my $col_num = 0;
67              
68             foreach my $width (@$widths_ref) {
69             # This is a heuristic (ok, nasty hack) for approximating the column
70             # width in whatever these excel units are, based on the number of chars.
71             # It works well enough most of the time.
72             my $column_width = 11 + 1.1 * ($width > 9 ? ($width - 9) : 1);
73             if ($column_width > $MAX_EXCEL_COLUMN_WIDTH) {
74             $column_width = $MAX_EXCEL_COLUMN_WIDTH;
75             }
76             $sheet->set_column($col_num, $col_num, $column_width);
77             $col_num++;
78             }
79             };
80              
81             my $generate_sheet = sub {
82             my ($book, $formats, $sheetname, $data) = @_;
83             my ($nrows, $ncols) = $find_sheet_dimensions->($data);
84             my $sheet = $book->add_worksheet($sheetname);
85             my $row_num = 0;
86             my @widths;
87              
88             foreach my $row (@$data) {
89             my $celltype = ($row_num == 0 ? 'header' : 'cell');
90             my $col_num = 0;
91              
92             foreach my $cell (@$row) {
93             $sheet->write($row_num, $col_num, $cell, $formats->{$celltype});
94             if (!defined($widths[$col_num]) || (defined($cell) && length($cell) > $widths[$col_num])) {
95             $widths[$col_num] = length($cell);
96             }
97             $col_num++;
98             }
99             $row_num++;
100             }
101              
102             $set_column_widths->($sheet, \@widths);
103              
104             $sheet->autofilter(0, 0, $nrows-1, $ncols-1);
105             $sheet->freeze_panes(1, 0);
106             };
107              
108             sub generate_xlsx
109             {
110 0     0 0   my $filename = shift;
111 0           my $sheet_number = 1;
112 0   0       my $book = Excel::Writer::XLSX->new( $filename )
113             || croak "failed to create workbook\n";
114 0           my $formats = $define_formats->($book);
115 0           my $sheet_name;
116              
117             # Note: if you set this then you have to write rows in order.
118             # any out-of-order writes are effectively ignored.
119             # see the doc for Excel::Writer::XLSX
120 0           $book->set_optimization;
121              
122 0           while (@_ > 0) {
123 0           my $data = shift @_;
124              
125 0 0 0       if (is_arrayref($data)) {
    0          
126 0           $sheet_name = "Sheet$sheet_number";
127             }
128             elsif (!is_ref($data) && @_ > 0) {
129 0           $sheet_name = $data;
130 0           $data = shift @_;
131             }
132             else {
133 0           croak "unexpected arguments -- see the documentation\n";
134             }
135              
136 0           $generate_sheet->($book, $formats, $sheet_name, $data);
137              
138 0           $sheet_number++;
139              
140             }
141              
142 0           $book->close;
143             }
144              
145             1;
146              
147             =head1 NAME
148              
149             Spreadsheet::GenerateXLSX - function to generate XLSX spreadsheet from array ref(s)
150              
151             =head1 SYNOPSIS
152              
153             use Spreadsheet::GenerateXLSX qw/ generate_xlsx /;
154              
155             my @data = (
156             ['Heading 1', 'Heading 2', 'Heading 2'],
157             ['blah', 'blah', 'blah'],
158             ['blah', 'blah', 'blah'],
159             );
160             generate_xlsx('example.xlsx', \@data);
161              
162             =head1 DESCRIPTION
163              
164             This module provides a function C which takes
165             an array of Perl data and generates a simple Excel spreadsheet
166             in the XLSX format.
167             The generated sheets have the first row frozen,
168             and auto filters enabled for every column.
169              
170             Each sheet in the spreadsheet is generated from an array of rows,
171             where each row is an arrayref.
172             The first row is treated as a header row.
173             Here's an example:
174              
175             my @sheet1 = (
176             ['Pokemon', 'Type', 'Number'],
177             ['Pikachu', 'Electric', 25],
178             ['Vulpix', 'Fire', 37],
179             ['Ditto', 'Normal', 132],
180             );
181              
182             The generated spreadsheet can have any numbers of sheets:
183              
184             generate_xslx('pokemon.xlsx', \@sheet1, \@sheet2);
185              
186             If you just pass arrayrefs, the sheets will be named B, B, etc.
187             You can also pass the name of the sheet:
188              
189             generate_xslx('pokemon.xlsx', 'All Pokemon' => \@sheet1, 'Hit List' => \@sheet2);
190              
191              
192             =head1 SEE ALSO
193              
194             The following modules can all generate the XLSX format.
195             I also wrote a L
196             which gives more details on some of these.
197              
198             =over 4
199              
200             L - the underlying module used to generate the spreadsheet.
201             Gives you full control over the spreadsheet generated, but as a result has a much
202             more complex interface.
203              
204             L - helps with formatting of cells when
205             using C or C.
206              
207             L - used to generate spreadsheets from
208             "JSON files which describe the desired content and formatting".
209             By default it generates XLSX format.
210              
211             L - converts between L objects and XLS or XLSX format spreadsheets.
212              
213             L - provides a simple interface for both reading and writing spreadsheets.
214             Minimal documentation, and what there is is written in Japanese.
215             The function for creating a spreadsheet is called `write_xls()`,
216             but it generates the XLSX format.
217              
218             =back
219              
220             The following modules only generate Microsoft's earlier xls binary format.
221              
222             =over 4
223              
224             L - provides the same interface as C,
225             but generates the XLS format.
226              
227             L - converts a database table to an XLS format spreadsheet.
228              
229             L - converts a query to an XLS spreadsheet,
230             as opposed to a table.
231              
232             L - provides a simpler OO interface
233             for generating single-sheet XLS spreadsheets.
234              
235             L - another simplified OO interface, which can write CSV or XLS output,
236             but not XLSX.
237              
238             L - a fork of C which supports more output formats
239             (CSV, XLS, HTML, XHTML, XML, ODS, and JSON), but doesn't (appear to) support XLSX.
240              
241             L - provides a simple OO interface for generating XLS spreadsheets,
242             and provides some control over the generated format.
243             But, as with most of the modules listed here, only XLS output is supported.
244              
245             L - a very simple OO interface built on C.
246             This one is close to the spirit of C, but only generates XLS.
247              
248             =back
249              
250             =head1 TODO
251              
252             * smarter auto-formatting of columns
253             * more tests
254             * better error handler
255              
256              
257             =head1 REPOSITORY
258              
259             L
260              
261              
262             =head1 AUTHOR
263              
264             Neil Bowers Eneilb@cpan.orgE
265              
266              
267             =head1 COPYRIGHT AND LICENSE
268              
269             This software is copyright (c) 2016 by Neil Bowers .
270              
271             This is free software; you can redistribute it and/or modify it under
272             the same terms as the Perl 5 programming language system itself.
273