File Coverage

blib/lib/Text/CSV/R.pm
Criterion Covered Total %
statement 185 185 100.0
branch 71 76 93.4
condition 38 42 90.4
subroutine 26 26 100.0
pod 8 8 100.0
total 328 337 97.3


line stmt bran cond sub pod time code
1             package Text::CSV::R;
2              
3             require 5.005;
4              
5 5     5   207721 use strict;
  5         12  
  5         180  
6 5     5   27 use warnings;
  5         10  
  5         172  
7              
8             require Exporter;
9 5     5   4863 use Text::CSV;
  5         118135  
  5         37  
10 5     5   3728 use Text::CSV::R::Matrix;
  5         18  
  5         176  
11 5     5   29 use Carp;
  5         12  
  5         341  
12 5     5   31 use Scalar::Util qw(reftype looks_like_number openhandle);
  5         11  
  5         274  
13 5     5   30 use List::Util qw(min max);
  5         9  
  5         14475  
14              
15             our @ISA = qw(Exporter);
16              
17             our %EXPORT_TAGS = (
18             'all' => [
19             qw(read_csv read_csv2 read_table read_delim write_table write_csv rownames colnames)
20             ]
21             );
22              
23             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
24              
25             our $VERSION = '0.3';
26              
27             # A mapping of the R options to the Text:CSV options. False if there is no
28             # Text::CSV equivalent (specified because R options are not passed to
29             # Text::CSV, so we need to know all of them).
30             our $R_OPT_MAP = {
31             sep => 'sep_char',
32             strip_white => 'allow_whitespace',
33             quote => 'quote_char',
34             map { $_ => 0 }
35             qw(dec skip nrow header encoding row_names col_names
36             blank_lines_skip append hr fill),
37             };
38              
39             # merge the global default options, function defaults and user options
40             sub _merge_options {
41 38     38   71 my ( $t_opt, $u_opt ) = @_;
42 38         207 my %ret = (
43             skip => 0,
44             nrow => -1,
45             sep_char => "\t",
46             binary => 1,
47             blank_lines_skip => 1,
48             );
49 38         135 @ret{ keys %{$t_opt} } = values %{$t_opt};
  38         126  
  38         109  
50 38         145 @ret{ keys %{$u_opt} } = values %{$u_opt};
  38         122  
  38         90  
51 38         65 for my $k ( keys %{$R_OPT_MAP} ) {
  38         488  
52 532 100 100     2179 if ( defined $ret{$k} && $R_OPT_MAP->{$k} ) {
53 13         38 $ret{ $R_OPT_MAP->{$k} } = $ret{$k};
54             }
55             }
56 38         197 return \%ret;
57             }
58              
59             sub read_table {
60 1     1 1 6 my ( $file, %u_opt ) = @_;
61 1         5 return _read( $file, _merge_options( {}, \%u_opt ) );
62             }
63              
64             sub read_csv {
65 20     20 1 960 my ( $file, %u_opt ) = @_;
66 20         80 my $t_opt = { sep_char => q{,}, header => 1, };
67 20         76 return _read( $file, _merge_options( $t_opt, \%u_opt ) );
68             }
69              
70             sub read_csv2 {
71 2     2 1 9 my ( $file, %u_opt ) = @_;
72 2         8 my $t_opt = { sep_char => q{;}, dec => q{,}, header => 1, };
73 2         7 return _read( $file, _merge_options( $t_opt, \%u_opt ) );
74             }
75              
76             sub read_delim {
77 4     4 1 932 my ( $file, %u_opt ) = @_;
78 4         14 my $t_opt = { sep_char => "\t", header => 1, };
79 4         15 return _read( $file, _merge_options( $t_opt, \%u_opt ) );
80             }
81              
82             sub write_table {
83 10     10 1 8616 my ( $data_ref, $file, %u_opt ) = @_;
84 10         50 return _write( $data_ref, $file,
85             _merge_options( { eol => "\n", fill => 1 }, \%u_opt ) );
86             }
87              
88             sub write_csv {
89 1     1 1 965 my ( $data_ref, $file, %u_opt ) = @_;
90 1         6 my $t_opt = { eol => "\n", fill => 1, hr => 1, sep_char => q{,} };
91 1         4 return _write( $data_ref, $file, _merge_options( $t_opt, \%u_opt ) );
92             }
93              
94             sub rownames {
95 38     38 1 1722 my ( $tied_ref, $values ) = @_;
96 38         46 return Text::CSV::R::Matrix::ROWNAMES( tied @{$tied_ref}, $values );
  38         125  
97             }
98              
99             sub colnames {
100 57     57 1 1305 my ( $tied_ref, $values ) = @_;
101 57         76 return Text::CSV::R::Matrix::COLNAMES( tied @{$tied_ref}, $values );
  57         355  
102             }
103              
104             # check if $file is an open filehandle, if not open file with correct
105             # encoding. return also whether to close the filehandle or not
106             sub _get_fh {
107 38     38   66 my ( $file, $read, $opts ) = @_;
108 38 100       155 if ( openhandle($file) ) {
109 2         8 return ( $file, 0 );
110             }
111 36         54 my $encoding = q{};
112 36 100 66     146 if ( defined $opts->{encoding} && length $opts->{encoding} > 0 ) {
113 1         5 $encoding = ':encoding(' . $opts->{encoding} . ')';
114             }
115 36 100 100     117 my $mode
    100          
116             = $read ? '<'
117             : ( defined $opts->{append} && $opts->{append} ) ? '>>'
118             : '>';
119 36 50   1   2198 open my $IN, $mode . $encoding, $file
  1         12  
  1         2  
  1         15  
120             or croak "Cannot open $file for reading: $!";
121 36         17108 return ( $IN, 1 );
122             }
123              
124             # replace decimal point if necessary
125             sub _replace_dec {
126 38     38   63 my ( $data_ref, $opts, $read ) = @_;
127 38 100 100     146 if ( defined $opts->{dec} && $opts->{dec} ne q{.} ) {
128 4         10 for my $row ( @{$data_ref} ) {
  4         19  
129 11         17 $row = [ map { _replace_dec_col( $_, $opts, $read ) } @{$row} ];
  48         88  
  11         32  
130             }
131             }
132 38         70 return;
133             }
134              
135             sub _replace_dec_col {
136 48     48   71 my ( $col, $opts, $read ) = @_;
137 48 100       135 if ($read) {
    100          
138 42         193 ( my $tmp = $col ) =~ s{$opts->{dec}}{.}xms;
139 42 100       243 $col = looks_like_number($tmp) ? $tmp : $col;
140             }
141             elsif ( looks_like_number($col) ) {
142 4         42 $col =~ s{\.}{$opts->{dec}}xms;
143             }
144 48         148 return $col;
145             }
146              
147             sub _fill {
148 12     12   15 my ($data) = @_;
149 12         16 my @l = map { scalar @{$_} } @{$data};
  30         28  
  30         72  
  12         33  
150 12         67 my $max = max @l;
151 12 100       39 if ($max == min @l) { return; }
  9         20  
152 3         11 for my $row_id ( 0 .. $#l ) {
153 8         18 for my $i ( 1 .. ( $max - $l[$row_id] ) ) {
154 3         6 push @{ $data->[$row_id] }, q{};
  3         13  
155             }
156             }
157 3         7 return;
158             }
159              
160             sub _read {
161 27     27   50 my ( $file, $opts ) = @_;
162              
163 27         83 my ( $fh, $toclose ) = _get_fh( $file, 1, $opts );
164 27         76 my $data_ref = _parse_fh( $fh, $opts );
165 27 100       76 if ($toclose) {
166 26 50       454 close $fh or croak "Cannot close $file: $!";
167             }
168 27         82 _replace_dec( $data_ref, $opts, 1 );
169              
170 27 100 100     94 if ( defined $opts->{fill} && $opts->{fill} ) {
171 2         7 _fill($data_ref);
172             }
173              
174 27         185 return $data_ref;
175             }
176              
177             sub _write {
178 11     11   19 my ( $data_ref, $file, $opts ) = @_;
179              
180 11         26 my ( $fh, $toclose ) = _get_fh( $file, 0, $opts );
181 11         31 _replace_dec( $data_ref, $opts, 0 );
182 11 100 66     55 if ( defined $opts->{fill} && $opts->{fill} ) {
183 10         22 _fill($data_ref);
184             }
185 11         25 _write_to_fh( $data_ref, $fh, $opts );
186 11 100       24 if ($toclose) {
187 10 50       441 close $fh or croak "Cannot close $file: $!";
188             }
189 11         57 return;
190             }
191              
192             sub _create_csv_obj {
193 38     38   174 my %text_csv_opts = @_;
194 38         51 delete @text_csv_opts{ keys %{$R_OPT_MAP} };
  38         296  
195 38 50       319 my $csv = Text::CSV->new( \%text_csv_opts )
196             or croak q{Cannot use CSV: } . Text::CSV->error_diag();
197 38         3612 return $csv;
198             }
199              
200             sub _write_to_fh {
201 11     11   15 my ( $data_ref, $IN, $opts ) = @_;
202              
203 11         11 my $tied_obj = tied @{$data_ref};
  11         62  
204 11         16 my $csv = _create_csv_obj( %{$opts} );
  11         55  
205              
206             # do we have and want col/rownames?
207 22 100       98 my %meta = map {
    100          
208 11         25 $_ => defined $opts->{$_} ? $opts->{$_}
209             : defined $tied_obj ? 1
210             : 0
211             } qw(row_names col_names);
212              
213 11         17 my @data = @{$data_ref};
  11         29  
214              
215 11 100       32 if ( $meta{row_names} ) {
216 3 100       19 $meta{row_names}
217             = reftype \$meta{row_names} eq 'SCALAR'
218             ? rownames($data_ref)
219             : $meta{row_names};
220             @data
221 3         8 = map { [ $meta{row_names}->[$_], @{ $data[$_] } ] } 0 .. $#data;
  8         13  
  8         33  
222             }
223              
224 11 100       26 if ( $meta{col_names} ) {
225 4 100       22 $meta{col_names}
226             = reftype \$meta{col_names} eq 'SCALAR'
227             ? colnames($data_ref)
228             : $meta{col_names};
229 4         10 unshift @data, $meta{col_names};
230 4 100 66     24 if ( defined $opts->{hr} && $opts->{hr} ) {
231 1         2 unshift @{ $data[0] }, q{};
  1         4  
232             }
233             }
234              
235 11         42 $csv->print( $IN, $_ ) for @data;
236              
237 11         3184 return;
238             }
239              
240             # parsing of the file in a 2d array, store column and row names.
241             sub _parse_fh {
242 27     27   41 my ( $IN, $opts ) = @_;
243 27         34 my @data;
244              
245 27         303 my $obj = tie @data, 'Text::CSV::R::Matrix';
246              
247 27         42 my $csv = _create_csv_obj( %{$opts} );
  27         142  
248              
249             # skip the first lines if option is set
250             {
251 27         54 local $. = 0;
  27         103  
252 27   100     269 do { } while ( $. < $opts->{skip} && <$IN> );
253             }
254              
255 27         55 my $max_cols = 0;
256             LINE:
257 27         545 while ( my $line = <$IN> ) {
258 112         371 chomp $line;
259              
260             # blank_lines_skip option
261 112 100 100     411 next LINE if !length($line) && $opts->{'blank_lines_skip'};
262              
263 101 50       327 $csv->parse($line)
264             or croak q{Cannot parse CSV: } . $csv->error_input();
265 101         29580 push @data, [ $csv->fields() ];
266 101 100       412 if ( scalar( @{ $data[-1] } ) > $max_cols ) {
  101         318  
267 33         45 $max_cols = scalar @{ $data[-1] };
  33         90  
268             }
269              
270             # nrow option. Store one more because file might contain header.
271 101 100 100     1224 last LINE if ( $opts->{nrow} >= 0 && $. > $opts->{nrow} );
272             }
273              
274             # If first line contains exactly one column less than the one with the
275             # max. number of columns, we expect that first line contains the header and
276             # first column the rownames (like read.tables does)
277 27 100       51 my $auto_col_row = scalar @{ $data[0] || [] } == $max_cols - 1 ? 1 : 0;
  27 100       108  
278              
279 27 100 100     185 if ( defined $opts->{header} && !$opts->{header} ) {
280 6         11 $auto_col_row = 0;
281             }
282              
283             # in which column are rownames?
284 27 100 66     146 my $rowname_id
    100          
285             = ( defined $opts->{row_names}
286             && reftype \$opts->{row_names} eq 'SCALAR' ) ? $opts->{row_names}
287             : $auto_col_row ? 0
288             : -1;
289              
290             # re-add the column name if it is omitted. use the same default name as R
291 27 100       92 if ($auto_col_row) {
292 5         7 unshift @{ $data[0] }, 'row.names';
  5         19  
293             }
294              
295 27 100 100     117 if ( $auto_col_row || $opts->{header} ) {
296              
297             # first line contains header
298 21         168 colnames( \@data, shift @data );
299             }
300             else {
301              
302             # no column names specified, then use the same default as R
303 6         27 colnames( \@data, [ map { 'V' . $_ } 1 .. $max_cols ] );
  24         86  
304              
305             # we might have parsed one line more than needed with the nrow option,
306             # so fix that if necessary
307 6 100 100     40 if ( $opts->{nrow} >= 0 && $. > $opts->{nrow} ) {
308 1         10 pop @data;
309             }
310             }
311              
312 27         48 my @rownames;
313 27 100       63 if ( $rowname_id >= 0 ) {
314 8         31 for my $row (@data) {
315 24         35 push @rownames, splice @{$row}, $rowname_id, 1;
  24         60  
316             }
317              
318             # remove the column from the colnames array
319 8         31 my @colnames = @{ colnames( \@data ) };
  8         32  
320 8         16 splice @colnames, $rowname_id, 1;
321 8         22 colnames( \@data, \@colnames );
322             }
323             else {
324 19         63 @rownames = 1 .. scalar @data;
325             }
326 27         98 rownames( \@data, \@rownames );
327              
328 27         526 return \@data;
329             }
330              
331             1;
332              
333             __END__