1 package SL::Helper::Csv;
9 use Params::Validate qw(:all);
10 use Rose::Object::MakeMethods::Generic scalar => [ qw(
11 file encoding sep_char quote_char escape_char header profile class
12 numberformat dateformat ignore_unknown_columns _io _csv _objects _parsed
21 my %params = validate(@_, {
22 sep_char => { default => ';' },
23 quote_char => { default => '"' },
24 escape_char => { default => '"' },
25 header => { type => ARRAYREF, optional => 1 },
26 profile => { type => HASHREF, optional => 1 },
32 ignore_unknown_columns => 0,
34 my $self = bless {}, $class;
36 $self->$_($params{$_}) for keys %params;
38 $self->_io(IO::File->new);
39 $self->_csv(Text::CSV->new({
41 sep_char => $self->sep_char,
42 quote_char => $self->quote_char,
43 escape_char => $self->escape_char,
52 my ($self, %params) = @_;
55 return if ! $self->_check_header;
56 return if $self->class && ! $self->_check_header_for_class;
57 return if ! $self->_parse_data;
68 my ($self, %params) = @_;
69 croak 'no class given' unless $self->class;
70 croak 'must parse first' unless $self->_parsed;
72 $self->_make_objects unless $self->_objects;
73 return wantarray ? @{ $self->_objects } : $self->_objects;
87 my ($self, %params) = @_;
89 $self->encoding($self->_guess_encoding) if !$self->encoding;
91 $self->_io->open($self->file, '<' . $self->_encode_layer)
92 or die "could not open file " . $self->file;
98 my ($self, %params) = @_;
99 return $self->header if $self->header;
101 my $header = $self->_csv->getline($self->_io);
104 $self->_csv->error_input,
105 $self->_csv->error_diag,
109 $self->header($header);
112 sub _check_header_for_class {
113 my ($self, %params) = @_;
116 carp 'this should never be called without' unless $self->class;
118 if ($self->ignore_unknown_columns) {
120 for my $method (@{ $self->header }) {
121 push @new_header, $self->class->can($self->_real_method($method))
125 $self->header(\@new_header);
129 for my $method (@{ $self->header }) {
131 next if $self->class->can($self->_real_method($method));
136 "header field $method is not recognized",
142 $self->_push_error(@errors);
148 my ($self, %params) = @_;
151 $self->_csv->column_names(@{ $self->header });
154 my $row = $self->_csv->getline($self->_io);
155 last if $self->_csv->eof;
159 @hr{@{ $self->header }} = @$row;
163 $self->_csv->error_input,
164 $self->_csv->error_diag,
165 $self->_io->input_line_number,
170 $self->_data(\@data);
171 $self->_push_error(@errors);
177 ':encoding(' . $_[0]->encoding . ')';
181 my ($self, %params) = @_;
184 eval "require " . $self->class;
185 local $::myconfig{numberformat} = $self->numberformat if $self->numberformat;
186 local $::myconfig{dateformat} = $self->dateformat if $self->dateformat;
188 for my $line (@{ $self->_data }) {
189 push @objs, $self->class->new(
191 $self->_real_method($_) => $line->{$_}
192 } grep { $_ } keys %$line
196 $self->_objects(\@objs);
200 my ($self, $arg) = @_;
201 ($self->profile && $self->profile->{$arg}) || $arg;
204 sub _guess_encoding {
210 my ($self, @errors) = @_;
211 my @new_errors = ($self->errors, @errors);
212 $self->_errors(\@new_errors);
224 SL::Helper::Csv - take care of csv file uploads
230 my $csv = SL::Helper::Csv->new(
231 file => \$::form->{upload_file},
232 encoding => 'utf-8', # undef means utf8
233 sep_char => ',', # default ';'
234 quote_char => ''', # default '"'
235 header => [qw(id text sellprice word)] # see later
236 profile => { sellprice => 'sellprice_as_number' }
237 class => 'SL::DB::CsvLine', # if present, map lines to this
240 my $status = $csv->parse;
241 my $hrefs = $csv->get_data;
242 my @objects = $scv->get_objects;
248 Text::CSV offeres already good functions to get lines out of a csv file, but in
249 most cases you will want those line to be parsed into hashes or even objects,
250 so this model just skips ahead and gives you objects.
252 Encoding autodetection is not easy, and should not be trusted. Try to avoid it
261 Standard constructor. You can use this to set most of the data.
265 Do the actual work. Will return true ($self actually) if success, undef if not.
269 Parse the data into objects and return those.
271 This method will return list or arrayref depending on context.
275 Returns an arrayref of the raw lines as hashrefs.
279 Return all errors that came up druing parsing. See error handling for detailed
290 The file which contents are to be read. Can be a name of a physical file or a
291 scalar ref for memory data.
295 Encoding of the CSV file. Note that this module does not do any encoding
296 guessing. Know what your data ist. Defaults to utf-8.
304 Same as in L<Text::CSV>
306 =item C<header> \@FIELDS
308 Can be an array of columns, in this case the first line is not used as a
309 header. Empty header fields will be ignored in objects.
311 =item C<profile> \%ACCESSORS
313 May be used to map header fields to custom accessors. Example:
315 { listprice => listprice_as_number }
317 In this case C<listprice_as_number> will be used to read in values from the
322 If present, the line will be handed to the new sub of this class,
323 and the return value used instead of the line itself.
325 =item C<ignore_unknown_columns>
327 If set, the import will ignore unkown header columns. Useful for lazy imports,
328 but deactivated by default.
332 =head1 ERROR HANDLING
334 After parsing a file all errors will be accumulated into C<errors>.
336 Each entry is an arrayref with the following structure:
339 0 offending raw input,
340 1 Text::CSV error code if T:C error, 0 else,
343 4 estimated line in file,
346 Note that the last entry can be off, but will give an estimate.
354 sep_char, quote_char, and escape_char are passed to Text::CSV on creation.
355 Changing them later has no effect currently.
359 Encoding errors are not dealt with properly.
365 Dispatch to child objects, like this:
367 $csv = SL::Helper::Csv->new(
369 class => SL::DB::Part,
384 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>