File Coverage

blib/lib/Catalyst/View/CSV.pm
Criterion Covered Total %
statement 66 66 100.0
branch 18 24 75.0
condition 4 6 66.6
subroutine 9 9 100.0
pod 2 2 100.0
total 99 107 92.5


line stmt bran cond sub pod time code
1             package Catalyst::View::CSV;
2              
3             # Copyright (C) 2011 Michael Brown <mbrown@fensystems.co.uk>.
4             #
5             # This program is free software. You can redistribute it and/or modify
6             # it under the same terms as Perl itself.
7             #
8             # This program is distributed in the hope that it will be useful, but
9             # WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11              
12             =head1 NAME
13              
14             Catalyst::View::CSV - CSV view class
15              
16             =head1 SYNOPSIS
17              
18             # Create MyApp::View::CSV using the helper:
19             script/create.pl view CSV CSV
20              
21             # Create MyApp::View::CSV manually:
22             package MyApp::View::CSV;
23             use base qw ( Catalyst::View::CSV );
24             __PACKAGE__->config ( sep_char => ",", suffix => "csv" );
25             1;
26              
27             # Return a CSV view from a controller:
28             $c->stash ( columns => [ qw ( Title Date ) ],
29             cursor => $c->model ( "FilmDB::Film" )->cursor,
30             current_view => "CSV" );
31             # or
32             $c->stash ( columns => [ qw ( Title Date ) ],
33             data => [
34             [ "Dead Poets Society", "1989" ],
35             [ "Stage Beauty", "2004" ],
36             ...
37             ],
38             current_view => "CSV" );
39              
40             =head1 DESCRIPTION
41              
42             L<Catalyst::View::CSV> provides a L<Catalyst> view that generates CSV
43             files.
44              
45             You can use either a Perl array of arrays, an array of hashes, an
46             array of objects, or a database cursor as the source of the CSV data.
47             For example:
48              
49             my $data = [
50             [ "Dead Poets Society", "1989" ],
51             [ "Stage Beauty", "2004" ],
52             ...
53             ];
54             $c->stash ( data => $data );
55              
56             or
57              
58             my $resultset = $c->model ( "FilmDB::Film" )->search ( ... );
59             $c->stash ( cursor => $resultset->cursor );
60              
61             The CSV file is generated using L<Text::CSV>.
62              
63             =head1 FILENAME
64              
65             The filename for the generated CSV file defaults to the last segment
66             of the request URI plus a C<.csv> suffix. For example, if the request
67             URI is C<http://localhost:3000/report> then the generated CSV file
68             will be named C<report.csv>.
69              
70             You can use the C<suffix> configuration parameter to specify the
71             suffix of the generated CSV file. You can also use the C<filename>
72             stash parameter to specify the filename on a per-request basis.
73              
74             =head1 CONFIGURATION PARAMETERS
75              
76             =head2 suffix
77              
78             The filename suffix that will be applied to the generated CSV file.
79             Defaults to C<csv>. For example, if the request URI is
80             C<http://localhost:3000/report> then the generated CSV file will be
81             named C<report.csv>.
82              
83             Set to C<undef> to prevent any manipulation of the filename suffix.
84              
85             =head2 charset
86              
87             The character set stated in the MIME type of the downloaded CSV file.
88             Defaults to C<utf-8>.
89              
90             =head2 content_type
91              
92             The Content-Type header to be set for the downloaded file.
93             Defaults to C<text/csv>.
94              
95             =head2 eol, quote_char, sep_char, etc.
96              
97             Any remaining configuration parameters are passed directly to
98             L<Text::CSV>.
99              
100             =head1 STASH PARAMETERS
101              
102             =head2 data
103              
104             An array containing the literal data to be included in the generated
105             CSV file. For example:
106              
107             # Array of arrays
108             my $data = [
109             [ "Dead Poets Society", "1989" ],
110             [ "Stage Beauty", "2004" ],
111             ];
112             $c->stash ( data => $data );
113              
114             or
115              
116             # Array of hashes
117             my $columns = [ qw ( Title Date ) ];
118             my $data = [
119             { Title => "Dead Poets Society", Date => 1989 },
120             { Title => "Stage Beauty", Date => 2004 },
121             ];
122             $c->stash ( data => $data, columns => $columns );
123              
124             or
125              
126             # Array of objects
127             my $columns = [ qw ( Title Date ) ];
128             my $data = [
129             Film->new ( Title => "Dead Poets Society", Date => 1989 ),
130             Film->new ( Title => "Stage Beauty", Date => 2004 ),
131             ];
132             $c->stash ( data => $data, columns => $columns );
133              
134             will all (assuming the default configuration parameters) generate the
135             CSV file body:
136              
137             "Dead Poets Society",1989
138             "Stage Beauty",2004
139              
140             You must specify either C<data> or C<cursor>.
141              
142             =head2 cursor
143              
144             A database cursor providing access to the data to be included in the
145             generated CSV file. If you are using L<DBIx::Class>, then you can
146             obtain a cursor from any result set using the C<cursor()> method. For
147             example:
148              
149             my $resultset = $c->model ( "FilmDB::Film" )->search ( ... );
150             $c->stash ( cursor => $resultset->cursor );
151              
152             You must specify either C<data> or C<cursor>. For large data sets,
153             using a cursor may be more efficient since it avoids copying the whole
154             data set into memory.
155              
156             =head2 columns
157              
158             An optional list of column headings. For example:
159              
160             $c->stash ( columns => [ qw ( Title Date ) ] );
161              
162             will produce the column heading row:
163              
164             Title,Date
165              
166             If no column headings are provided, the CSV file will be generated
167             without a header row (and the MIME type attributes will indicate that
168             no header row is present).
169              
170             If you are using literal data in the form of an B<array of hashes> or
171             an B<array of objects>, then you must specify C<columns>. You do not
172             need to specify C<columns> when using literal data in the form of an
173             B<array of arrays>, or when using a database cursor.
174              
175             Extracting the column names from a L<DBIx::Class> result set is
176             surprisingly non-trivial. The closest approximation is
177              
178             $c->stash ( columns => $resultset->result_source->columns );
179              
180             This will use the column names from the primary result source
181             associated with the result set. If you are doing anything even
182             remotely sophisticated, then this will not be what you want. There
183             does not seem to be any supported way to properly extract a list of
184             column names from the result set itself.
185              
186             =head2 filename
187              
188             An optional filename for the generated CSV file. For example:
189              
190             $c->stash ( data => $data, filename => "films.csv" );
191              
192             If this is not specified, then the filename will be generated from the
193             request URI and the C<suffix> configuration parameter as described
194             above.
195              
196             =cut
197              
198 1     1   2279161 use Text::CSV;
  1         13299  
  1         46  
