File Coverage

blib/lib/Tie/CSV_File.pm
Criterion Covered Total %
statement 128 152 84.2
branch 19 38 50.0
condition 5 20 25.0
subroutine 33 39 84.6
pod n/a
total 185 249 74.3


line stmt bran cond sub pod time code
1             package Tie::CSV_File;
2              
3 12     12   254700 use strict;
  12         23  
  12         509  
4 12     12   65 use warnings;
  12         21  
  12         487  
5              
6             require Exporter;
7              
8 12     12   39453 use Data::Dumper;
  12         214779  
  12         1204  
9 12     12   16778 use Tie::Array;
  12         22839  
  12         418  
10 12     12   28923 use Text::CSV_XS;
  12         240644  
  12         927  
11 12     12   21569 use Tie::File;
  12         520374  
  12         654  
12 12     12   29893 use Params::Validate qw/:all/;
  12         244675  
  12         3443  
13 12     12   131 use Carp;
  12         27  
  12         3566  
14              
15             our @ISA = qw(Exporter Tie::Array);
16              
17             our $VERSION = '0.21';
18              
19             # There's a common misspelling of sepArated (an E instead of A)
20             # That's why all csv file definitions are defined even with an E and an A
21             sub __mispell($) {
22 60     60   252 shift =~ /^(.*_SEP)A(RATED)/;
23 60         262 return "$1E$2";
24             }
25              
26             # Export all predefined file types
27             our @EXPORT = map {($_, __mispell $_)}
28             map {$_ . "_SEPARATED"}
29             qw/TAB COLON SEMICOLON PIPE WHITESPACE/;
30              
31 12         1566 use constant SPLIT_SEPARATED_STANDARD_OPTIONS => (
32             quote_char => undef,
33             eol => undef, # default
34             escape_char => undef,
35             always_quote => 0 # default
36 12     12   85 );
  12         26  
37              
38 12         2200 use constant SEPARATOR_CHARS => (
39             [TAB => "\t"],
40             [COLON => ":"],
41             [SEMICOLON => ";"],
42             [PIPE => "|"]
43 12     12   220 );
  12         23  
44              
45             # Create typical file format constants,
46             # only different on their seperator chars
47             BEGIN {
48 12     12   42 foreach (SEPARATOR_CHARS) {
49 48         102 my ($name, $char) = @$_;
50 48         116 $name .= "_SEPARATED";
51 48     12   4443 eval "use constant $name => (sep_char => \$char,
  12     12   91  
  12     12   31  
  12     12   590  
  12         68  
  12         21  
  12         570  
  12         64  
  12         36  
  12         698  
  12         77  
  12         20  
  12         558  
52             SPLIT_SEPARATED_STANDARD_OPTIONS)";
53 48         661 (my $name_with_spelling_mistake = $name) =~ s/(?<=SEP)A(?=RATED)/E/;
54 48         3259 eval "*$name_with_spelling_mistake = *$name";
55             };
56             }
57             # Note that the BEGIN block is necessary for Perl <= 5.6.1
58             # otherwise it detects too late the constant creation
59             # and signalizes the *_SEPARATED as barewords :-((
60              
61 12         11767 use constant WHITESPACE_SEPARATED => (
62             sep_re => qr/\s+/,
63             sep_char => ' ',
64             quote_char => undef,
65             eol => undef, # default
66             escape_char => undef,
67             always_quote => 0 # default
68 12     12   168 );
  12         30  
