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 _io _csv _objects _parsed _data _errors
20 my %params = validate(@_, {
21 sep_char => { default => ';' },
22 quote_char => { default => '"' },
23 escape_char => { default => '"' },
24 header => { type => ARRAYREF, optional => 1 },
25 profile => { type => HASHREF, optional => 1 },
32 my $self = bless {}, $class;
34 $self->$_($params{$_}) for keys %params;
36 $self->_io(IO::File->new);
37 $self->_csv(Text::CSV->new({
39 sep_char => $self->sep_char,
40 quote_char => $self->quote_char,
41 escape_char => $self->escape_char,
50 my ($self, %params) = @_;
53 return unless $self->_check_header;
54 return unless $self->_parse_data;
65 my ($self, %params) = @_;
66 croak 'no class given' unless $self->class;
67 croak 'must parse first' unless $self->_parsed;
69 $self->_make_objects unless $self->_objects;
70 return wantarray ? @{ $self->_objects } : $self->_objects;
84 my ($self, %params) = @_;
86 $self->encoding($self->_guess_encoding) if !$self->encoding;
88 $self->_io->open($self->file, '<' . $self->_encode_layer)
89 or die "could not open file " . $self->file;
95 my ($self, %params) = @_;
96 return $self->header if $self->header;
98 my $header = $self->_csv->getline($self->_io);
101 $self->_csv->error_input,
102 $self->_csv->error_diag,
106 $self->header($header);
109 sub _check_header_for_class {
110 my ($self, %params) = @_;
113 return unless $self->class;
114 return $self->header;
116 for my $method (@{ $self->header }) {
117 next if $self->class->can($self->_real_method($method));
122 "header field $method is not recognized",
128 $self->_push_error(@errors);
134 my ($self, %params) = @_;
137 $self->_csv->column_names(@{ $self->header });
140 my $row = $self->_csv->getline($self->_io);
141 last if $self->_csv->eof;
145 @hr{@{ $self->header }} = @$row;
149 $self->_csv->error_input,
150 $self->_csv->error_diag,
151 $self->_io->input_line_number,
156 $self->_data(\@data);
157 $self->_push_error(@errors);
163 ':encoding(' . $_[0]->encoding . ')';
167 my ($self, %params) = @_;
170 eval "require " . $self->class;
171 local $::myconfig{numberformat} = $self->numberformat if $self->numberformat;
172 local $::myconfig{dateformat} = $self->dateformat if $self->dateformat;
174 for my $line (@{ $self->_data }) {
175 push @objs, $self->class->new(
177 $self->_real_method($_) => $line->{$_}
178 } grep { $_ } keys %$line
182 $self->_objects(\@objs);
186 my ($self, $arg) = @_;
187 ($self->profile && $self->profile->{$arg}) || $arg;
190 sub _guess_encoding {
196 my ($self, @errors) = @_;
197 my @new_errors = ($self->errors, @errors);
198 $self->_errors(\@new_errors);
210 SL::Helper::Csv - take care of csv file uploads
216 my $csv = SL::Helper::Csv->new(
217 file => \$::form->{upload_file},
218 encoding => 'utf-8', # undef means utf8
219 sep_char => ',', # default ';'
220 quote_char => ''', # default '"'
221 header => [qw(id text sellprice word)] # see later
222 profile => { sellprice => 'sellprice_as_number' }
223 class => 'SL::DB::CsvLine', # if present, map lines to this
226 my $status = $csv->parse;
227 my $hrefs = $csv->get_data;
228 my @objects = $scv->get_objects;
234 Text::CSV offeres already good functions to get lines out of a csv file, but in
235 most cases you will want those line to be parsed into hashes or even objects,
236 so this model just skips ahead and gives you objects.
238 Encoding autodetection is not easy, and should not be trusted. Try to avoid it
247 Standard constructor. You can use this to set most of the data.
251 Do the actual work. Will return true ($self actually) if success, undef if not.
255 Parse the data into objects and return those.
257 This method will return list or arrayref depending on context.
261 Returns an arrayref of the raw lines as hashrefs.
265 Return all errors that came up druing parsing. See error handling for detailed
276 The file which contents are to be read. Can be a name of a physical file or a
277 scalar ref for memory data.
281 Encoding of the CSV file. Note that this module does not do any encoding
282 guessing. Know what your data ist. Defaults to utf-8.
290 Same as in L<Text::CSV>
292 =item C<header> \@FIELDS
294 Can be an array of columns, in this case the first line is not used as a
295 header. Empty header fields will be ignored in objects.
297 =item C<profile> \%ACCESSORS
299 May be used to map header fields to custom accessors. Example:
301 { listprice => listprice_as_number }
303 In this case C<listprice_as_number> will be used to read in values from the
308 If present, the line will be handed to the new sub of this class,
309 and the return value used instead of the line itself.
313 =head1 ERROR HANDLING
315 After parsing a file all errors will be accumulated into C<errors>.
317 Each entry is an arrayref with the following structure:
321 Text::CSV error code if T:C error, 0 else,
324 estimated line in file,
327 Note that the last entry can be off, but will give an estimate.
335 sep_char, quote_char, and escape_char are passed to Text::CSV on creation.
336 Changing them later has no effect currently.
340 Encoding errors are not dealt with properly.
346 Dispatch to child objects, like this:
348 $csv = SL::Helper::Csv->new(
350 class => SL::DB::Part,
365 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>