Case insensitive header waren nicht voll abgedeckt, und haben Fehler bei kaputten...
[kivitendo-erp.git] / SL / Helper / Csv.pm
1 package SL::Helper::Csv;
2
3 use strict;
4 use warnings;
5
6 use Carp;
7 use IO::File;
8 use Params::Validate qw(:all);
9 use Text::CSV_XS;
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 strict_profile _io _csv
13   _objects _parsed _data _errors
14 ) ];
15
16 use SL::Helper::Csv::Dispatcher;
17 use SL::Helper::Csv::Error;
18
19 # public interface
20
21 sub new {
22   my $class  = shift;
23   my %params = validate(@_, {
24     sep_char               => { default => ';' },
25     quote_char             => { default => '"' },
26     escape_char            => { default => '"' },
27     header                 => { type    => ARRAYREF, optional => 1 },
28     profile                => { type    => HASHREF,  optional => 1 },
29     file                   => 1,
30     encoding               => 0,
31     class                  => 0,
32     numberformat           => 0,
33     dateformat             => 0,
34     ignore_unknown_columns => 0,
35     strict_profile         => 0,
36   });
37   my $self = bless {}, $class;
38
39   $self->$_($params{$_}) for keys %params;
40
41   $self->_io(IO::File->new);
42   $self->_csv(Text::CSV_XS->new({
43     binary => 1,
44     sep_char    => $self->sep_char,
45     quote_char  => $self->quote_char,
46     escape_char => $self->escape_char,
47
48   }));
49   $self->_errors([]);
50
51   return $self;
52 }
53
54 sub parse {
55   my ($self, %params) = @_;
56
57   $self->_open_file;
58   return if ! $self->_check_header;
59   return if ! $self->dispatcher->parse_profile;
60   return if ! $self->_parse_data;
61
62   $self->_parsed(1);
63   return $self;
64 }
65
66 sub get_data {
67   $_[0]->_data;
68 }
69
70 sub get_objects {
71   my ($self, %params) = @_;
72   croak 'no class given'   unless $self->class;
73   croak 'must parse first' unless $self->_parsed;
74
75   $self->_make_objects unless $self->_objects;
76   return wantarray ? @{ $self->_objects } : $self->_objects;
77 }
78
79 sub errors {
80   @{ $_[0]->_errors }
81 }
82
83 sub check_header {
84   $_[0]->_check_header;
85 }
86
87 # private stuff
88
89 sub _open_file {
90   my ($self, %params) = @_;
91
92   $self->encoding($self->_guess_encoding) if !$self->encoding;
93
94   $self->_io->open($self->file, '<' . $self->_encode_layer)
95     or die "could not open file " . $self->file;
96
97   return $self->_io;
98 }
99
100 sub _check_header {
101   my ($self, %params) = @_;
102   my $header = $self->header;
103
104   if (! $header) {
105     $header = $self->_csv->getline($self->_io);
106
107     $self->_push_error([
108       $self->_csv->error_input,
109       $self->_csv->error_diag,
110       0,
111     ]) unless $header;
112   }
113
114   return unless $header;
115   return $self->header([ map { lc } @$header ]);
116 }
117
118 sub _parse_data {
119   my ($self, %params) = @_;
120   my (@data, @errors);
121
122   $self->_csv->column_names(@{ $self->header });
123
124   while (1) {
125     my $row = $self->_csv->getline($self->_io);
126     if ($row) {
127       my %hr;
128       @hr{@{ $self->header }} = @$row;
129       push @data, \%hr;
130     } else {
131       last if $self->_csv->eof;
132       push @errors, [
133         $self->_csv->error_input,
134         $self->_csv->error_diag,
135         $self->_io->input_line_number,
136       ];
137     }
138     last if $self->_csv->eof;
139   }
140
141   $self->_data(\@data);
142   $self->_push_error(@errors);
143
144   return ! @errors;
145 }
146
147 sub _encode_layer {
148   ':encoding(' . $_[0]->encoding . ')';
149 }
150
151 sub _make_objects {
152   my ($self, %params) = @_;
153   my @objs;
154
155   eval "require " . $self->class;
156   local $::myconfig{numberformat} = $self->numberformat if $self->numberformat;
157   local $::myconfig{dateformat}   = $self->dateformat   if $self->dateformat;
158
159   for my $line (@{ $self->_data }) {
160     my $tmp_obj = $self->class->new;
161     $self->dispatcher->dispatch($tmp_obj, $line);
162     push @objs, $tmp_obj;
163   }
164
165   $self->_objects(\@objs);
166 }
167
168 sub dispatcher {
169   my ($self, %params) = @_;
170
171   $self->{_dispatcher} ||= $self->_make_dispatcher;
172 }
173
174 sub _make_dispatcher {
175   my ($self, %params) = @_;
176
177   die 'need a header to make a dispatcher' unless $self->header;
178
179   return SL::Helper::Csv::Dispatcher->new($self);
180 }
181
182 sub _guess_encoding {
183   # won't fix
184   'utf-8';
185 }
186
187 sub _push_error {
188   my ($self, @errors) = @_;
189   my @new_errors = ($self->errors, map { SL::Helper::Csv::Error->new(@$_) } @errors);
190   $self->_errors(\@new_errors);
191 }
192
193
194 1;
195
196 __END__
197
198 =encoding utf-8
199
200 =head1 NAME
201
202 SL::Helper::Csv - take care of csv file uploads
203
204 =head1 SYNOPSIS
205
206   use SL::Helper::Csv;
207
208   my $csv = SL::Helper::Csv->new(
209     file        => \$::form->{upload_file},
210     encoding    => 'utf-8', # undef means utf8
211     sep_char    => ',',     # default ';'
212     quote_char  => '\'',    # default '"'
213     escape_char => '"',     # default '"'
214     header      => [qw(id text sellprice word)], # see later
215     profile     => { sellprice => 'sellprice_as_number' },
216     class       => 'SL::DB::CsvLine',   # if present, map lines to this
217   );
218
219   my $status  = $csv->parse;
220   my $hrefs   = $csv->get_data;
221   my @objects = $csv->get_objects;
222
223   my @errors  = $csv->errors;
224
225 =head1 DESCRIPTION
226
227 See Synopsis.
228
229 Text::CSV offeres already good functions to get lines out of a csv file, but in
230 most cases you will want those line to be parsed into hashes or even objects,
231 so this model just skips ahead and gives you objects.
232
233 Its basic assumptions are:
234
235 =over 4
236
237 =item You do know what you expect to be in that csv file.
238
239 This means first and foremost you have knowledge about encoding, number and
240 date format, csv parameters such as quoting and separation characters. You also
241 know what content will be in that csv and what L<Rose::DB> is responsible for
242 it. You provide valid header columns and their mapping to the objects.
243
244 =item You do NOT know if the csv provider yields to your expectations.
245
246 Stuff that does not work with what you expect should not crash anything, but
247 give you a hint what went wrong. As a result, if you remeber to check for
248 errors after each step, you should be fine.
249
250 =item Data does not make sense. It's just data.
251
252 Almost all data imports have some type of constraints. Some data needs to be
253 unique, other data needs to be connected to existing data sets. This will not
254 happen here. You will receive a plain mapping of the data into the class tree,
255 nothing more.
256
257 =back
258
259 =head1 METHODS
260
261 =over 4
262
263 =item C<new> PARAMS
264
265 Standard constructor. You can use this to set most of the data.
266
267 =item C<parse>
268
269 Do the actual work. Will return true ($self actually) if success, undef if not.
270
271 =item C<get_objects>
272
273 Parse the data into objects and return those.
274
275 This method will return list or arrayref depending on context.
276
277 =item C<get_data>
278
279 Returns an arrayref of the raw lines as hashrefs.
280
281 =item C<errors>
282
283 Return all errors that came up during parsing. See error handling for detailed
284 information.
285
286 =back
287
288 =head1 PARAMS
289
290 =over 4
291
292 =item C<file>
293
294 The file which contents are to be read. Can be a name of a physical file or a
295 scalar ref for memory data.
296
297 =item C<encoding>
298
299 Encoding of the CSV file. Note that this module does not do any encoding
300 guessing. Know what your data is. Defaults to utf-8.
301
302 =item C<sep_char>
303
304 =item C<quote_char>
305
306 =item C<escape_char>
307
308 Same as in L<Text::CSV>
309
310 =item C<header> \@FIELDS
311
312 Can be an array of columns, in this case the first line is not used as a
313 header. Empty header fields will be ignored in objects.
314
315 =item C<profile> \%ACCESSORS
316
317 May be used to map header fields to custom accessors. Example:
318
319   { listprice => listprice_as_number }
320
321 In this case C<listprice_as_number> will be used to read in values from the
322 C<listprice> column.
323
324 In case of a One-To-One relationsship these can also be set over
325 relationsships by sparating the steps with a dot (C<.>). This will work:
326
327   { customer => 'customer.name' }
328
329 And will result in something like this:
330
331   $obj->customer($obj->meta->relationship('customer')->class->new);
332   $obj->customer->name($csv_line->{customer})
333
334 But beware, this will not try to look up anything in the database. You will
335 simply receive objects that represent what the profile defined. If some of
336 these information are unique, and should be connected to preexisting data, you
337 will have to do that for yourself. Since you provided the profile, it is
338 assumed you know what to do in this case.
339
340 =item C<class>
341
342 If present, the line will be handed to the new sub of this class,
343 and the return value used instead of the line itself.
344
345 =item C<ignore_unknown_columns>
346
347 If set, the import will ignore unkown header columns. Useful for lazy imports,
348 but deactivated by default.
349
350 =item C<strict_profile>
351
352 If set, all columns to be parsed must be specified in C<profile>. Every header
353 field not listed there will be treated like an unknown column.
354
355 =back
356
357 =head1 ERROR HANDLING
358
359 After parsing a file all errors will be accumulated into C<errors>.
360 Each entry is an object with the following attributes:
361
362  raw_input:  offending raw input,
363  code:   Text::CSV error code if Text:CSV signalled an error, 0 else,
364  diag:   error diagnostics,
365  line:   position in line,
366  col:    estimated line in file,
367
368 Note that the last entry can be off, but will give an estimate.
369
370 =head1 CAVEATS
371
372 =over 4
373
374 =item *
375
376 sep_char, quote_char, and escape_char are passed to Text::CSV on creation.
377 Changing them later has no effect currently.
378
379 =item *
380
381 Encoding errors are not dealt with properly.
382
383 =back
384
385 =head1 TODO
386
387 Dispatch to child objects, like this:
388
389  $csv = SL::Helper::Csv->new(
390    file  => ...
391    class => SL::DB::Part,
392    profile => [
393      makemodel => {
394        make_1  => make,
395        model_1 => model,
396      },
397      makemodel => {
398        make_2  => make,
399        model_2 => model,
400      },
401    ]
402  );
403
404 =head1 AUTHOR
405
406 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
407
408 =cut