Filename | /usr/lib/x86_64-linux-gnu/perl5/5.20/Text/CSV_XS.pm |
Statements | Executed 22 statements in 5.29ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 28µs | 34µs | BEGIN@24 | Text::CSV_XS::
1 | 1 | 1 | 26µs | 26µs | bootstrap (xsub) | Text::CSV_XS::
1 | 1 | 1 | 11µs | 22µs | BEGIN@23 | Text::CSV_XS::
1 | 1 | 1 | 6µs | 34µs | BEGIN@28 | Text::CSV_XS::
1 | 1 | 1 | 6µs | 37µs | BEGIN@30 | Text::CSV_XS::
1 | 1 | 1 | 3µs | 3µs | BEGIN@27 | Text::CSV_XS::
1 | 1 | 1 | 3µs | 3µs | SetDiag (xsub) | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | IV | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | NV | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | PV | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _check_sanity | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _csv_attr | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _set_attr_C | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _set_attr_N | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _set_attr_X | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | _unhealthy_whitespace | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | allow_loose_escapes | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | allow_loose_quotes | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | allow_unquoted_escape | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | allow_whitespace | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | always_quote | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | auto_diag | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | binary | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | bind_columns | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | blank_is_undef | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | callbacks | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | column_names | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | combine | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | csv | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | decode_utf8 | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | diag_verbose | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | empty_is_undef | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | eof | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | eol | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | error_diag | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | escape_char | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | fields | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | fragment | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | getline_hr | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | getline_hr_all | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | is_binary | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | is_missing | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | is_quoted | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | keep_meta_info | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | meta_info | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | new | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | parse | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | print_hr | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | quote | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | quote_binary | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | quote_char | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | quote_null | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | quote_space | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | record_number | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | sep | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | sep_char | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | status | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | string | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | types | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | verbatim | Text::CSV_XS::
0 | 0 | 0 | 0s | 0s | version | Text::CSV_XS::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | package Text::CSV_XS; | ||||
2 | |||||
3 | # Copyright (c) 2007-2014 H.Merijn Brand. All rights reserved. | ||||
4 | # Copyright (c) 1998-2001 Jochen Wiedmann. All rights reserved. | ||||
5 | # Copyright (c) 1997 Alan Citterman. All rights reserved. | ||||
6 | # | ||||
7 | # This program is free software; you can redistribute it and/or | ||||
8 | # modify it under the same terms as Perl itself. | ||||
9 | |||||
10 | # HISTORY | ||||
11 | # | ||||
12 | # Written by: | ||||
13 | # Jochen Wiedmann <joe@ispsoft.de> | ||||
14 | # | ||||
15 | # Based on Text::CSV by: | ||||
16 | # Alan Citterman <alan@mfgrtl.com> | ||||
17 | # | ||||
18 | # Extended and Remodelled by: | ||||
19 | # H.Merijn Brand (h.m.brand@xs4all.nl) | ||||
20 | |||||
21 | 1 | 15µs | require 5.006001; | ||
22 | |||||
23 | 2 | 26µs | 2 | 32µs | # spent 22µs (11+11) within Text::CSV_XS::BEGIN@23 which was called:
# once (11µs+11µs) by Text::CSV::BEGIN@1 at line 23 # spent 22µs making 1 call to Text::CSV_XS::BEGIN@23
# spent 11µs making 1 call to strict::import |
24 | 2 | 26µs | 2 | 39µs | # spent 34µs (28+6) within Text::CSV_XS::BEGIN@24 which was called:
# once (28µs+6µs) by Text::CSV::BEGIN@1 at line 24 # spent 34µs making 1 call to Text::CSV_XS::BEGIN@24
# spent 6µs making 1 call to warnings::import |
25 | |||||
26 | 1 | 1µs | require Exporter; | ||
27 | 2 | 18µs | 1 | 3µs | # spent 3µs within Text::CSV_XS::BEGIN@27 which was called:
# once (3µs+0s) by Text::CSV::BEGIN@1 at line 27 # spent 3µs making 1 call to Text::CSV_XS::BEGIN@27 |
28 | 2 | 29µs | 2 | 62µs | # spent 34µs (6+28) within Text::CSV_XS::BEGIN@28 which was called:
# once (6µs+28µs) by Text::CSV::BEGIN@1 at line 28 # spent 34µs making 1 call to Text::CSV_XS::BEGIN@28
# spent 28µs making 1 call to Exporter::import |
29 | |||||
30 | 2 | 5.11ms | 2 | 68µs | # spent 37µs (6+31) within Text::CSV_XS::BEGIN@30 which was called:
# once (6µs+31µs) by Text::CSV::BEGIN@1 at line 30 # spent 37µs making 1 call to Text::CSV_XS::BEGIN@30
# spent 31µs making 1 call to vars::import |
31 | 1 | 400ns | $VERSION = "1.11"; | ||
32 | 1 | 13µs | @ISA = qw( DynaLoader Exporter ); | ||
33 | 1 | 500ns | @EXPORT_OK = qw( csv ); | ||
34 | 1 | 12µs | 1 | 892µs | bootstrap Text::CSV_XS $VERSION; # spent 892µs making 1 call to DynaLoader::bootstrap |
35 | |||||
36 | sub PV { 0 } | ||||
37 | sub IV { 1 } | ||||
38 | sub NV { 2 } | ||||
39 | |||||
40 | # version | ||||
41 | # | ||||
42 | # class/object method expecting no arguments and returning the version | ||||
43 | # number of Text::CSV. there are no side-effects. | ||||
44 | |||||
45 | sub version | ||||
46 | { | ||||
47 | return $VERSION; | ||||
48 | } # version | ||||
49 | |||||
50 | # new | ||||
51 | # | ||||
52 | # class/object method expecting no arguments and returning a reference to | ||||
53 | # a newly created Text::CSV object. | ||||
54 | |||||
55 | 1 | 11µs | my %def_attr = ( | ||
56 | eol => '', | ||||
57 | sep_char => ',', | ||||
58 | quote_char => '"', | ||||
59 | escape_char => '"', | ||||
60 | binary => 0, | ||||
61 | decode_utf8 => 1, | ||||
62 | auto_diag => 0, | ||||
63 | diag_verbose => 0, | ||||
64 | blank_is_undef => 0, | ||||
65 | empty_is_undef => 0, | ||||
66 | allow_whitespace => 0, | ||||
67 | allow_loose_quotes => 0, | ||||
68 | allow_loose_escapes => 0, | ||||
69 | allow_unquoted_escape => 0, | ||||
70 | always_quote => 0, | ||||
71 | quote_space => 1, | ||||
72 | quote_null => 1, | ||||
73 | quote_binary => 1, | ||||
74 | keep_meta_info => 0, | ||||
75 | verbatim => 0, | ||||
76 | types => undef, | ||||
77 | callbacks => undef, | ||||
78 | |||||
79 | _EOF => 0, | ||||
80 | _RECNO => 0, | ||||
81 | _STATUS => undef, | ||||
82 | _FIELDS => undef, | ||||
83 | _FFLAGS => undef, | ||||
84 | _STRING => undef, | ||||
85 | _ERROR_INPUT => undef, | ||||
86 | _COLUMN_NAMES => undef, | ||||
87 | _BOUND_COLUMNS => undef, | ||||
88 | _AHEAD => undef, | ||||
89 | ); | ||||
90 | 1 | 1µs | my %attr_alias = ( | ||
91 | quote_always => "always_quote", | ||||
92 | verbose_diag => "diag_verbose", | ||||
93 | ); | ||||
94 | 1 | 7µs | 1 | 3µs | my $last_new_err = Text::CSV_XS->SetDiag (0); # spent 3µs making 1 call to Text::CSV_XS::SetDiag |
95 | |||||
96 | # NOT a method: is also used before bless | ||||
97 | sub _unhealthy_whitespace | ||||
98 | { | ||||
99 | my $self = shift; | ||||
100 | $_[0] or return 0; # no checks needed without allow_whitespace | ||||
101 | |||||
102 | my $quo = $self->{quote}; | ||||
103 | defined $quo && length ($quo) or $quo = $self->{quote_char}; | ||||
104 | my $esc = $self->{escape_char}; | ||||
105 | |||||
106 | (defined $quo && $quo =~ m/^[ \t]/) || (defined $esc && $esc =~ m/^[ \t]/) and | ||||
107 | return 1002; | ||||
108 | |||||
109 | return 0; | ||||
110 | } # _sane_whitespace | ||||
111 | |||||
112 | sub _check_sanity | ||||
113 | { | ||||
114 | my $self = shift; | ||||
115 | |||||
116 | my $sep = $self->{sep}; | ||||
117 | defined $sep && length ($sep) or $sep = $self->{sep_char}; | ||||
118 | my $quo = $self->{quote}; | ||||
119 | defined $quo && length ($quo) or $quo = $self->{quote_char}; | ||||
120 | my $esc = $self->{escape_char}; | ||||
121 | |||||
122 | # use DP;::diag ("SEP: '", DPeek ($sep), | ||||
123 | # "', QUO: '", DPeek ($quo), | ||||
124 | # "', ESC: '", DPeek ($esc),"'"); | ||||
125 | # sep_char cannot be undefined | ||||
126 | defined $quo && $quo eq $sep and return 1001; | ||||
127 | defined $esc && $esc eq $sep and return 1001; | ||||
128 | |||||
129 | defined $_ && $_ =~ m/[\r\n]/ and return 1003 for $sep, $quo, $esc; | ||||
130 | |||||
131 | return _unhealthy_whitespace ($self, $self->{allow_whitespace}); | ||||
132 | } # _check_sanity | ||||
133 | |||||
134 | sub new | ||||
135 | { | ||||
136 | $last_new_err = Text::CSV_XS->SetDiag (1000, | ||||
137 | "usage: my \$csv = Text::CSV_XS->new ([{ option => value, ... }]);"); | ||||
138 | |||||
139 | my $proto = shift; | ||||
140 | my $class = ref ($proto) || $proto or return; | ||||
141 | @_ > 0 && ref $_[0] ne "HASH" and return; | ||||
142 | my $attr = shift || {}; | ||||
143 | my %attr = map { | ||||
144 | my $k = m/^[a-zA-Z]\w+$/ ? lc $_ : $_; | ||||
145 | exists $attr_alias{$k} and $k = $attr_alias{$k}; | ||||
146 | $k => $attr->{$_}; | ||||
147 | } keys %$attr; | ||||
148 | |||||
149 | my $sep_aliased = 0; | ||||
150 | if (defined $attr{sep}) { | ||||
151 | $attr{sep_char} = delete $attr{sep}; | ||||
152 | $sep_aliased = 1; | ||||
153 | } | ||||
154 | my $quote_aliased = 0; | ||||
155 | if (defined $attr{quote}) { | ||||
156 | $attr{quote_char} = delete $attr{quote}; | ||||
157 | $quote_aliased = 1; | ||||
158 | } | ||||
159 | for (keys %attr) { | ||||
160 | if (m/^[a-z]/ && exists $def_attr{$_}) { | ||||
161 | # uncoverable condition false | ||||
162 | defined $attr{$_} && $] >= 5.008002 && m/_char$/ and | ||||
163 | utf8::decode ($attr{$_}); | ||||
164 | next; | ||||
165 | } | ||||
166 | # croak? | ||||
167 | $last_new_err = Text::CSV_XS->SetDiag (1000, "INI - Unknown attribute '$_'"); | ||||
168 | $attr{auto_diag} and error_diag (); | ||||
169 | return; | ||||
170 | } | ||||
171 | if ($sep_aliased) { | ||||
172 | my @b = unpack "U0C*", $attr{sep_char}; | ||||
173 | if (@b > 1) { | ||||
174 | $attr{sep} = $attr{sep_char}; | ||||
175 | $attr{sep_char} = "\0"; | ||||
176 | } | ||||
177 | else { | ||||
178 | $attr{sep} = undef; | ||||
179 | } | ||||
180 | } | ||||
181 | if ($quote_aliased) { | ||||
182 | my @b = unpack "U0C*", $attr{quote_char}; | ||||
183 | if (@b > 1) { | ||||
184 | $attr{quote} = $attr{quote_char}; | ||||
185 | $attr{quote_char} = "\0"; | ||||
186 | } | ||||
187 | else { | ||||
188 | $attr{quote} = undef; | ||||
189 | } | ||||
190 | } | ||||
191 | |||||
192 | my $self = { %def_attr, %attr }; | ||||
193 | if (my $ec = _check_sanity ($self)) { | ||||
194 | $last_new_err = Text::CSV_XS->SetDiag ($ec); | ||||
195 | $attr{auto_diag} and error_diag (); | ||||
196 | return; | ||||
197 | } | ||||
198 | if (defined $self->{callbacks} && ref $self->{callbacks} ne "HASH") { | ||||
199 | warn "The 'callbacks' attribute is set but is not a hash: ignored\n"; | ||||
200 | $self->{callbacks} = undef; | ||||
201 | } | ||||
202 | |||||
203 | $last_new_err = Text::CSV_XS->SetDiag (0); | ||||
204 | defined $\ && !exists $attr{eol} and $self->{eol} = $\; | ||||
205 | bless $self, $class; | ||||
206 | defined $self->{types} and $self->types ($self->{types}); | ||||
207 | $self; | ||||
208 | } # new | ||||
209 | |||||
210 | # Keep in sync with XS! | ||||
211 | 1 | 6µs | my %_cache_id = ( # Only expose what is accessed from within PM | ||
212 | quote_char => 0, | ||||
213 | escape_char => 1, | ||||
214 | sep_char => 2, | ||||
215 | sep => 38, # 38 .. 54 | ||||
216 | binary => 3, | ||||
217 | keep_meta_info => 4, | ||||
218 | always_quote => 5, | ||||
219 | allow_loose_quotes => 6, | ||||
220 | allow_loose_escapes => 7, | ||||
221 | allow_unquoted_escape => 8, | ||||
222 | allow_whitespace => 9, | ||||
223 | blank_is_undef => 10, | ||||
224 | eol => 11, | ||||
225 | quote => 15, | ||||
226 | verbatim => 22, | ||||
227 | empty_is_undef => 23, | ||||
228 | auto_diag => 24, | ||||
229 | diag_verbose => 33, | ||||
230 | quote_space => 25, | ||||
231 | quote_null => 31, | ||||
232 | quote_binary => 32, | ||||
233 | decode_utf8 => 35, | ||||
234 | _has_hooks => 36, | ||||
235 | _is_bound => 26, # 26 .. 29 | ||||
236 | ); | ||||
237 | |||||
238 | # A `character' | ||||
239 | sub _set_attr_C | ||||
240 | { | ||||
241 | my ($self, $name, $val, $ec) = @_; | ||||
242 | defined $val or $val = 0; | ||||
243 | $] >= 5.008002 and utf8::decode ($val); | ||||
244 | $self->{$name} = $val; | ||||
245 | $ec = _check_sanity ($self) and | ||||
246 | croak ($self->SetDiag ($ec)); | ||||
247 | $self->_cache_set ($_cache_id{$name}, $val); | ||||
248 | } # _set_attr_C | ||||
249 | |||||
250 | # A flag | ||||
251 | sub _set_attr_X | ||||
252 | { | ||||
253 | my ($self, $name, $val) = @_; | ||||
254 | defined $val or $val = 0; | ||||
255 | $self->{$name} = $val; | ||||
256 | $self->_cache_set ($_cache_id{$name}, 0 + $val); | ||||
257 | } # _set_attr_X | ||||
258 | |||||
259 | # A number | ||||
260 | sub _set_attr_N | ||||
261 | { | ||||
262 | my ($self, $name, $val) = @_; | ||||
263 | $self->{$name} = $val; | ||||
264 | $self->_cache_set ($_cache_id{$name}, 0 + $val); | ||||
265 | } # _set_attr_N | ||||
266 | |||||
267 | # Accessor methods. | ||||
268 | # It is unwise to change them halfway through a single file! | ||||
269 | sub quote_char | ||||
270 | { | ||||
271 | my $self = shift; | ||||
272 | if (@_) { | ||||
273 | $self->_set_attr_C ("quote_char", shift); | ||||
274 | $self->_cache_set ($_cache_id{quote}, ""); | ||||
275 | } | ||||
276 | $self->{quote_char}; | ||||
277 | } # quote_char | ||||
278 | |||||
279 | sub quote | ||||
280 | { | ||||
281 | my $self = shift; | ||||
282 | if (@_) { | ||||
283 | my $quote = shift; | ||||
284 | defined $quote or $quote = ""; | ||||
285 | $] >= 5.008002 and utf8::decode ($quote); | ||||
286 | my @b = unpack "U0C*", $quote; | ||||
287 | if (@b > 1) { | ||||
288 | $self->quote_char ("\0"); | ||||
289 | } | ||||
290 | else { | ||||
291 | $self->quote_char ($quote); | ||||
292 | $quote = ""; | ||||
293 | } | ||||
294 | $self->{quote} = $quote; | ||||
295 | |||||
296 | my $ec = _check_sanity ($self); | ||||
297 | $ec and croak ($self->SetDiag ($ec)); | ||||
298 | |||||
299 | $self->_cache_set ($_cache_id{quote}, $quote); | ||||
300 | } | ||||
301 | my $quote = $self->{quote}; | ||||
302 | defined $quote && length ($quote) ? $quote : $self->{quote_char}; | ||||
303 | } # quote | ||||
304 | |||||
305 | sub escape_char | ||||
306 | { | ||||
307 | my $self = shift; | ||||
308 | @_ and $self->_set_attr_C ("escape_char", shift); | ||||
309 | $self->{escape_char}; | ||||
310 | } # escape_char | ||||
311 | |||||
312 | sub sep_char | ||||
313 | { | ||||
314 | my $self = shift; | ||||
315 | if (@_) { | ||||
316 | $self->_set_attr_C ("sep_char", shift); | ||||
317 | $self->_cache_set ($_cache_id{sep}, ""); | ||||
318 | } | ||||
319 | $self->{sep_char}; | ||||
320 | } # sep_char | ||||
321 | |||||
322 | sub sep | ||||
323 | { | ||||
324 | my $self = shift; | ||||
325 | if (@_) { | ||||
326 | my $sep = shift; | ||||
327 | defined $sep or $sep = ""; | ||||
328 | $] >= 5.008002 and utf8::decode ($sep); | ||||
329 | my @b = unpack "U0C*", $sep; | ||||
330 | if (@b > 1) { | ||||
331 | $self->sep_char ("\0"); | ||||
332 | } | ||||
333 | else { | ||||
334 | $self->sep_char ($sep); | ||||
335 | $sep = ""; | ||||
336 | } | ||||
337 | $self->{sep} = $sep; | ||||
338 | |||||
339 | my $ec = _check_sanity ($self); | ||||
340 | $ec and croak ($self->SetDiag ($ec)); | ||||
341 | |||||
342 | $self->_cache_set ($_cache_id{sep}, $sep); | ||||
343 | } | ||||
344 | my $sep = $self->{sep}; | ||||
345 | defined $sep && length ($sep) ? $sep : $self->{sep_char}; | ||||
346 | } # sep | ||||
347 | |||||
348 | sub eol | ||||
349 | { | ||||
350 | my $self = shift; | ||||
351 | if (@_) { | ||||
352 | my $eol = shift; | ||||
353 | defined $eol or $eol = ""; | ||||
354 | $self->{eol} = $eol; | ||||
355 | $self->_cache_set ($_cache_id{eol}, $eol); | ||||
356 | } | ||||
357 | $self->{eol}; | ||||
358 | } # eol | ||||
359 | |||||
360 | sub always_quote | ||||
361 | { | ||||
362 | my $self = shift; | ||||
363 | @_ and $self->_set_attr_X ("always_quote", shift); | ||||
364 | $self->{always_quote}; | ||||
365 | } # always_quote | ||||
366 | |||||
367 | sub quote_space | ||||
368 | { | ||||
369 | my $self = shift; | ||||
370 | @_ and $self->_set_attr_X ("quote_space", shift); | ||||
371 | $self->{quote_space}; | ||||
372 | } # quote_space | ||||
373 | |||||
374 | sub quote_null | ||||
375 | { | ||||
376 | my $self = shift; | ||||
377 | @_ and $self->_set_attr_X ("quote_null", shift); | ||||
378 | $self->{quote_null}; | ||||
379 | } # quote_null | ||||
380 | |||||
381 | sub quote_binary | ||||
382 | { | ||||
383 | my $self = shift; | ||||
384 | @_ and $self->_set_attr_X ("quote_binary", shift); | ||||
385 | $self->{quote_binary}; | ||||
386 | } # quote_binary | ||||
387 | |||||
388 | sub binary | ||||
389 | { | ||||
390 | my $self = shift; | ||||
391 | @_ and $self->_set_attr_X ("binary", shift); | ||||
392 | $self->{binary}; | ||||
393 | } # binary | ||||
394 | |||||
395 | sub decode_utf8 | ||||
396 | { | ||||
397 | my $self = shift; | ||||
398 | @_ and $self->_set_attr_X ("decode_utf8", shift); | ||||
399 | $self->{decode_utf8}; | ||||
400 | } # decode_utf8 | ||||
401 | |||||
402 | sub keep_meta_info | ||||
403 | { | ||||
404 | my $self = shift; | ||||
405 | @_ and $self->_set_attr_X ("keep_meta_info", shift); | ||||
406 | $self->{keep_meta_info}; | ||||
407 | } # keep_meta_info | ||||
408 | |||||
409 | sub allow_loose_quotes | ||||
410 | { | ||||
411 | my $self = shift; | ||||
412 | @_ and $self->_set_attr_X ("allow_loose_quotes", shift); | ||||
413 | $self->{allow_loose_quotes}; | ||||
414 | } # allow_loose_quotes | ||||
415 | |||||
416 | sub allow_loose_escapes | ||||
417 | { | ||||
418 | my $self = shift; | ||||
419 | @_ and $self->_set_attr_X ("allow_loose_escapes", shift); | ||||
420 | $self->{allow_loose_escapes}; | ||||
421 | } # allow_loose_escapes | ||||
422 | |||||
423 | sub allow_whitespace | ||||
424 | { | ||||
425 | my $self = shift; | ||||
426 | if (@_) { | ||||
427 | my $aw = shift; | ||||
428 | _unhealthy_whitespace ($self, $aw) and | ||||
429 | croak ($self->SetDiag (1002)); | ||||
430 | $self->_set_attr_X ("allow_whitespace", $aw); | ||||
431 | } | ||||
432 | $self->{allow_whitespace}; | ||||
433 | } # allow_whitespace | ||||
434 | |||||
435 | sub allow_unquoted_escape | ||||
436 | { | ||||
437 | my $self = shift; | ||||
438 | @_ and $self->_set_attr_X ("allow_unquoted_escape", shift); | ||||
439 | $self->{allow_unquoted_escape}; | ||||
440 | } # allow_unquoted_escape | ||||
441 | |||||
442 | sub blank_is_undef | ||||
443 | { | ||||
444 | my $self = shift; | ||||
445 | @_ and $self->_set_attr_X ("blank_is_undef", shift); | ||||
446 | $self->{blank_is_undef}; | ||||
447 | } # blank_is_undef | ||||
448 | |||||
449 | sub empty_is_undef | ||||
450 | { | ||||
451 | my $self = shift; | ||||
452 | @_ and $self->_set_attr_X ("empty_is_undef", shift); | ||||
453 | $self->{empty_is_undef}; | ||||
454 | } # empty_is_undef | ||||
455 | |||||
456 | sub verbatim | ||||
457 | { | ||||
458 | my $self = shift; | ||||
459 | @_ and $self->_set_attr_X ("verbatim", shift); | ||||
460 | $self->{verbatim}; | ||||
461 | } # verbatim | ||||
462 | |||||
463 | sub auto_diag | ||||
464 | { | ||||
465 | my $self = shift; | ||||
466 | if (@_) { | ||||
467 | my $v = shift; | ||||
468 | !defined $v || $v eq "" and $v = 0; | ||||
469 | $v =~ m/^[0-9]/ or $v = lc $v eq "false" ? 0 : 1; # true/truth = 1 | ||||
470 | $self->_set_attr_X ("auto_diag", $v); | ||||
471 | } | ||||
472 | $self->{auto_diag}; | ||||
473 | } # auto_diag | ||||
474 | |||||
475 | sub diag_verbose | ||||
476 | { | ||||
477 | my $self = shift; | ||||
478 | if (@_) { | ||||
479 | my $v = shift; | ||||
480 | !defined $v || $v eq "" and $v = 0; | ||||
481 | $v =~ m/^[0-9]/ or $v = lc $v eq "false" ? 0 : 1; # true/truth = 1 | ||||
482 | $self->_set_attr_X ("diag_verbose", $v); | ||||
483 | } | ||||
484 | $self->{diag_verbose}; | ||||
485 | } # diag_verbose | ||||
486 | |||||
487 | # status | ||||
488 | # | ||||
489 | # object method returning the success or failure of the most recent | ||||
490 | # combine () or parse (). there are no side-effects. | ||||
491 | |||||
492 | sub status | ||||
493 | { | ||||
494 | my $self = shift; | ||||
495 | return $self->{_STATUS}; | ||||
496 | } # status | ||||
497 | |||||
498 | sub eof | ||||
499 | { | ||||
500 | my $self = shift; | ||||
501 | return $self->{_EOF}; | ||||
502 | } # status | ||||
503 | |||||
504 | sub types | ||||
505 | { | ||||
506 | my $self = shift; | ||||
507 | if (@_) { | ||||
508 | if (my $types = shift) { | ||||
509 | $self->{_types} = join "", map { chr $_ } @{$types}; | ||||
510 | $self->{types} = $types; | ||||
511 | } | ||||
512 | else { | ||||
513 | delete $self->{types}; | ||||
514 | delete $self->{_types}; | ||||
515 | undef; | ||||
516 | } | ||||
517 | } | ||||
518 | else { | ||||
519 | $self->{types}; | ||||
520 | } | ||||
521 | } # types | ||||
522 | |||||
523 | sub callbacks | ||||
524 | { | ||||
525 | my $self = shift; | ||||
526 | if (@_) { | ||||
527 | my $cb; | ||||
528 | my $hf = 0x00; | ||||
529 | if (defined $_[0]) { | ||||
530 | grep { !defined $_ } @_ and croak ($self->SetDiag (1004)); | ||||
531 | $cb = @_ == 1 && ref $_[0] eq "HASH" ? shift | ||||
532 | : @_ % 2 == 0 ? { @_ } | ||||
533 | : croak ($self->SetDiag (1004)); | ||||
534 | foreach my $cbk (keys %$cb) { | ||||
535 | (!ref $cbk && $cbk =~ m/^[\w.]+$/) && ref $cb->{$cbk} eq "CODE" or | ||||
536 | croak ($self->SetDiag (1004)); | ||||
537 | } | ||||
538 | exists $cb->{error} and $hf |= 0x01; | ||||
539 | exists $cb->{after_parse} and $hf |= 0x02; | ||||
540 | exists $cb->{before_print} and $hf |= 0x04; | ||||
541 | } | ||||
542 | elsif (@_ > 1) { | ||||
543 | # (undef, whatever) | ||||
544 | croak ($self->SetDiag (1004)); | ||||
545 | } | ||||
546 | $self->_set_attr_X ("_has_hooks", $hf); | ||||
547 | $self->{callbacks} = $cb; | ||||
548 | } | ||||
549 | $self->{callbacks}; | ||||
550 | } # callbacks | ||||
551 | |||||
552 | # erro_diag | ||||
553 | # | ||||
554 | # If (and only if) an error occurred, this function returns a code that | ||||
555 | # indicates the reason of failure | ||||
556 | |||||
557 | sub error_diag | ||||
558 | { | ||||
559 | my $self = shift; | ||||
560 | my @diag = (0 + $last_new_err, $last_new_err, 0, 0); | ||||
561 | |||||
562 | if ($self && ref $self && # Not a class method or direct call | ||||
563 | $self->isa (__PACKAGE__) && exists $self->{_ERROR_DIAG}) { | ||||
564 | $diag[0] = 0 + $self->{_ERROR_DIAG}; | ||||
565 | $diag[1] = $self->{_ERROR_DIAG}; | ||||
566 | $diag[2] = 1 + $self->{_ERROR_POS} if exists $self->{_ERROR_POS}; | ||||
567 | $diag[3] = $self->{_RECNO}; | ||||
568 | |||||
569 | $diag[0] && $self && $self->{callbacks} && $self->{callbacks}{error} and | ||||
570 | return $self->{callbacks}{error}->(@diag); | ||||
571 | } | ||||
572 | |||||
573 | my $context = wantarray; | ||||
574 | unless (defined $context) { # Void context, auto-diag | ||||
575 | if ($diag[0] && $diag[0] != 2012) { | ||||
576 | my $msg = "# CSV_XS ERROR: $diag[0] - $diag[1] \@ rec $diag[3] pos $diag[2]\n"; | ||||
577 | if ($self && ref $self) { # auto_diag | ||||
578 | if ($self->{diag_verbose} and $self->{_ERROR_INPUT}) { | ||||
579 | $msg .= "$self->{_ERROR_INPUT}'\n"; | ||||
580 | $msg .= " " x ($diag[2] - 1); | ||||
581 | $msg .= "^\n"; | ||||
582 | } | ||||
583 | |||||
584 | my $lvl = $self->{auto_diag}; | ||||
585 | if ($lvl < 2) { | ||||
586 | my @c = caller (2); | ||||
587 | if (@c >= 11 && $c[10] && ref $c[10] eq "HASH") { | ||||
588 | my $hints = $c[10]; | ||||
589 | (exists $hints->{autodie} && $hints->{autodie} or | ||||
590 | exists $hints->{"guard Fatal"} && | ||||
591 | !exists $hints->{"no Fatal"}) and | ||||
592 | $lvl++; | ||||
593 | # Future releases of autodie will probably set $^H{autodie} | ||||
594 | # to "autodie @args", like "autodie :all" or "autodie open" | ||||
595 | # so we can/should check for "open" or "new" | ||||
596 | } | ||||
597 | } | ||||
598 | $lvl > 1 ? die $msg : warn $msg; | ||||
599 | } | ||||
600 | else { # called without args in void context | ||||
601 | warn $msg; | ||||
602 | } | ||||
603 | } | ||||
604 | return; | ||||
605 | } | ||||
606 | return $context ? @diag : $diag[1]; | ||||
607 | } # error_diag | ||||
608 | |||||
609 | sub record_number | ||||
610 | { | ||||
611 | my $self = shift; | ||||
612 | return $self->{_RECNO}; | ||||
613 | } # record_number | ||||
614 | |||||
615 | # string | ||||
616 | # | ||||
617 | # object method returning the result of the most recent combine () or the | ||||
618 | # input to the most recent parse (), whichever is more recent. there are | ||||
619 | # no side-effects. | ||||
620 | |||||
621 | sub string | ||||
622 | { | ||||
623 | my $self = shift; | ||||
624 | return ref $self->{_STRING} ? ${$self->{_STRING}} : undef; | ||||
625 | } # string | ||||
626 | |||||
627 | # fields | ||||
628 | # | ||||
629 | # object method returning the result of the most recent parse () or the | ||||
630 | # input to the most recent combine (), whichever is more recent. there | ||||
631 | # are no side-effects. | ||||
632 | |||||
633 | sub fields | ||||
634 | { | ||||
635 | my $self = shift; | ||||
636 | return ref $self->{_FIELDS} ? @{$self->{_FIELDS}} : undef; | ||||
637 | } # fields | ||||
638 | |||||
639 | # meta_info | ||||
640 | # | ||||
641 | # object method returning the result of the most recent parse () or the | ||||
642 | # input to the most recent combine (), whichever is more recent. there | ||||
643 | # are no side-effects. meta_info () returns (if available) some of the | ||||
644 | # field's properties | ||||
645 | |||||
646 | sub meta_info | ||||
647 | { | ||||
648 | my $self = shift; | ||||
649 | return ref $self->{_FFLAGS} ? @{$self->{_FFLAGS}} : undef; | ||||
650 | } # meta_info | ||||
651 | |||||
652 | sub is_quoted | ||||
653 | { | ||||
654 | my ($self, $idx, $val) = @_; | ||||
655 | ref $self->{_FFLAGS} && | ||||
656 | $idx >= 0 && $idx < @{$self->{_FFLAGS}} or return; | ||||
657 | $self->{_FFLAGS}[$idx] & 0x0001 ? 1 : 0; | ||||
658 | } # is_quoted | ||||
659 | |||||
660 | sub is_binary | ||||
661 | { | ||||
662 | my ($self, $idx, $val) = @_; | ||||
663 | ref $self->{_FFLAGS} && | ||||
664 | $idx >= 0 && $idx < @{$self->{_FFLAGS}} or return; | ||||
665 | $self->{_FFLAGS}[$idx] & 0x0002 ? 1 : 0; | ||||
666 | } # is_binary | ||||
667 | |||||
668 | sub is_missing | ||||
669 | { | ||||
670 | my ($self, $idx, $val) = @_; | ||||
671 | $idx < 0 || !ref $self->{_FFLAGS} and return; | ||||
672 | $idx >= @{$self->{_FFLAGS}} and return 1; | ||||
673 | $self->{_FFLAGS}[$idx] & 0x0010 ? 1 : 0; | ||||
674 | } # is_missing | ||||
675 | |||||
676 | # combine | ||||
677 | # | ||||
678 | # Object method returning success or failure. The given arguments are | ||||
679 | # combined into a single comma-separated value. Failure can be the | ||||
680 | # result of no arguments or an argument containing an invalid character. | ||||
681 | # side-effects include: | ||||
682 | # setting status () | ||||
683 | # setting fields () | ||||
684 | # setting string () | ||||
685 | # setting error_input () | ||||
686 | |||||
687 | sub combine | ||||
688 | { | ||||
689 | my $self = shift; | ||||
690 | my $str = ""; | ||||
691 | $self->{_FIELDS} = \@_; | ||||
692 | $self->{_FFLAGS} = undef; | ||||
693 | $self->{_STATUS} = (@_ > 0) && $self->Combine (\$str, \@_, 0); | ||||
694 | $self->{_STRING} = \$str; | ||||
695 | $self->{_STATUS}; | ||||
696 | } # combine | ||||
697 | |||||
698 | # parse | ||||
699 | # | ||||
700 | # Object method returning success or failure. The given argument is | ||||
701 | # expected to be a valid comma-separated value. Failure can be the | ||||
702 | # result of no arguments or an argument containing an invalid sequence | ||||
703 | # of characters. Side-effects include: | ||||
704 | # setting status () | ||||
705 | # setting fields () | ||||
706 | # setting meta_info () | ||||
707 | # setting string () | ||||
708 | # setting error_input () | ||||
709 | |||||
710 | sub parse | ||||
711 | { | ||||
712 | my ($self, $str) = @_; | ||||
713 | |||||
714 | my $fields = []; | ||||
715 | my $fflags = []; | ||||
716 | $self->{_STRING} = \$str; | ||||
717 | if (defined $str && $self->Parse ($str, $fields, $fflags)) { | ||||
718 | $self->{_FIELDS} = $fields; | ||||
719 | $self->{_FFLAGS} = $fflags; | ||||
720 | $self->{_STATUS} = 1; | ||||
721 | } | ||||
722 | else { | ||||
723 | $self->{_FIELDS} = undef; | ||||
724 | $self->{_FFLAGS} = undef; | ||||
725 | $self->{_STATUS} = 0; | ||||
726 | } | ||||
727 | $self->{_STATUS}; | ||||
728 | } # parse | ||||
729 | |||||
730 | sub column_names | ||||
731 | { | ||||
732 | my ($self, @keys) = @_; | ||||
733 | @keys or | ||||
734 | return defined $self->{_COLUMN_NAMES} ? @{$self->{_COLUMN_NAMES}} : (); | ||||
735 | |||||
736 | @keys == 1 && ! defined $keys[0] and | ||||
737 | return $self->{_COLUMN_NAMES} = undef; | ||||
738 | |||||
739 | if (@keys == 1 && ref $keys[0] eq "ARRAY") { | ||||
740 | @keys = @{$keys[0]}; | ||||
741 | } | ||||
742 | elsif (join "", map { defined $_ ? ref $_ : "" } @keys) { | ||||
743 | croak ($self->SetDiag (3001)); | ||||
744 | } | ||||
745 | |||||
746 | $self->{_BOUND_COLUMNS} && @keys != @{$self->{_BOUND_COLUMNS}} and | ||||
747 | croak ($self->SetDiag (3003)); | ||||
748 | |||||
749 | $self->{_COLUMN_NAMES} = [ map { defined $_ ? $_ : "\cAUNDEF\cA" } @keys ]; | ||||
750 | @{$self->{_COLUMN_NAMES}}; | ||||
751 | } # column_names | ||||
752 | |||||
753 | sub bind_columns | ||||
754 | { | ||||
755 | my ($self, @refs) = @_; | ||||
756 | @refs or | ||||
757 | return defined $self->{_BOUND_COLUMNS} ? @{$self->{_BOUND_COLUMNS}} : undef; | ||||
758 | |||||
759 | if (@refs == 1 && ! defined $refs[0]) { | ||||
760 | $self->{_COLUMN_NAMES} = undef; | ||||
761 | return $self->{_BOUND_COLUMNS} = undef; | ||||
762 | } | ||||
763 | |||||
764 | $self->{_COLUMN_NAMES} && @refs != @{$self->{_COLUMN_NAMES}} and | ||||
765 | croak ($self->SetDiag (3003)); | ||||
766 | |||||
767 | join "", map { ref $_ eq "SCALAR" ? "" : "*" } @refs and | ||||
768 | croak ($self->SetDiag (3004)); | ||||
769 | |||||
770 | $self->_set_attr_N ("_is_bound", scalar @refs); | ||||
771 | $self->{_BOUND_COLUMNS} = [ @refs ]; | ||||
772 | @refs; | ||||
773 | } # bind_columns | ||||
774 | |||||
775 | sub getline_hr | ||||
776 | { | ||||
777 | my ($self, @args, %hr) = @_; | ||||
778 | $self->{_COLUMN_NAMES} or croak ($self->SetDiag (3002)); | ||||
779 | my $fr = $self->getline (@args) or return; | ||||
780 | if (ref $self->{_FFLAGS}) { | ||||
781 | $self->{_FFLAGS}[$_] = 0x0010 for ($#{$fr} + 1) .. $#{$self->{_COLUMN_NAMES}}; | ||||
782 | } | ||||
783 | @hr{@{$self->{_COLUMN_NAMES}}} = @$fr; | ||||
784 | \%hr; | ||||
785 | } # getline_hr | ||||
786 | |||||
787 | sub getline_hr_all | ||||
788 | { | ||||
789 | my ($self, @args, %hr) = @_; | ||||
790 | $self->{_COLUMN_NAMES} or croak ($self->SetDiag (3002)); | ||||
791 | my @cn = @{$self->{_COLUMN_NAMES}}; | ||||
792 | [ map { my %h; @h{@cn} = @$_; \%h } @{$self->getline_all (@args)} ]; | ||||
793 | } # getline_hr_all | ||||
794 | |||||
795 | sub print_hr | ||||
796 | { | ||||
797 | my ($self, $io, $hr) = @_; | ||||
798 | $self->{_COLUMN_NAMES} or croak ($self->SetDiag (3009)); | ||||
799 | ref $hr eq "HASH" or croak ($self->SetDiag (3010)); | ||||
800 | $self->print ($io, [ map { $hr->{$_} } $self->column_names ]); | ||||
801 | } # print_hr | ||||
802 | |||||
803 | sub fragment | ||||
804 | { | ||||
805 | my ($self, $io, $spec) = @_; | ||||
806 | |||||
807 | my $qd = qr{\s* [0-9]+ \s* }x; # digit | ||||
808 | my $qs = qr{\s* (?: [0-9]+ | \* ) \s*}x; # digit or star | ||||
809 | my $qr = qr{$qd (?: - $qs )?}x; # range | ||||
810 | my $qc = qr{$qr (?: ; $qr )*}x; # list | ||||
811 | defined $spec && $spec =~ m{^ \s* | ||||
812 | \x23 ? \s* # optional leading # | ||||
813 | ( row | col | cell ) \s* = | ||||
814 | ( $qc # for row and col | ||||
815 | | $qd , $qd (?: - $qs , $qs)? # for cell (ranges) | ||||
816 | (?: ; $qd , $qd (?: - $qs , $qs)? )* # and cell (range) lists | ||||
817 | ) \s* $}xi or croak ($self->SetDiag (2013)); | ||||
818 | my ($type, $range) = (lc $1, $2); | ||||
819 | |||||
820 | my @h = $self->column_names (); | ||||
821 | |||||
822 | my @c; | ||||
823 | if ($type eq "cell") { | ||||
824 | my @spec; | ||||
825 | my $min_row; | ||||
826 | my $max_row = 0; | ||||
827 | for (split m/\s*;\s*/ => $range) { | ||||
828 | my ($tlr, $tlc, $brr, $brc) = (m{ | ||||
829 | ^ \s* ([0-9]+ ) \s* , \s* ([0-9]+ ) \s* | ||||
830 | (?: - \s* ([0-9]+ | \*) \s* , \s* ([0-9]+ | \*) \s* )? | ||||
831 | $}x) or croak ($self->SetDiag (2013)); | ||||
832 | defined $brr or ($brr, $brc) = ($tlr, $tlc); | ||||
833 | $tlr == 0 || $tlc == 0 || | ||||
834 | ($brr ne "*" && ($brr == 0 || $brr < $tlr)) || | ||||
835 | ($brc ne "*" && ($brc == 0 || $brc < $tlc)) | ||||
836 | and croak ($self->SetDiag (2013)); | ||||
837 | $tlc--; | ||||
838 | $brc-- unless $brc eq "*"; | ||||
839 | defined $min_row or $min_row = $tlr; | ||||
840 | $tlr < $min_row and $min_row = $tlr; | ||||
841 | $brr eq "*" || $brr > $max_row and | ||||
842 | $max_row = $brr; | ||||
843 | push @spec, [ $tlr, $tlc, $brr, $brc ]; | ||||
844 | } | ||||
845 | my $r = 0; | ||||
846 | while (my $row = $self->getline ($io)) { | ||||
847 | ++$r < $min_row and next; | ||||
848 | my %row; | ||||
849 | my $lc; | ||||
850 | foreach my $s (@spec) { | ||||
851 | my ($tlr, $tlc, $brr, $brc) = @$s; | ||||
852 | $r < $tlr || ($brr ne "*" && $r > $brr) and next; | ||||
853 | !defined $lc || $tlc < $lc and $lc = $tlc; | ||||
854 | my $rr = $brc eq "*" ? $#$row : $brc; | ||||
855 | $row{$_} = $row->[$_] for $tlc .. $rr; | ||||
856 | } | ||||
857 | push @c, [ @row{sort { $a <=> $b } keys %row } ]; | ||||
858 | if (@h) { | ||||
859 | my %h; @h{@h} = @{$c[-1]}; | ||||
860 | $c[-1] = \%h; | ||||
861 | } | ||||
862 | $max_row ne "*" && $r == $max_row and last; | ||||
863 | } | ||||
864 | return \@c; | ||||
865 | } | ||||
866 | |||||
867 | # row or col | ||||
868 | my @r; | ||||
869 | my $eod = 0; | ||||
870 | for (split m/\s*;\s*/ => $range) { | ||||
871 | my ($from, $to) = m/^\s* ([0-9]+) (?: \s* - \s* ([0-9]+ | \* ))? \s* $/x | ||||
872 | or croak ($self->SetDiag (2013)); | ||||
873 | $to ||= $from; | ||||
874 | $to eq "*" and ($to, $eod) = ($from, 1); | ||||
875 | $from <= 0 || $to <= 0 || $to < $from and croak ($self->SetDiag (2013)); | ||||
876 | $r[$_] = 1 for $from .. $to; | ||||
877 | } | ||||
878 | |||||
879 | my $r = 0; | ||||
880 | $type eq "col" and shift @r; | ||||
881 | $_ ||= 0 for @r; | ||||
882 | while (my $row = $self->getline ($io)) { | ||||
883 | $r++; | ||||
884 | if ($type eq "row") { | ||||
885 | if (($r > $#r && $eod) || $r[$r]) { | ||||
886 | push @c, $row; | ||||
887 | if (@h) { | ||||
888 | my %h; @h{@h} = @{$c[-1]}; | ||||
889 | $c[-1] = \%h; | ||||
890 | } | ||||
891 | } | ||||
892 | next; | ||||
893 | } | ||||
894 | push @c, [ map { ($_ > $#r && $eod) || $r[$_] ? $row->[$_] : () } 0..$#$row ]; | ||||
895 | if (@h) { | ||||
896 | my %h; @h{@h} = @{$c[-1]}; | ||||
897 | $c[-1] = \%h; | ||||
898 | } | ||||
899 | } | ||||
900 | |||||
901 | return \@c; | ||||
902 | } # fragment | ||||
903 | |||||
904 | 1 | 300ns | my $csv_usage = q{usage: my $aoa = csv (in => $file);}; | ||
905 | |||||
906 | sub _csv_attr | ||||
907 | { | ||||
908 | my %attr = (@_ == 1 && ref $_[0] eq "HASH" ? %{$_[0]} : @_) or croak; | ||||
909 | |||||
910 | $attr{binary} = 1; | ||||
911 | |||||
912 | my $enc = delete $attr{encoding} || ""; | ||||
913 | $enc =~ m/^[-\w.]+$/ and $enc = ":encoding($enc)"; | ||||
914 | |||||
915 | my $fh; | ||||
916 | my $cls = 0; # If I open a file, I have to close it | ||||
917 | my $in = delete $attr{in} || delete $attr{file} or croak $csv_usage; | ||||
918 | my $out = delete $attr{out} || delete $attr{file}; | ||||
919 | |||||
920 | ref $in eq "CODE" || ref $in eq "ARRAY" and $out ||= \*STDOUT; | ||||
921 | |||||
922 | if ($out) { | ||||
923 | $in or croak $csv_usage; # No out without in | ||||
924 | defined $attr{eol} or $attr{eol} = "\r\n"; | ||||
925 | if ((ref $out and ref $out ne "SCALAR") or "GLOB" eq ref \$out) { | ||||
926 | $fh = $out; | ||||
927 | } | ||||
928 | else { | ||||
929 | open $fh, ">$enc", $out or croak "$out: $!"; | ||||
930 | $cls = 1; | ||||
931 | } | ||||
932 | } | ||||
933 | |||||
934 | if ( ref $in eq "CODE" or ref $in eq "ARRAY") { | ||||
935 | # All done | ||||
936 | } | ||||
937 | elsif (ref $in eq "SCALAR") { | ||||
938 | # Strings with code points over 0xFF may not be mapped into in-memory file handles | ||||
939 | # "<$enc" does not change that :( | ||||
940 | open $fh, "<", $in or croak "Cannot open from SCALAR using PerlIO"; | ||||
941 | $cls = 1; | ||||
942 | } | ||||
943 | elsif (ref $in or "GLOB" eq ref \$in) { | ||||
944 | if (!ref $in && $] < 5.008005) { | ||||
945 | $fh = \*$in; | ||||
946 | } | ||||
947 | else { | ||||
948 | $fh = $in; | ||||
949 | } | ||||
950 | } | ||||
951 | else { | ||||
952 | open $fh, "<$enc", $in or croak "$in: $!"; | ||||
953 | $cls = 1; | ||||
954 | } | ||||
955 | $fh or croak qq{No valid source passed. "in" is required}; | ||||
956 | |||||
957 | my $hdrs = delete $attr{headers}; | ||||
958 | my $frag = delete $attr{fragment}; | ||||
959 | my $key = delete $attr{key}; | ||||
960 | |||||
961 | my $cbai = delete $attr{callbacks}{after_in} || | ||||
962 | delete $attr{after_in} || | ||||
963 | delete $attr{callbacks}{after_parse} || | ||||
964 | delete $attr{after_parse}; | ||||
965 | my $cbbo = delete $attr{callbacks}{before_out} || | ||||
966 | delete $attr{before_out}; | ||||
967 | my $cboi = delete $attr{callbacks}{on_in} || | ||||
968 | delete $attr{on_in}; | ||||
969 | |||||
970 | defined $attr{auto_diag} or $attr{auto_diag} = 1; | ||||
971 | my $csv = Text::CSV_XS->new (\%attr) or croak $last_new_err; | ||||
972 | |||||
973 | return { | ||||
974 | csv => $csv, | ||||
975 | fh => $fh, | ||||
976 | cls => $cls, | ||||
977 | in => $in, | ||||
978 | out => $out, | ||||
979 | hdrs => $hdrs, | ||||
980 | key => $key, | ||||
981 | frag => $frag, | ||||
982 | cbai => $cbai, | ||||
983 | cbbo => $cbbo, | ||||
984 | cboi => $cboi, | ||||
985 | }; | ||||
986 | } # _csv_attr | ||||
987 | |||||
988 | sub csv | ||||
989 | { | ||||
990 | # This is a function, not a method | ||||
991 | @_ && ref $_[0] ne __PACKAGE__ or croak $csv_usage; | ||||
992 | |||||
993 | my $c = _csv_attr (@_); | ||||
994 | |||||
995 | my ($csv, $in, $fh, $hdrs) = @{$c}{"csv", "in", "fh", "hdrs"}; | ||||
996 | |||||
997 | if ($c->{out}) { | ||||
998 | if (ref $in eq "CODE") { | ||||
999 | my $hdr = 1; | ||||
1000 | while (my $row = $in->($csv)) { | ||||
1001 | if (ref $row eq "ARRAY") { | ||||
1002 | $csv->print ($fh, $row); | ||||
1003 | next; | ||||
1004 | } | ||||
1005 | if (ref $row eq "HASH") { | ||||
1006 | if ($hdr) { | ||||
1007 | $hdrs ||= [ keys %$row ]; | ||||
1008 | $csv->print ($fh, $hdrs); | ||||
1009 | $hdr = 0; | ||||
1010 | } | ||||
1011 | $csv->print ($fh, [ @{$row}{@$hdrs} ]); | ||||
1012 | } | ||||
1013 | } | ||||
1014 | } | ||||
1015 | elsif (ref $in->[0] eq "ARRAY") { # aoa | ||||
1016 | ref $hdrs and $csv->print ($fh, $hdrs); | ||||
1017 | for (@{$in}) { | ||||
1018 | $c->{cboi} and $c->{cboi}->($csv, $_); | ||||
1019 | $c->{cbbo} and $c->{cbbo}->($csv, $_); | ||||
1020 | $csv->print ($fh, $_); | ||||
1021 | } | ||||
1022 | } | ||||
1023 | else { # aoh | ||||
1024 | my @hdrs = ref $hdrs ? @{$hdrs} : keys %{$in->[0]}; | ||||
1025 | defined $hdrs or $hdrs = "auto"; | ||||
1026 | ref $hdrs || $hdrs eq "auto" and $csv->print ($fh, \@hdrs); | ||||
1027 | for (@{$in}) { | ||||
1028 | $c->{cboi} and $c->{cboi}->($csv, $_); | ||||
1029 | $c->{cbbo} and $c->{cbbo}->($csv, $_); | ||||
1030 | $csv->print ($fh, [ @{$_}{@hdrs} ]); | ||||
1031 | } | ||||
1032 | } | ||||
1033 | |||||
1034 | $c->{cls} and close $fh; | ||||
1035 | return 1; | ||||
1036 | } | ||||
1037 | |||||
1038 | ref $in eq "CODE" and croak "CODE only valid fro in when using out"; | ||||
1039 | |||||
1040 | my $key = $c->{key} and $hdrs ||= "auto"; | ||||
1041 | if (defined $hdrs && !ref $hdrs) { | ||||
1042 | $hdrs eq "skip" and $csv->getline ($fh); | ||||
1043 | $hdrs eq "auto" and $hdrs = $csv->getline ($fh); | ||||
1044 | } | ||||
1045 | |||||
1046 | my $frag = $c->{frag}; | ||||
1047 | my $ref = ref $hdrs | ||||
1048 | ? # aoh | ||||
1049 | do { | ||||
1050 | $csv->column_names ($hdrs); | ||||
1051 | $frag ? $csv->fragment ($fh, $frag) : | ||||
1052 | $key ? { map { $_->{$key} => $_ } @{$csv->getline_hr_all ($fh)} } | ||||
1053 | : $csv->getline_hr_all ($fh); | ||||
1054 | } | ||||
1055 | : # aoa | ||||
1056 | $frag ? $csv->fragment ($fh, $frag) | ||||
1057 | : $csv->getline_all ($fh); | ||||
1058 | $ref or Text::CSV_XS->auto_diag; | ||||
1059 | $c->{cls} and close $fh; | ||||
1060 | if ($ref and $c->{cbai} || $c->{cboi}) { | ||||
1061 | for (@{$ref}) { | ||||
1062 | $c->{cbai} and $c->{cbai}->($csv, $_); | ||||
1063 | $c->{cboi} and $c->{cboi}->($csv, $_); | ||||
1064 | } | ||||
1065 | } | ||||
1066 | return $ref; | ||||
1067 | } # csv | ||||
1068 | |||||
1069 | 1 | 11µs | 1; | ||
1070 | |||||
1071 | __END__ | ||||
# spent 3µs within Text::CSV_XS::SetDiag which was called:
# once (3µs+0s) by Text::CSV::BEGIN@1 at line 94 | |||||
# spent 26µs within Text::CSV_XS::bootstrap which was called:
# once (26µs+0s) by DynaLoader::bootstrap at line 210 of DynaLoader.pm |