69             *WHITESPACE_SEPERATED = *WHITESPACE_SEPARATED;
70             # ^ ^ you see the difference
71              
72             sub TIEARRAY {
73 17     17   56518 my ($class, $fname) = (shift(), shift());
74            
75             # Parameter validation
76 17         1883 my %options = validate( @_, {
77             quote_char => {default => q/"/, type => SCALAR | UNDEF},
78             eol => {default => undef, type => SCALAR | UNDEF},
79             sep_char => {default => q/,/, type => SCALAR | UNDEF},
80             sep_re => {default => undef, isa => 'Regexp'},
81             escape_char => {default => q/"/, type => SCALAR | UNDEF},
82             always_quote => {default => 0, type => SCALAR | UNDEF}
83             });
84            
85 14         230 $options{binary} = 1; # to handle with 'ä','ö','ü' and so on, not for "\n"
86            
87             # Check for some cases to warn
88 14 100       68 unless( defined $options{sep_char} ) {
89 1         27 carp "The sep_char should either be defined or not mentioned, ".
90             "but I got something like sep_char => undef\n" .
91             "It's interpreted as the default value ',' (a comma)!";
92 1         566 $options{sep_char} = ',';
93             }
94 14 100       72 unless ( (my $l = length $options{sep_char}) == 1) {
95 2         34 carp "The sep_char should have a length of 1, not $l"
96             }
97 14 100 66     1806 if (defined(my $c = $options{sep_char}) && defined(my $r = $options{sep_re})) {
98 1 50       18 carp "The sep_char '$c' is itself not matched by the sep_re '$r'"
99             if $c !~ /$r/;
100             }
101            
102 14 100       746 tie my @lines, 'Tie::File', $fname or die "Can't open $fname: $!";
103 13         3119 my $self = {
104             lines => \@lines,
105             csv => Text::CSV_XS->new(\%options),
106             quote_char => $options{quote_char},
107             eol => $options{eol},
108             sep_char => $options{sep_char},
109             sep_re => $options{sep_re},
110             escape_char => $options{escape_char},
111             always_quote=> $options{always_quote},
112             };
113 13         4822 bless $self, $class;
114             }
115              
116             sub FETCHSIZE {
117 11     11   5435 my ($self) = @_;
118 11         19 return scalar( @{ $self->{lines} } );
  11         90  
119             }
120              
121             sub FETCH {
122 6     6   107 my ($self, $line_nr) = @_;
123 6         21 my @csv_options = map {$self->{$_}} qw/csv eol sep_char sep_re quote_char/;
  30         86  
124 6         64 tie my @fields, 'Tie::CSV_File::Line', $self->{lines}, $line_nr, @csv_options;
125 6         44 return \@fields;
126             }
127              
128             sub EXISTS {
129 0     0   0 my ($self, $line_nr) = @_;
130 0         0 exists $self->{lines}->[$line_nr];
131             }
132              
133             sub STORE {
134 4     4   417 my ($self, $line_nr, $columns) = @_;
135 4         12 my $csv = $self->{csv};
136 4 50       22 if (@$columns) {
137 4 0       303 $csv->combine(@$columns) or die "Can't store " . Dumper($columns);
138 0         0 $self->{lines}->[$line_nr] = $csv->string;
139             } else {
140 0   0     0 $self->{lines}->[$line_nr] = $self->{eol} || '';
141             }
142             }
143              
144             sub STORESIZE {
145 3     3   55 my ($self, $count) = @_;
146 3         10 $#{$self->{lines}} = $count-1;
  3         44  
147             }
148              
149             sub DELETE {
150 0     0   0 my ($self, $line_nr) = @_;
151 0         0 delete $self->{lines}->[$line_nr];
152             }
153              
154             package Tie::CSV_File::Line;
155              
156 12     12   92 use strict;
  12         34  
  12         545  
157 12     12   66 use warnings;
  12         23  
  12         543  
158              
159 12     12   67 use Tie::Array;
  12         21  
  12         264  
160 12     12   57 use Text::CSV_XS;
  12         29  
  12         550  
161 12     12   64 use Tie::File;
  12         20  
  12         320  
162 12     12   71 use Data::Dumper;
  12         19  
  12         15236  
163              
164             our @ISA = qw(Exporter Tie::Array);
165              
166             sub TIEARRAY {
167 6     6   23 my ($class, $data, $line_nr, $csv, $eol, $sep_char, $sep_re, $quote_char) = @_;
168 6         73 my $self = bless {
169             data => $data,
170             line_nr => $line_nr,
171             csv => $csv,
172             eol => $eol,
173             sep_char => $sep_char,
174             sep_re => $sep_re,
175             quote_char => $quote_char,
176             fields => undef
177             }, $class;
178             }
179              
180             sub columns {
181 6     6   13 my $self = shift;
182 6         13 my @fields = (); # even if there aren't any fields, it's an empty list
183 6         42 my $line = $self->{data}->[$self->{line_nr}];
184 6 100       673 defined($line) or return $self->{fields} = \@fields;
185 4 50       21 if (defined( my $eol = $self->{eol} )) {
186 0         0 $line =~ s/\Q$eol\E$//;
187             }
188 4 50       21 if (defined( my $re = $self->{sep_re} )) {
189 0 0       0 push @fields,
190 0         0 map {defined($_) ? $_ : ''} # empty fields shall be '', not undef
191             grep !/$re/, # ugly, but needed see downside
192             split /($re)/, $line; # needed, as perl has problems with
193             # split /x/,"xxxxxxxxxx"; or similar
194 0 0       0 push @fields, '' if $line =~ /$re$/; # needed when the last element is empty
195             # - it won't be catched with split
196             } else {
197 4         11 my $csv = $self->{csv};
198 4 0       539 $csv->parse($line) and push @fields, $csv->fields();
199             }
200 0         0 return $self->{fields} = \@fields;
201             }
202              
203             sub set_new_fields {
204 2     2   4 my ($self, $fields) = @_;
205 2         5 $self->{fields} = $fields;
206              
207 2         3 my $csv_string;
208 2 50 66     28 if (@$fields == 0) { # No columns
    100          
209 0         0 my $eol = $self->{eol};
210 0 0       0 $csv_string = defined($eol) ? $eol : "";
211             } elsif (@$fields == 1 and $fields->[0] eq '') { # One column with an empty string
212 1         1 my $quote_char = $self->{quote_char};
213 1         3 my $eol = $self->{eol};
214             $_ = defined($_) ? $_ : ""
215 1 50       6 for $eol, $quote_char;
216              
217 1         3 $csv_string = $quote_char . $quote_char . $eol;
218             } else { # Default
219 1         2 my $csv = $self->{csv};
220 1 0       87 $csv->combine(@$fields) or die "Can't store columns " . Dumper($fields);
221 0         0 $csv_string = $csv->string;
222             }
223 1         6 $self->{data}->[$self->{line_nr}] = $csv_string;
224             }
225              
226             sub FETCHSIZE {
227 4     4   753 my ($self) = @_;
228 4 50       11 return scalar( @{$self->{fields} || $self->columns} );
  4         51  
229             }
230              
231             sub FETCH {
232 0     0   0 my ($self, $col_nr) = @_;
233 0   0     0 ($self->{fields} || $self->columns)->[$col_nr];
234             }
235              
236             sub EXISTS {
237 0     0   0 my ($self, $col_nr) = @_;
238 0   0     0 exists( ($self->{fields} || $self->columns)->[$col_nr] );
239             }
240              
241             sub STORE {
242 2     2   7 my ($self, $col_nr, $value) = @_;
243 2         16 my $csv = $self->{csv};
244 2   33     16 my $fields = $self->{fields} || $self->columns;
245 2         6 $fields->[$col_nr] = $value;
246 2         9 $self->set_new_fields($fields);
247             }
248              
249             sub STORESIZE {
250 0     0     my ($self, $new_size) = @_;
251 0   0       my $fields = $self->{fields} || $self->columns;
252 0           $#$fields = $new_size-1; # Set new size => last element is now at
253             # index size-1
254 0           $self->set_new_fields($fields);
255             }
256              
257             sub DELETE {
258 0     0     my ($self, $col_nr) = @_;
259 0           $self->STORE($col_nr,"");
260             }
261              
262             1;
263             __END__