source: trunk/LATMOS-Accounts-Web/lib/Catalyst/View/CSV.pm @ 2361

Last change on this file since 2361 was 2124, checked in by nanardon, 7 years ago

Allow to outut data as CSV

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