199 1     1   10 use URI;
  1         1  
  1         26  
200 1     1   5 use base qw ( Catalyst::View );
  1         2  
  1         547  
201 1     1   12114 use mro "c3";
  1         3  
  1         9  
202 1     1   29 use strict;
  1         2  
  1         23  
203 1     1   5 use warnings;
  1         1  
  1         35  
204              
205 1     1   29 use 5.009_005;
  1         3  
206             our $VERSION = "1.8";
207              
208             __PACKAGE__->mk_accessors ( qw ( csv charset suffix content_type ) );
209              
210             sub new {
211 2     2 1 235793 ( my $self, my $app, my $arguments ) = @_;
212              
213             # Resolve configuration
214             my $config = {
215             eol => "\r\n",
216             charset => "utf-8",
217             suffix => "csv",
218             content_type => "text/csv",
219 2         8 %{ $self->config },
  2         8  
220             %$arguments,
221             };
222 2         1964 $self = $self->next::method ( $app, $config );
223              
224             # Record character set
225 2         3466 $self->charset ( $config->{charset} );
226 2         636 delete $config->{charset};
227              
228             # Record suffix
229 2         15 $self->suffix ( $config->{suffix} );
230 2         526 delete $config->{suffix};
231              
232             # Record content-type
233 2         15 $self->content_type( $config->{content_type} );
234 2         633 delete $config->{content_type};
235              
236             # Create underlying Text::CSV object
237 2         4 delete $config->{catalyst_component_name};
238 2 50       20 my $csv = Text::CSV->new ( $config )
239             or die "Cannot use CSV view: ".Text::CSV->error_diag();
240 2         443 $self->csv ( $csv );
241              
242 2         727 return $self;
243             }
244              
245             sub process {
246 30     30 1 623522 ( my $self, my $c ) = @_;
247              
248             # Extract instance parameters
249 30         175 my $charset = $self->charset;
250 30         4024 my $suffix = $self->suffix;
251 30         3147 my $csv = $self->csv;
252 30         3128 my $content_type = $self->content_type;
253              
254             # Extract stash parameters
255 30         3044 my $columns = $c->stash->{columns};
256             die "No cursor or inline data provided\n"
257 30 50 66     1844 unless exists $c->stash->{data} || exists $c->stash->{cursor};
258 30         2874 my $data = $c->stash->{data};
259 30         1692 my $cursor = $c->stash->{cursor};
260 30         1620 my $filename = $c->stash->{filename};
261              
262             # Determine resulting CSV filename
263 30 100       1676 if ( ! defined $filename ) {
264 25   66     82 $filename = ( [ $c->req->uri->path_segments ]->[-1] ||
265             [ $c->req->uri->path_segments ]->[-2] );
266 25 50       2876 if ( $suffix ) {
267 25         64 $filename =~ s/\.[^.]*$//;
268 25         66 $filename .= ".".$suffix;
269             }
270             }
271              
272             # Set HTTP headers
273 30         629 my $response = $c->response;
274 30         319 my $headers = $response->headers;
275 30 100       3004 my @content_type = ( $content_type,
276             "header=".( $columns ? "present" : "absent" ),
277             "charset=".$charset );
278 30         178 $headers->content_type ( join ( "; ", @content_type ) );
279 30         730 $headers->header ( "Content-disposition",
280             "attachment; filename=".$filename );
281              
282             # Generate CSV file
283 30 100       2005 if ( $columns ) {
284 25 50       333 $csv->print ( $response, $columns )
285             or die "Could not print column headings: ".$csv->error_diag."\n";
286             }
287 30 100       79606 if ( $data ) {
288 10         45 foreach my $row ( @$data ) {
289 50 100       85979 if ( ref $row eq "ARRAY" ) {
    100          
290             # No futher processing required
291             } elsif ( ref $row eq "HASH" ) {
292 10         87 $row = [ @$row{@$columns} ];
293             } else {
294 10         45 $row = [ map { $row->$_ } @$columns ];
  20         658  
295             }
296 50 50       2037 $csv->print ( $response, $row )
297             or die "Could not generate row data: ".$csv->error_diag."\n";
298             }
299             } else {
300 20         75 while ( ( my @row = $cursor->next ) ) {
301 80 50       210746 $csv->print ( $response, \@row )
302             or die "Could not generate row data: ".$csv->error_diag."\n";
303             }
304             }
305              
306 30         64544 return 1;
307             }
308              
309             =head1 AUTHOR
310              
311             Michael Brown <mbrown@fensystems.co.uk>
312              
313             =head1 LICENSE
314              
315             This library is free software. You can redistribute it and/or modify
316             it under the same terms as Perl itself.
317              
318             =cut
319              
320             1;