File Coverage

blib/lib/Catmandu/Importer/CSV.pm
Criterion Covered Total %
statement 17 17 100.0
branch 4 4 100.0
condition n/a
subroutine 6 6 100.0
pod n/a
total 27 27 100.0


line stmt bran cond sub pod time code
1             package Catmandu::Importer::CSV;
2              
3 7     7   106917 use Catmandu::Sane;
  7         20  
  7         51  
4              
5             our $VERSION = '1.2020';
6              
7 7     7   6471 use Text::CSV;
  7         104691  
  7         356  
8 7     7   77 use List::Util qw(reduce);
  7         16  
  7         486  
9 7     7   42 use Moo;
  7         20  
  7         73  
10 7     7   2926 use namespace::clean;
  7         24  
  7         83  
11              
12             with 'Catmandu::Importer';
13              
14             has csv => (is => 'ro', lazy => 1, builder => '_build_csv');
15             has sep_char => (
16             is => 'ro',
17             default => sub {','},
18             coerce => sub {
19             my $sep_char = $_[0];
20             $sep_char =~ s/(\\[abefnrt])/"qq{$1}"/gee;
21             return $sep_char;
22             }
23             );
24             has quote_char => (is => 'ro', default => sub {'"'});
25             has escape_char => (is => 'ro', default => sub {'"'});
26             has allow_loose_quotes => (is => 'ro', default => sub {0});
27             has allow_loose_escapes => (is => 'ro', default => sub {0});
28             has header => (is => 'ro', default => sub {1});
29             has fields => (
30             is => 'rwp',
31             coerce => sub {
32             my $fields = $_[0];
33             if (ref $fields eq 'ARRAY') {return $fields}
34             if (ref $fields eq 'HASH') {return [sort keys %$fields]}
35             return [split ',', $fields];
36             },
37             );
38              
39             sub _build_csv {
40 23     23   228 my ($self) = @_;
41 23 100       447 Text::CSV->new(
    100          
42             {
43             binary => 1,
44             sep_char => $self->sep_char,
45             quote_char => $self->quote_char ? $self->quote_char : undef,
46             escape_char => $self->escape_char ? $self->escape_char : undef,
47             allow_loose_quotes => $self->allow_loose_quotes,
48             allow_loose_escapes => $self->allow_loose_escapes,
49             }
50             );
51             }
52              
53             sub generator {
54             my ($self) = @_;
55             sub {
56             state $line = 0;
57             state $fh = $self->fh;
58             state $csv = do {
59             if ($self->header) {
60             if ($self->fields) {
61             $self->csv->getline($fh);
62             $line++;
63             }
64             else {
65             $self->_set_fields($self->csv->getline($fh));
66             $line++;
67             }
68             }
69             if ($self->fields) {
70             $self->csv->column_names($self->fields);
71             }
72             $self->csv;
73             };
74              
75             # generate field names if needed
76             unless ($self->fields) {
77             my $row = $csv->getline($fh) // return;
78             $line++;
79             my $fields = [0 .. (@$row - 1)];
80             $self->_set_fields($fields);
81             $csv->column_names($fields);
82             return reduce {
83             $a->{$b} = $row->[$b] if length $row->[$b];
84             $a;
85             }
86             +{}, @$fields;
87             }
88              
89             my $rec = $csv->getline_hr($fh);
90             $line++;
91              
92             if (defined $rec || $csv->eof()) {
93             return $rec;
94             }
95             else {
96             my ($cde, $str, $pos) = $csv->error_diag();
97             die
98             "at line $line (byte $pos) found a Text::CSV parse error($cde) $str";
99             }
100             };
101             }
102              
103             1;
104              
105             __END__
106              
107             =pod
108              
109             =head1 NAME
110              
111             Catmandu::Importer::CSV - Package that imports CSV data
112              
113             =head1 SYNOPSIS
114              
115             # From the command line
116              
117             # convert a CSV file to JSON
118             catmandu convert CSV to JSON < journals.csv
119              
120             # set column names if CSV file has no header line
121             echo '12157,"The Journal of Headache and Pain",2193-1801' | \
122             catmandu convert CSV --header 0 --fields 'id,title,issn' to YAML
123            
124             # set field separator and quote character
125             echo '12157;$The Journal of Headache and Pain$;2193-1801' | \
126             catmandu convert CSV --header 0 --fields 'id,title,issn' --sep_char ';' --quote_char '$' to XLSX --file journal.xlsx
127              
128             # In a Perl script
129              
130             use Catmandu;
131              
132             my $importer = Catmandu->importer('CSV', file => "/foo/bar.csv");
133              
134             my $n = $importer->each(sub {
135             my $hashref = $_[0];
136             # ...
137             });
138              
139             =head1 DESCRIPTION
140              
141             The package imports comma-separated values (CSV). The object
142             fields are read from the CSV header line or given via the C<fields> parameter.
143             Strings in CSV are quoted by C<quote_char> and fields are separated by
144             C<sep_char>.
145              
146             =head1 CONFIGURATION
147              
148             =over
149              
150             =item file
151              
152             Read input from a local file given by its path. Alternatively a scalar
153             reference can be passed to read from a string.
154              
155             =item fh
156              
157             Read input from an L<IO::Handle>. If not specified, L<Catmandu::Util::io> is used to
158             create the input stream from the C<file> argument or by using STDIN.
159              
160             =item encoding
161              
162             Binmode of the input stream C<fh>. Set to C<:utf8> by default.
163              
164             =item fix
165              
166             An ARRAY of one or more fixes or file scripts to be applied to imported items.
167              
168             =item fields
169              
170             List of fields to be used as columns, given as array reference, comma-separated
171             string, or hash reference. If C<header> is C<0> and C<fields> is C<undef> the
172             fields will be named by column index ("0", "1", "2", ...).
173              
174             =item header
175              
176             Read fields from a header line with the column names, if set to C<1> (the
177             default).
178              
179             =item sep_char
180              
181             Column separator (C<,> by default)
182              
183             =item quote_char
184              
185             Quotation character (C<"> by default)
186              
187             =item escape_char
188              
189             Character for escaping inside quoted field (C<"> by default)
190              
191             =item allow_loose_quotes
192              
193             =item allow_loose_escapes
194              
195             Allow common bad-practice in CSV escaping
196              
197             =back
198              
199             =head1 METHODS
200              
201             Every L<Catmandu::Importer> is a L<Catmandu::Iterable> all its methods are
202             inherited. The methods are not idempotent: CSV streams can only be read once.
203              
204             =head1 SEE ALSO
205              
206             L<Catmandu::Exporter::CSV>, L<Catmandu::Importer::XLS>
207              
208             =cut