File Coverage

blib/lib/Snail/CSV.pm
Criterion Covered Total %
statement 12 152 7.8
branch 0 62 0.0
condition 0 37 0.0
subroutine 4 16 25.0
pod 10 10 100.0
total 26 277 9.3


line stmt bran cond sub pod time code
1             package Snail::CSV;
2              
3 1     1   26543 use strict;
  1         2  
  1         43  
4 1     1   1490 use Text::CSV_XS;
  1         12899  
  1         90  
5 1     1   1535 use IO::File;
  1         9817  
  1         124  
6              
7 1     1   8 use vars qw($VERSION);
  1         2  
  1         1221  
8             $VERSION = '0.07';
9              
10             sub new
11             {
12 0     0 1   my $class = shift;
13 0           my $this = bless {}, $class;
14              
15 0   0       $this->{'OPTS'} = shift || {};
16 0 0         unless ( %{$this->{'OPTS'}} )
  0            
17             {
18 0           $this->{'OPTS'} = { 'eol' => "\015\012", 'sep_char' => ';', 'quote_char' => '"', 'escape_char' => '"', 'binary' => 1 };
19             }
20 0           return $this;
21             }
22              
23             sub setFile
24             {
25 0     0 1   my $this = shift;
26 0           $this->{'FILE'} = shift;
27 0   0       $this->{'FIELDS'} = shift || [];
28 0   0       $this->{'FILTER'} = shift || {};
29              
30 0 0         $this->{'FILE'} or die "Please provide a filename to parse\n";
31 0 0         -f $this->{'FILE'} or die "Cannot find filename: ". $this->{'FILE'}. "\n";
32              
33 0           return $this;
34             }
35              
36             sub fetchall_arrayref
37             {
38 0     0 1   return shift->parse([]);
39             }
40              
41             sub fetchall_hashref
42             {
43 0     0 1   return shift->parse({});
44             }
45              
46             sub parse
47             {
48 0     0 1   my $this = shift;
49 0 0         exists($this->{'FILE'}) or die "Please provide a filename to parse\n";
50 0 0         exists($this->{'CSVXS'}) or $this->_init_csv;
51              
52 0   0       $this->{'DATA'} = shift || [];
53 0           my $dtype = ref $this->{'DATA'};
54              
55             {
56 0   0       local $/ = $this->{'OPTS'}->{'eol'} || "\015\012";
  0            
57              
58 0           my $fh = new IO::File "$this->{'FILE'}", "r";
59 0 0         if (defined $fh)
60             {
61 0           my $NUMB = 1;
62 0           while (my $columns = $this->{'CSVXS'}->getline($fh))
63             {
64 0 0         last unless @{$columns};
  0            
65 0           my $tmp = {};
66 0           my $f_flag = 1;
67 0           for (my $j = 0; $j < @{$columns}; $j++)
  0            
68             {
69 0 0         my $colname = $this->{'FIELDS'}->[$j] ? $this->{'FIELDS'}->[$j] : "";
70 0 0         next unless $colname;
71              
72 0           $tmp->{$colname} = $columns->[$j];
73 0 0 0       if (exists($this->{'FILTER'}->{$colname}) && ref $this->{'FILTER'}->{$colname} eq 'CODE')
74             {
75 0           $f_flag = $this->{'FILTER'}->{$colname}->($tmp->{$colname});
76             }
77 0 0 0       if (exists($this->{'FILTER'}->{$colname}) && !ref($this->{'FILTER'}->{$colname}))
78             {
79 0 0         $f_flag = $this->{'FILTER'}->{$colname} eq $tmp->{$colname} ? 1 : 0;
80             }
81             }
82 0 0 0       if ($f_flag && $dtype eq 'ARRAY') { $tmp->{'NUMBER'} = $NUMB; push @{$this->{'DATA'}}, $tmp; }
  0            
  0            
  0            
83 0 0 0       if ($f_flag && $dtype eq 'HASH') { $this->{'DATA'}->{$NUMB} = $tmp; }
  0            
84 0           $NUMB++;
85             }
86 0           $fh->close;
87             }
88             }
89 0           return $this->{'DATA'};
90             }
91              
92             sub getData
93             {
94 0     0 1   my $this = shift;
95 0 0         return exists($this->{'DATA'}) ? $this->{'DATA'} : [];
96             }
97              
98             sub setData
99             {
100 0     0 1   my $this = shift;
101 0   0       $this->{'DATA'} = shift || [];
102 0           return $this;
103             }
104              
105             sub update
106             {
107 0     0 1   my $this = shift;
108 0   0       my $nfile = shift || $this->{'FILE'};
109 0           my $tfile = $nfile . "." . time; # temp file for inplace update - it is bad method for create filename
110              
111 0 0         if (ref $this->{'DATA'} eq 'ARRAY') { $this->_to_hashref; }
  0            
112 0 0         if (ref $this->{'DATA'} ne 'HASH') { return $this; }
  0            
113 0 0         unless (%{$this->{'DATA'}}) { return $this; }
  0            
  0            
114              
115             {
116 0   0       local $/ = $this->{'OPTS'}->{'eol'} || "\015\012";
  0            
117              
118 0           my $tfh = new IO::File "$tfile", "w";
119 0 0         if (defined $tfh)
120             {
121 0           my $fh = new IO::File "$this->{'FILE'}", "r";
122 0 0         if (defined $fh)
123             {
124 0           my $NUMB = 1;
125 0           while (my $columns = $this->{'CSVXS'}->getline($fh))
126             {
127 0 0         last unless @{$columns};
  0            
128 0 0         unless (exists($this->{'DATA'}->{$NUMB}))
129             {
130 0           $this->{'CSVXS'}->combine( @{$columns} );
  0            
131 0           print $tfh $this->{'CSVXS'}->string;
132 0           $NUMB++; next;
  0            
133             }
134              
135 0           for (my $j = 0; $j < @{$columns}; $j++)
  0            
136             {
137 0 0         my $colname = $this->{'FIELDS'}->[$j] ? $this->{'FIELDS'}->[$j] : "COLUMNS" . $NUMB;
138 0 0 0       if (exists($this->{'DATA'}->{$NUMB}->{$colname}) && $this->{'DATA'}->{$NUMB}->{$colname} ne $columns->[$j])
139             {
140 0           $columns->[$j] = $this->{'DATA'}->{$NUMB}->{$colname};
141             }
142             }
143              
144 0           $this->{'CSVXS'}->combine( @{$columns} );
  0            
145 0           print $tfh $this->{'CSVXS'}->string;
146              
147 0           $NUMB++;
148             }
149 0           $fh->close;
150             }
151 0           $tfh->close;
152             }
153             }
154 0           rename $tfile, $nfile;
155 0           unlink $tfile;
156 0           return $this;
157             }
158              
159             sub save
160             {
161 0     0 1   my $this = shift;
162 0   0       my $nfile = shift || $this->{'FILE'};
163 0           my $tfile = $nfile . "." . time; # temp file for inplace update - it is bad method for create filename
164              
165 0 0         if (ref $this->{'DATA'} eq 'ARRAY') { $this->_to_hashref; }
  0            
166 0 0         if (ref $this->{'DATA'} ne 'HASH') { return $this; }
  0            
167 0 0         unless (%{$this->{'DATA'}}) { return $this; }
  0            
  0            
168              
169             {
170 0   0       local $/ = $this->{'OPTS'}->{'eol'} || "\015\012";
  0            
171              
172 0           my $tfh = new IO::File "$tfile", "w";
173 0 0         if (defined $tfh)
174             {
175              
176 0           $this->{'CSVXS'}->combine( @{$this->{'FIELDS'}} );
  0            
177 0           print $tfh $this->{'CSVXS'}->string;
178              
179 0           foreach my $nitem (keys %{$this->{'DATA'}})
  0            
180             {
181 0           my $columns = [];
182 0           for (@{$this->{'FIELDS'}})
  0            
183             {
184 0 0         push @{$columns}, exists($this->{'DATA'}->{$nitem}->{$_}) ? $this->{'DATA'}->{$nitem}->{$_} : "";
  0            
185             }
186 0           $this->{'CSVXS'}->combine( @{$columns} );
  0            
187 0           print $tfh $this->{'CSVXS'}->string;
188             }
189 0           $tfh->close;
190             }
191             }
192 0           rename $tfile, $nfile;
193 0           unlink $tfile;
194 0           return $this;
195             }
196              
197              
198             sub _to_hashref
199             {
200 0     0     my $this = shift;
201 0           my $hash = {};
202 0           while (defined(my $item = shift @{$this->{'DATA'}}))
  0            
203             {
204 0 0         next unless exists($item->{'NUMBER'});
205 0 0         unless (exists($hash->{$item->{'NUMBER'}}))
206             {
207 0           $hash->{$item->{'NUMBER'}} = $item;
208 0           delete $hash->{$item->{'NUMBER'}}->{'NUMBER'};
209             }
210             }
211 0           $this->{'DATA'} = {};
212 0           $this->{'DATA'} = $hash;
213 0           return $this;
214             }
215              
216             sub _init_csv
217             {
218 0     0     my $this = shift;
219 0           $this->{'CSVXS'} = Text::CSV_XS->new( $this->{'OPTS'} );
220 0           return $this;
221             }
222              
223 0     0 1   sub version { return $VERSION; }
224              
225             1;
226              
227             =head1 NAME
228              
229             Snail::CSV - Perl extension for read/write/update CSV files.
230              
231             =head1 SYNOPSIS
232              
233             use Snail::CSV;
234             my $csv = Snail::CSV->new(\%args); # %args - Text::CSV_XS options
235              
236              
237             my %filter = (
238             'pq' => 3,
239             'name' => sub { my $name = shift; $name =~ /XP$/ ? 1 : 0; }
240             );
241              
242             $csv->setFile("lamps.csv", [ "id", "name", "pq" ], \%filter);
243              
244              
245             my $lamps = $csv->parse;
246              
247             # or
248              
249             $csv->parse;
250             # some code
251             my $lamps = $csv->getData;
252              
253              
254             $csv->setFile("tents.csv", [ "id", "name", "brand", "price" ]);
255              
256              
257             my $tents = $csv->fetchall_hashref; # $tents is HASHREF
258             for my $item (values %{$tents})
259             {
260             $item->{'price'} = $item->{'brand'} eq 'Marmot' ? 0.95 * $item->{'price'} : $item->{'price'};
261             }
262             $csv->setData($tents);
263             $csv->update; # to tents.csv
264              
265             # or
266              
267             for my $item ( @{ $csv->fetchall_arrayref } )
268             {
269             $item->{'price'} = $item->{'brand'} eq 'Marmot' ? 0.95 * $item->{'price'} : $item->{'price'};
270             }
271             $csv->update("/full/path/to/new_file.csv"); # to new CSV file
272              
273              
274             =head1 DESCRIPTION
275              
276             This module can be used to read/write/update data from/to CSV files. L is used for parsing CSV files.
277              
278             =head1 METHOD
279              
280             =over
281              
282             =item B
283              
284             =item B
285              
286             This is constructor. %args - L options. Return object.
287              
288             =item B
289              
290             =item B
291              
292             Set CSV file, fields name and filters for fields name. Return object.
293              
294             Fields and Filters:
295              
296             my @fields_name = ("id", "name", "pq");
297             my %filter = (
298             'pq' => 3,
299             'name' => sub { my $name = shift; $name =~ /XP$/ ? 1 : 0; }
300             );
301              
302             =item B
303              
304             Read and parse CSV file. Return arrayref.
305              
306             =item B
307              
308             An alternative to B. Return arrayref.
309              
310             =item B
311              
312             An alternative to B. Return hashref.
313              
314             =item B
315              
316             Return current data. Use this method after B (B, B).
317              
318             =item B
319              
320             =item B
321              
322             Set new data. Return object.
323              
324             =item B
325              
326             =item B
327              
328             Attention! If new file not defined, update current file. Return object.
329              
330             =item B
331              
332             =item B
333              
334             Save current object data. Attention! If new file not defined, save data to current file. Return object.
335              
336             =item B
337              
338             Return version number.
339              
340             =back
341              
342             =head2 EXPORT
343              
344             None by default.
345              
346              
347              
348             =head1 EXAMPLE
349              
350             =head2 First example.
351              
352             Code:
353              
354             #!/usr/bin/perl -w
355             use strict;
356              
357             use Snail::CSV;
358             use Data::Dumper;
359              
360             my $csv = Snail::CSV->new();
361              
362             $csv->setFile("lamps.csv", [ "id", "name", "pq" ]);
363             # or
364             $csv->setFile("lamps.csv", [ "id", "", "pq" ], { 'pq' => sub { my $pq = shift; $pq > 2 ? 1 : 0; } });
365              
366             my $lamps = $csv->parse;
367              
368             print Dumper($lamps);
369              
370             lamps.csv
371              
372             1;"Tikka Plus";3
373             2;"Myo XP";1
374             3;"Duobelt Led 8";5
375              
376             If you wrote:
377              
378             $csv->setFile("lamps.csv", [ "id", "name", "pq" ]);
379              
380             then C is:
381              
382             $VAR1 = [
383             {
384             'id' => '1',
385             'name' => 'Tikka Plus',
386             'pq' => '3'
387             },
388             {
389             'id' => '2',
390             'name' => 'Myo XP',
391             'pq' => '1'
392             },
393             {
394             'id' => '3',
395             'name' => 'Duobelt Led 8',
396             'pq' => '5'
397             }
398             ];
399              
400             but if:
401              
402             $csv->setFile("lamps.csv", [ "id", "", "pq" ], { 'pq' => sub { my $pq = shift; $pq > 2 ? 1 : 0; } });
403              
404             C is:
405              
406             $VAR1 = [
407             {
408             'id' => '1',
409             'pq' => '3'
410             },
411             {
412             'id' => '3',
413             'pq' => '5'
414             }
415             ];
416              
417             =head2 Other example.
418              
419             Done.
420              
421              
422             =head1 TODO
423              
424             Goog idea? Welcome...
425              
426              
427             =head1 SEE ALSO
428              
429             L, L
430              
431             =head1 AUTHOR
432              
433             Dmitriy Dontsov, Emit@cpan.orgE
434              
435             =head1 COPYRIGHT AND LICENSE
436              
437             Copyright (C) 2006 by Dmitriy Dontsov
438              
439             This library is free software; you can redistribute it and/or modify
440             it under the same terms as Perl itself, either Perl version 5.8.8 or,
441             at your option, any later version of Perl 5 you may have available.
442              
443              
444             =cut