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