CSV

=encoding utf-8

DISCLAIMER

Note that updating these docs is an ongoing process and some perl5 idioms might not have been translated yet into correct raku idiom. My bad. Sorry. (Feedback welcome)

NAME

Text::CSV - comma-separated values manipulation routines

SYNOPSIS

use Text::CSV;
# Read whole file in memory
 my @aoa = csv(in => "data.csv");  # as array of arrays
 my @aoh = csv(in => "data.csv",
               headers => "auto"); # as array of hashes
# Write array of arrays as csv file
 csv(in => @aoa, out => "file.csv", sep => ";");
my @rows;
 # Read/parse CSV
 my $csv = Text::CSV.new;
 my $fh  = open "test.csv", :r, :!chomp;
 while (my @row = $csv.getline($fh)) {
     @row[2] ~~ m/pattern/ or next; # 3rd field should match
     @rows.push: @row;
     }
 $fh.close;
# and write as CSV
 $fh = open "new.csv", :w;
 $csv.say($fh, $_) for @rows;
 $fh.close;

DESCRIPTION

Text::CSV provides facilities for the composition and decomposition of comma-separated values. An instance of the Text::CSV class will combine fields into a CSV string and parse a CSV string into fields.

The module accepts either strings or files as input and support the use of user-specified characters (or sequences thereof) for delimiters, separators, and escapes.

In all following documentation, WIP stands for "Work In Progress" and NYI for "Not Yet Implemented". The goal is to get rid of all of those.

Embedded newlines

Important Note: The default behavior is to accept only UTF-8 characters.

But you still have the problem that you have to pass a correct line to the /parse method, which is more complicated from the usual point of usage:

my $csv = Text::CSV.new;
 for lines() : eager {          #  WRONG!
     $csv.parse($_);
     my @fields = $csv.fields;
     }

this will break for several reasons. The default open mode is to chomp lines, which will also remove the newline sequence if that sequence is not (part of) the newline at all. As the for might read broken lines: it does not care about the quoting. If you need to support embedded newlines, the way to go is to not pass eol in the parser (it accepts \n, \r, and \r\n by default) and then

my $csv = Text::CSV.new;
 my $io = open $file, :r, :!chomp;
 while (my $row = $csv.getline($io)) {
     my @fields = @$row;
     }

Binary data

For now, Text::CSV only accepts Unicode. Binary data is planned.

SPECIFICATION

While no formal specification for CSV exists, RFC 4180 (1) describes the common format and establishes text/csv as the MIME type registered with the IANA. RFC 7111 (2) adds fragments to CSV.

Many informal documents exist that describe the CSV format. "How To: The Comma Separated Value (CSV) File Format" (3) provides an overview of the CSV format in the most widely used applications and explains how it can best be used and supported.

1) https://datatracker.ietf.org/doc/html/rfc4180
 2) https://datatracker.ietf.org/doc/html/rfc7111
 3) http://creativyst.com/Doc/Articles/CSV/CSV01.shtml

The basic rules are as follows:

CSV is a delimited data format that has fields/columns separated by the comma character and records/rows separated by newlines. Fields that contain a special character (comma, newline, or double quote), must be enclosed in double quotes. However, if a line contains a single entry that is the empty string, it may be enclosed in double quotes. If a field's value contains a double quote character it is escaped by placing another double quote character next to it. The CSV file format does not require a specific character encoding, byte order, or line terminator format.

=over 2

  • *

Each record is a single line ended by a line feed (ASCII/LF=0x0A) or a carriage return and line feed pair (ASCII/CRLF=0x0D 0x0A), however, line-breaks may be embedded.

  • *

Fields are separated by commas.

  • *

Allowable characters within a CSV field include 0x09 (TAB) and the inclusive range of 0x20 (space) through 0x7E (tilde). In binary mode all characters are accepted, at least in quoted fields.

  • *

A field within CSV must be surrounded by double-quotes to contain a separator character (comma).

=back

Though this is the most clear and restrictive definition, Text::CSV is way more liberal than this, and allows extension:

=over 2

  • *

Line termination by a single carriage return is accepted by default

  • *

The separation, escape, and escape can be any valid Unicode sequence.

  • *

A field in CSV must be surrounded by double-quotes to make an embedded double-quote, represented by a pair of consecutive double-quotes, valid. You may additionally use the sequence "0 for representation of a NULL byte. Using 0x00 is just as valid.

  • *

Several violations of the above specification may be lifted by passing some options as attributes to the object constructor.

=back

METHODS

version version

Returns the current module version.

new new

Returns a new instance of class Text::CSV. The attributes are described by the optional named parameters

my $csv = Text::CSV.new(attributes ...);

The following attributes are available:

=over 4

  • eol eol

my $csv = Text::CSV.new(eol => "\r\n");
           $csv.eol(Str);
 my $eol = $csv.eol;

The end-of-line string to add to rows for /print or the record separator for /getline.

When not set in a parser instance, the default behavior is to accept \n, \r, and \r\n, so it is probably safer to not specify eol at all. Passing Str or the empty string behave the same.

As raku interprets \r\n as a single grapheme in input, it is dissuaded to use \r\n as eol when parsing. Please choose Str instead.

When not passed in a generating instance, records are not terminated at all, so it is probably wise to pass something you expect. A safe choice for eol on output is either \n or \r\n.

Common values for eol are "\012" (\n or Line Feed), "\015\012" (\r\n or Carriage Return, Line Feed), and "\015" (\r or Carriage Return).

  • sep sep

  • sep_char sep_char

  • sep-char sep-char

  • separator separator

my $csv = Text::CSV.new(sep => ";");
           $csv.sep("\x[ff0c]"); # FULLWIDTH COMMA
 my $sep = $csv.sep;

The sequence used to separate fields, by default a comma: (,). This sequence is required and cannot be disabled.

The separation sequence can not be equal to the quote sequence, the escape sequence or the newline sequence.

See also /CAVEATS

  • quote quote

  • quote_char quote_char

  • quote-char quote-char

my $csv = Text::CSV.new(quote => "'");
           $csv.quote("\x[ff02]); # FULLWIDTH QUOTATION MARK
           $csv.quote(Str);
 my $quo = $csv.quote;

The sequence to quote fields containing blanks or binary data, by default the double quote character ("). A value of Str disables quotation (for simple cases only).

The quotation sequence can not be equal to the separation sequence or the newline sequence.

See also /CAVEATS

  • escape escape

  • escape_char escape_char

  • escape-char escape-char

my $csv = Text::CSV.new(escape => "\\");
           $csv.escape("\x[241b]");  # SYMBOL FOR ESCAPE
           $csv.escape(Str);
 my $esc = $csv.escape;

The sequence to escape certain characters inside quoted fields.

The escape defaults to being the double-quote mark ("). In other words the same as the default sequence for quote. This means that doubling the quote mark in a field escapes it:

"foo","bar","Escape ""quote mark"" with two ""quote marks""","baz"

If you change quote without changing escape, escape will still be the double-quote ("). If instead you want to escape quote by doubling it you will need to also change escape to be the same as what you have changed quote to.

The escape sequence can not be equal to the separation sequence.

  • binary binary

WIP: Default is True. Non-UTF-8 real binary (Blob) does not yet parse. Opening the resource with encoding utf8-c8 is most likely the way to go.

my $csv = Text::CSV.new(:binary);
           $csv.binary(False);
 my $bin = $csv.binary;

If this attribute is True, you may use binary data in quoted fields, including line feeds, carriage returns and NULL bytes. (The latter could be escaped as "0.) By default this feature is on.

Note that valid Unicode (UTF-8) is not considered binary.

  • strict strict

my $csv = Text::CSV.new(:strict);
           $csv.strict(False);
 my $flg = $csv.strict;

If set to True, any row that parses to a different number of columns than the previous row will cause the parser to throw error 2014.

  • formula-handling formula-handling

  • formula_handling formula_handling

  • formula formula

my $csv = Text::CSV.new(formula => "none");
         $csv.formula("none");
 my $f = $csv.formula;

This defines the behavior of fields containing formulas. As formulas are considered dangerous in spreadsheets, this attribute can define an optional action to be taken if a field starts with an equal sign (=).

For purpose of code-readability, this can also be written as

my $csv = Text::CSV_XS.new(formula-handling => "none"});
         $csv.formula-handling("none");
 my $f = $csv.formula-handling;

or

my $csv = Text::CSV_XS.new(formula_handling => "none"});
         $csv.formula_handling("none");
 my $f = $csv.formula_handling;

Possible values for this attribute are

=over 2

  • none

Take no specific action. This is the default.

$csv.formula("none");
  • die

Cause the process to die whenever a leading = is encountered.

$csv.formula("die");
  • croak

Cause the process to die whenever a leading = is encountered.

$csv.formula("croak");

This option just exists for perl5 compatibility.

  • diag

Report position and content of the field whenever a leading = is found. The value of the field is unchanged.

$csv.formula("diag");
  • empty

Replace the content of fields that start with a = with the empty string.

$csv.formula("empty");
 $csv.formula("");
  • undef

Replace the content of fields that start with a = with Str.

$csv.formula("undef");
 $csv.formula(Str);

=back

All other values will throw an exception with error code 1500.

  • skip_empty_rows skip_empty_rows

  • skip-empty-rows skip-empty-rows

Defines optional actions for empty rows

my $csv = Text::CSV.new(skip_empty_rows => False);
           $csv.skip_empty_rows(True);
 my $ser = $csv.skip-empty-rows;

Possible values are

=over 2

  • 0 | False

This is the default. An empty line is seen as a single empty field.

  • 1 | True | "skip"

Empty lines are ignored.

  • 2 | "stop" | "eof"

Empty lines cause the parse to stop immediately without any error ignoring the rest of the data.

  • 3 | "die"

Empty lines cause the parse to stop immediately with an exception.

  • 5 | "error"

Empty lines cause the parse to stop immediately with error 2015 ("Empty row")

  • callback

On an empty row, the callback is invoked to get alternative data to use instead. The callback is expected to return a single string in valid CSV format.

=back

  • auto_diag auto_diag

  • auto-diag auto-diag

my $csv = Text::CSV.new(auto_diag => False);
           $csv.auto_diag(True);
 my $a-d = $csv.auto_diag;

Set this attribute to a number between 1 and 9 causes /error_diag to be automatically called in void context upon errors. The value True evaluates to 1.

In case of error 2012 - EOF, this call will be void.

If auto_diag is set to a numeric value greater than 1, it will die on errors instead of warn.

  • diag_verbose diag_verbose

  • diag-verbose diag-verbose

my $csv = Text::CSV.new(diag_verbose => 1);
           $csv.diag_verbose(2);
 my $d-v = $csv.diag_verbose;

Set the verbosity of the output triggered by auto_diag.

WIP: Does not add any information yet.

  • blank_is_undef blank_is_undef

  • blank-is-undef blank-is-undef

my $csv = Text::CSV.new(:blank_is_undef);
           $csv.blank_is_undef(False);
 my $biu = $csv.blank_is_undef;

Under normal circumstances, CSV data makes no distinction between quoted- and unquoted empty fields. These both end up in an empty string field once read, thus

1,"",," ",2

is read as

[ "1", "", "", " ", "2" ]

When writing CSV files with always_quote set, the unquoted empty field is the result of an undefined value. To enable this distinction when reading CSV data, the blank_is_undef attribute will cause unquoted empty fields to be set to Str, causing the above to be parsed as

[ "1", "", Str, " ", "2" ]
  • empty_is_undef empty_is_undef

  • empty-is-undef empty-is-undef

my $csv = Text::CSV.new(:empty_is_undef);
           $csv.empty_is_undef(False);
 my $eiu = $csv.empty_is_undef;

Going one step further than blank_is_undef, this attribute causes all empty fields to return as Str, so

1,"",," ",2

is read as

[ 1, Str, Str, " ", 2 ]

Note that this effects only fields that are originally empty, not fields that are empty after stripping allowed whitespace. YMMV.

  • allow_whitespace allow_whitespace

  • allow-whitespace allow-whitespace

my $csv = Text::CSV.new(:allow_whitespace);
           $csv.allow_whitespace(False);
 my $a-w = $csv.allow_whitespace;

When this option is set to True, the whitespace (TAB's and SPACE's) surrounding the separation sequence is removed when parsing. If either TAB or SPACE is one of the three major sequences sep, quote, or escape it will not be considered whitespace.

Now lines like:

1 , "foo" , bar , 3 , zapp

are parsed as valid CSV, even though it violates the CSV specs.

Note that all whitespace is stripped from both start and end of each field. That would make it more than a feature to enable parsing bad CSV lines, as

1,   2.0,  3,   ape  , monkey

will now be parsed as

[ "1", "2.0", "3", "ape", "monkey" ]

even if the original line was perfectly acceptable CSV.

  • allow_loose_quotes allow_loose_quotes

  • allow-loose-quotes allow-loose-quotes

my $csv = Text::CSV.new(:allow_loose_quotes);
           $csv.allow_loose_quotes(False);
 my $alq = $csv.allow_loose_quotes;

By default, parsing unquoted fields containing quote's like

1,foo "bar" baz,42

would result in parse error 2034. Though it is still bad practice to allow this format, we cannot help the fact that some vendors make their applications spit out lines styled this way.

If there is really bad CSV data, like

1,"foo "bar" baz",42

or

1,""foo bar baz"",42

there is a way to get this data-line parsed and leave the quotes inside the quoted field as-is. This can be achieved by setting allow_loose_quotes AND making sure that the escape is not equal to quote.

  • allow_loose_escapes allow_loose_escapes

  • allow-loose-escapes allow-loose-escapes

my $csv = Text::CSV.new(:allow_loose_escapes);
           $csv.allow_loose_escapes(False);
 my $ale = $csv.allow_loose_escapes;

Parsing fields that have escape sequences that escape characters that do not need to be escaped, like:

my $csv = Text::CSV.new(escape_char => "\\");
 $csv.parse(q{1,"my bar\'s",baz,42});

would result in parse returning False with reason 2025. Though it is bad practice to allow this format, this attribute enables you to treat all escape sequences equal.

  • allow_unquoted_escape allow_unquoted_escape

  • allow-unquoted-escape allow-unquoted-escape

my $csv = Text::CSV.new(:allow_unquoted_escape);
           $csv.allow_unquoted_escape(False);
 my $aue = $csv.allow_unquoted_escape;

A backward compatibility issue where escape differs from quote prevents escape to be in the first position of a field. If quote is equal to the default " and escape is set to \, this would be illegal:

1,\0,2

Setting this attribute to True might help to overcome issues with backward compatibility and allow this style.

  • always_quote always_quote

  • always-quote always-quote

my $csv = Text::CSV.new(:always_quote);
         $csv.always_quote(False);
 my $f = $csv.always_quote;

By default the generated fields are quoted only if they need to be. For example, if they contain the separator sequence. If you set this attribute to 1 then all defined fields will be quoted. (undefined (Str) fields are not quoted, see /blank_is_undef). This makes it quite often easier to handle exported data in external applications. (Poor creatures who are better to use Text::CSV. :)

  • quote_empty quote_empty

  • quote-empty quote-empty

my $csv = Text::CSV.new(:quote_empty);
           $csv.quote_empty(False);
 my $q-s = $csv.quote_empty;

By default the generated fields are quoted only if they need to be. An empty defined field does not need quotation. If you set this attribute to True then empty defined fields will be quoted. See also always_quote.

  • quote_space quote_space

  • quote-space quote-space

my $csv = Text::CSV.new(:quote_space);
           $csv.quote_space(False);
 my $q-s = $csv.quote_space;

By default, a space in a field would trigger quotation. As no rule exists this to be forced in CSV, nor any for the opposite, the default is True for safety. You can exclude the space from this trigger by setting this attribute to False.

  • escape_null escape_null

  • quote-null quote-null

my $csv = Text::CSV.new(:escape_null);
           $csv.escape_null(False);
 my $q-n = $csv.escape_null;

By default, a NULL byte in a field would be escaped. This option enables you to treat the NULL byte as a simple binary character in binary mode (the < binary => True > is set). The default is True. You can prevent NULL escapes by setting this attribute to False.

  • quote_binary quote_binary

  • quote-binary quote-binary

my $csv = Text::CSV.new(:quote_binary);
           $csv.quote_binary(False);
 my $q-b = $csv.quote_binary;

By default, all "unsafe" bytes inside a string cause the combined field to be quoted. By setting this attribute to False, you can disable that trigger for bytes >= 0x7F. (WIP)

  • keep_meta keep_meta

  • keep-meta keep-meta

my $csv = Text::CSV.new(:keep_meta);
           $csv.keep_meta(False);
 my $k-m = $csv.keep_meta_info;

By default, the parsing of input records is as simple and fast as possible. However, some parsing information - like quotation of the original field - is lost in that process. Setting this flag to true enables retrieving that information after parsing with the methods /meta_info, /is_quoted, and /is_binary described below. Default is False for ease of use.

If keep-meta is set to True, the returned fields are not of type Str but of type CSV::Field.

  • undef_str undef_str

  • undef-str undef-str

NYI - this should replace undefined values in generating CSV

  • comment_str comment_str

  • comment-str comment-str

my $csv = Text::CSV.new(comment_str => "#");
         $csv.comment_str (Str);
 my $s = $csv.comment_str;

This attribute optionally defines a string to be recognized as comment. If this attribute is defined, all lines starting with this sequence will not be parsed as CSV but skipped as comment.

This attribute has no meaning when generating CSV.

  • types

NYI

A set of column types; the attribute is immediately passed to the /types method.

  • callbacks callbacks

See the /Callbacks section below.

=back

To sum it up,

$csv = Text::CSV.new;

is equivalent to

$csv = Text::CSV.new(
     eol                   => Nil, # \r, \n, or \r\n
     sep                   => ',',
     quote                 => '"',
     escape                => '"',
     binary                => True,
     auto-diag             => False,
     diag-verbose          => 0,
     blank-is-undef        => False,
     empty-is-undef        => False,
     allow-whitespace      => False,
     allow-loose-quotes    => False,
     allow-loose-escapes   => False,
     allow-unquoted-escape => False,
     always-quote          => False,
     quote-space           => True,
     escape-null           => True,
     quote-binary          => True,
     keep-meta             => False,
     strict                => False,
     formula               => "none",
     undef-str             => Str,
     comment-str           => Str,
     types                 => Nil,
     callbacks             => Nil,
     });

For all of the above mentioned flags, an accessor method is available where you can inquire the current value, or change the value

my $quote = $csv.quote;
 $csv.binary(True);

It is not wise to change these settings halfway through writing CSV data to a stream. If however you want to create a new stream using the available CSV object, there is no harm in changing them.

If the /new constructor call fails, an exception of type CSV::Diac is thrown with the reason like the /error_diag method would return:

my $e;
 {   $csv = Text::CSV.new(ecs_char => ":") or
     CATCH { default { $e = $_; }}
     }
 $e and $e.message.say;

The message will be a string like

"INI - Unknown attribute 'ecs_char'"
$status = $csv.print($io,  $fld, ... );
 $status = $csv.print($io, ($fld, ...));
 $status = $csv.print($io, [$fld, ...]);
 $status = $csv.print($io,  @fld      );
$csv.column_names(%fld.keys); # or use a subset
 $status = $csv.print($io,  %fld      );

Similar to /combine + /string + /print, but much more efficient. It takes an IO object and any number of arguments interpreted as fields. The resulting string is immediately written to the $io stream.

NYI: no fields in combination with /bind_columns, like

$csv.bind_columns(\($foo, $bar));
 $status = $csv.print($fh);

A benchmark showed this order of preference, but the difference is within noise range:

my @data = ^20;
 $csv.print($io,   @data  );   # 2.6 sec
 $csv.print($io, [ @data ]);   # 2.7 sec
 $csv.print($io,    ^20   );   # 2.7 sec
 $csv.print($io,  \@data  );   # 2.8 sec

say say

Is the same a s/print where eol defaults to $*OUT.nl.

$status = $csv.say($io,  $fld, ... );
 $status = $csv.say($io, ($fld, ...));
 $status = $csv.say($io, [$fld, ...]);
 $status = $csv.say($io,  @fld      );
$csv.column_names(%fld.keys); # or use a subset
 $status = $csv.say($io,  %fld      );

combine combine

$status = $csv.combine(@fields);
 $status = $csv.combine($fld, ...);
 $status = $csv.combine(\@fields);

This method constructs a CSV row from @fields, returning success or failure. Failure can result from lack of arguments or an argument that contains invalid data. Upon success, /string can be called to retrieve the resultant CSV string. Upon failure, the value returned by /string is undefined and /error_input could be called to retrieve the invalid argument. (WIP)

string string

$line = $csv.string;

This method returns the input to /parse or the resultant CSV string of /combine, whichever was called more recently. If eol is defined, it is added to the string.

getline getline

@row = $csv.getline($io);
 @row = $csv.getline($io,  :meta);
 @row = $csv.getline($str);
 @row = $csv.getline($str, :meta);

This is the counterpart to /print, as /parse is the counterpart to /combine: it parses a row from the $io handle or $str using the /getline method associated with $io (or the internal temporary IO handle used to read from the string as if it were an IO handle) and parses this row into an array. This array is returned by the function or Array for failure. When $io does not support getline, you are likely to hit errors.

NYI: When fields are bound with /bind_columns the return value is a reference to an empty list.

getline_all

getline-all getline_all getline-all

@rows = $csv.getline_all($io);
 @rows = $csv.getline_all($io,                   :meta);
 @rows = $csv.getline_all($io, $offset);
 @rows = $csv.getline_all($io, $offset,          :meta);
 @rows = $csv.getline_all($io, $offset, $length);
 @rows = $csv.getline_all($io, $offset, $length, :meta);

This will return a list of getline($io) results. If $offset is negative, as with splice, only the last abs($offset) records of $io are taken into consideration.

Given a CSV file with 10 lines:

lines call
 ----- ---------------------------------------------------------
 0..9  $csv.getline_all($io)         # all
 0..9  $csv.getline_all($io,  0)     # all
 8..9  $csv.getline_all($io,  8)     # start at 8
 -     $csv.getline_all($io,  0,  0) # start at 0 first 0 rows
 0..4  $csv.getline_all($io,  0,  5) # start at 0 first 5 rows
 4..5  $csv.getline_all($io,  4,  2) # start at 4 first 2 rows
 8..9  $csv.getline_all($io, -2)     # last 2 rows
 6..7  $csv.getline_all($io, -4,  2) # first 2 of last  4 rows

getline_hr

getline-hr getline_hr getline-hr

The /getline_hr and /column_names methods work together to allow you to have rows returned as hashes instead of arrays. You must invoke /column_names first to declare your column names.

$csv.column_names(< code name price description >);
 %hr = $csv.getline_hr($str, :meta);
 %hr = $csv.getline_hr($io);
 say "Price for %hr<name> is %hr<price> \c[EURO SIGN]";

/getline_hr will fail if invoked before /column_names.

getline_hr_all

getline-hr-all getline_hr_all getline-hr-all

@rows = $csv.getline_hr_all($io);
 @rows = $csv.getline_hr_all($io,                   :meta);
 @rows = $csv.getline_hr_all($io, $offset);
 @rows = $csv.getline_hr_all($io, $offset,          :meta);
 @rows = $csv.getline_hr_all($io, $offset, $length);
 @rows = $csv.getline_hr_all($io, $offset, $length, :meta);

This will return a list of getline_hr($io) results.

parse parse

$status = $csv.parse($line);

This method decomposes a CSV string into fields, returning success or failure. Failure can result from a lack of argument or improper format in the given CSV string. Upon success, invoke /fields or /strings to get the decomposed fields. Upon failure these methods shall not be trusted to return reliable data.

NYI: You may use the /types method for setting column types. See /types' description below.

fragment fragment

This function implements RFC7111 (URI Fragment Identifiers for the text/csv Media Type).

my @rows = $csv.fragment($io, $spec);

In specifications, * is used to specify the last item, a dash (-) to indicate a range. All indices are 1-based: the first row or column has index 1. Selections can be combined with the semi-colon (;).

When using this method in combination with /column_names, the returned array will be a list of hashes instead of an array of arrays. A disjointed cell-based combined selection might return rows with different number of columns making the use of hashes unpredictable.

$csv.column_names(< Name Age >);
 my @rows = $csv.fragment($io, "row=3;8");

Note that for col="..", the column names are the names for before the selection is taken to make it more consistent with reading possible headers from the first line of the CSV datastream.

$csv,column_names(< foo bar >);  # WRONG
 $csv.fragment($io, "col=3");

would set the column names for the first two columns that are then skipped in the fragment. To skip the unwanted columns, use placeholders.

$csv.column_names(< x x Name >);
 $csv.fragment($io, "col=3");

WIP: If the /after_parse callback is active, it is also called on every line parsed and skipped before the fragment.

=over 2

  • row

row=4
 row=5-7
 row=6-*
 row=1-2;4;6-*
  • col

col=2
 col=1-3
 col=4-*
 col=1-2;4;7-*
  • cell

In cell-based selection, the comma (,) is used to pair row and column

cell=4,1

The range operator (-) using cells can be used to define top-left and bottom-right cell location

cell=3,1-4,6

The * is only allowed in the second part of a pair

cell=3,2-*,2    # row 3 till end, only column 2
 cell=3,2-3,*    # column 2 till end, only row 3
 cell=3,2-*,*    # strip row 1 and 2, and column 1

Cells and cell ranges may be combined with ;, possibly resulting in rows with different number of columns

cell=1,1-2,2;3,3-4,4;1,4;4,1

Disjointed selections will only return selected cells. The cells that are not specified will not be included in the returned set, not even as Str. As an example given a CSV like

11,12,13,...19
 21,22,...28,29
 :            :
 91,...97,98,99

with cell=1,1-2,2;3,3-4,4;1,4;4,1 will return:

11,12,14
 21,22
 33,34
 41,43,44

Overlapping cell-specs will return those cells only once, So cell=1,1-3,3;2,2-4,4;2,3;4,2 will return:

11,12,13
 21,22,23,24
 31,32,33,34
 42,43,44

=back

RFC7111 does not allow different types of specs to be combined (either row or col or cell). Passing an invalid fragment specification will croak and set error 2013.

Using /colrange and /rowrange instead of /fragment will allow you to combine row- and column selecting as a grid.

colrange colrange

my Int @range = ^5, 5..9;
 $csv.colrange(@range);
 $csv.colrange("0-4;6-10");
 my @range = $csv.colrange;

Set or inspect the column ranges. When passed as an array of Int, the indexes are 0-based. When passed as a string, the syntax of the range is as defined by RFC7111 and thus 1-based.

rowrange rowrange

$csv.rowrange("1;16-*");
 my @r = $csv.rowrange;

Set or inspect the row ranges. Only supports RFC7111 style. Indexes are 1-based.

column_names

column-names column_names column-names

Set the "keys" that will be used in the /getline_hr calls. If no keys (column names) are passed, it will return the current setting as a list.

$csv.column_names(< code description price >);
 my @names = $csv.column_names;

/column_names accepts a list of strings (the column names) or a single array with the names. You can pass the return value from /getline too:

$csv.column_names($csv.getline($io));

/column_names does no checking on duplicates at all, which might lead to unexpected results. As raku does not accept undefined keys in a hash, passing just types will lead to fail later on.

$csv.column_names(Str, "", "name"); # Will FAIL becaus of Str
 $csv.column_names(< code name count name >); # will drop the second column
 %hr = $csv.getline_hr($io);

Parse the CSV header and set sep and encoding.

my @hdr = $csv.header($fh).column-names;
 $csv.header($fh, sep-set => [ ";", ",", "|", "\t" ]);
 $csv.header($fh, munge-column-names => "fc");

The first argument should be a file handle.

Assuming that the file opened for parsing has a header, and the header does not contain problematic characters like embedded newlines, read the first line from the open handle then auto-detect whether the header separates the column names with a character from the allowed separator list.

If any of the allowed separators matches, and none of the other allowed separators match, set sep to that separator for the current CSV_XS instance and use it to parse the first line, map those to lowercase, and use that to set the instance /column_names:

my $csv = Text::CSV.new;
 my $fh = open "file.csv";
 $csv.header($fh);
 while (my $row = $csv.getline_hr($fh)) {
     ...
     }

If the header is empty, contains more than one unique separator out of the allowed set, contains empty fields, or contains identical fields (after folding), it will croak with error 1010, 1011, 1012, or 1013 respectively.

If the header contains embedded newlines or is not valid CSV in any other way, this method will throw an exception.

A successful call to header will always set the sep of the $csv object. This behavior can not be disabled.

return value

On error this method will throw an exception.

On success, this method will return the instance.

Options

=over 2

  • sep-set

$csv.header($fh, sep_set => [ ";", ",", "|", "\t" ]);

The list of legal separators defaults to [ ";", "," ] and can be changed by this option.

Multi-byte sequences are allowed, both multi-character and Unicode. See sep.

  • munge-column-names

This option offers the means to modify the column names into something that is most useful to the application. The default is to map all column names to fold case.

$csv.header($fh, munge-column-names => "lc");

The following values are available:

fc   - fold case
  lc   - lower case
  uc   - upper case
  none - do not change
  &cb  - supply a callback
$csv.header($fh, munge-column-names => { "column_".$col++ });
  • set-column-names

$csv.header($fh, :set-column-names);

The default is to set the instances column names using /column_names if the method is successful, so subsequent calls to /getline_hr can return a hash. Disable setting the header can be forced using a false value for this option like :!set-column-names.

=back

bind_columns

bind-columns bind_columns bind-columns

NYI!

Takes a list of scalar references to be used for output with /print or to store in the fields fetched by /getline. When you do not pass enough references to store the fetched fields in, /getline will fail with error 3006. If you pass more than there are fields to return, the content of the remaining references is left untouched.

$csv.bind_columns(\$code, \$name, \$price, \$description);
 while ($csv.getline($io)) {
     print "The price of a $name is \x[20ac] $price\n";
     }

To reset or clear all column binding, call /bind_columns with the single undefined argument like Array. This will also clear column names.

$csv.bind_columns(Array);

If no arguments are passed at all, /bind_columns will return the list of current bindings or Array if no binds are active.

eof eof

$eof = $csv.eof;

If /parse or /getline was used with an IO stream, this method will return True if the last call hit end of file, otherwise it will return False. This is useful to see the difference between a failure and end of file.

If the last /parse or /getline finished just before end of file, the next /parse or /getline will fail and set eof.

That implies that if you are not using /auto-diag, an idiom like

while (my @row = $csv.getline ($fh)) {
     # ...
     }
 $csv.eof or $csv.error_diag;

will not report the error. You would have to change that to

while (my @row = $csv.getline ($fh)) {
     # ...
     }
 $csv.error_diag.error and $csv.error_diag;

types types

NYI!

$csv.types(@types);
 my @types = $csv.types;

This method is used to force a type for all fields in a column. For example, if you have an integer column, two columns with doubles and a string column, then you might do a

$csv.types(Int, Num, Num, Str);

You can unset column types by doing a

$csv.types(Array);

row row

CSV::Row $row = $csv.row;

Returns the last row parsed. See CSV::Row

fields fields

CSV::Field @fields = $csv.fields;

This method returns the input to /combine or the resultant decomposed fields of a successful /parse or /getline, whichever was called more recently. The fields are still of type CSV::Field and thus feature attributes.

strings strings

@fields = $csv.strings;

This method returns the input to /combine or the resultant decomposed fields of a successful /parse or /getline, whichever was called more recently. The fields are simplified to Str entries from CSV::Field, so no attributes ate available.

NYI: If types are used, the fields should comply to the types.

meta_info meta_info

meta-info meta-info

$csv.meta_info(True);
 my $km = $csv.keep_meta;

This methods sets or inquires the default setting for keeping meta-info on fields. See CSV::Field.

is_quoted is_quoted

is-quoted is-quoted

my $quoted = $csv.is_quoted($column_idx);

Where $column_idx is the (zero-based) index of the column in the last result of /parse or /getline, even if meta was false on that last invocation.

This returns True if the data in the indicated column was enclosed in quote_char quotes. This might be important for fields where content ,20070108, is to be treated as a numeric value, and where ,"20070108", is explicitly marked as character string data.

Also see CSV::Field.

is_binary is_binary

is-binary is-binary

NYI/WIP: utf8-c8

my $binary = $csv.is_binary($column_idx);

Where $column_idx is the (zero-based) index of the column in the last result of /parse or /getline, even if meta was false on that last invocation.

Also see CSV::Field.

is_missing is_missing

is-missing is-missing

NYI

my $missing = $csv.is_missing($column_idx);

Where $column_idx is the (zero-based) index of the column in the last result of /parse or /getline, even if meta was false on that last invocation.

while (my @row = $csv.getline_hr($fh, :meta)) {
     $csv.is_missing(0) and next; # This was an empty line
     }

When using /getline_hr, it is impossible to tell if the parsed fields are undefined because they where not filled in the CSV stream or because they were not read at all, as all the fields defined by /column_names are set in the hash-ref. If you still need to know if all fields in each row are provided, you should enable keep_meta so you can check the flags.

status status

$status = $csv.status;

This method returns success (or failure) of the last invoked /combine or /parse call.

error_input error_input

error-input error-input

$bad_argument = $csv.error_input;

This method returns the erroneous argument (if it exists) of /combine, /parse, or /getline, whichever was called most recent. If the last invocation was successful, error_input will return Str.

DIAGNOSTICS

Errors are transported internally using the CSV::Diag class. Text::CSV will return that object when it fails, so it can be caught, but on non-fatal failures, like parse returning False, one can use the methods to inquire the internal status.

CSV::Diag

The method is created with the widest possible use in mind, serving both the mindset of raku as well as the direct approach of the old module. It is immutable: it is created with all available error parameters known at the time of failure, and the cannot be changed afterwards.

my CSV::Diag $d .= new(
     error   => 0,
     message => "",
     pos     => 0,
     field   => 0,
     record  => 0,
     buffer  => Str
     );

If only error is given, the message is set accordingly if it is a known error value.

The object can be used in many contexts:

=over 2

  • void context

CSV::Diag.new(error => 2034, buffer => q{ "",}, pos => 1);

will print

EIF - Loose unescaped quote : error 2034 @ record 1, field 1, position 2
  "?",

which is what happens when auto_diag is True and you parse illegal CSV:

Text::CSV.new(:auto_diag).parse(q{ "",});'
 EIF - Loose unescaped quote : error 2034 @ record 1, field 1, position 2
  "?",
  • numeric context

Will return the error code

my Int $e;
 {   fail CSV::Diag(error => 2034);
     CATCH { default { $e = +$_; }}
     }
 # $e is now 2034
  • string context

Will return the error message

my Str $e;
 {   fail CSV::Diag(error => 2034);
     CATCH { default { $e = ~$_; }}
     }
 # $e is now "EIF - Loose unescaped quote"
  • list context

All of the 6 items can be retrieved as a list or positional:

{   fail CSV::Diag(error => 2034);
     CATCH { default { $_[0].say; }}
     }

The indices are chosen to be compatible with the old API

$e[0] = error number
 $e[1] = error message
 $e[2] = error position in buffer
 $e[3] = field number
 $e[4] = record number
 $e[5] = errror buffer
  • hash context

All of the 6 items can be retrieved as a hash entry

{   fail CSV::Diag(error => 2034);
     CATCH { default { $_<errno>.say; }}
     }

The keys are chosen to be compatible with the old API.

$e<errno>  = error number
 $e<error>  = error message
 $e<pos>    = error position in buffer
 $e<field>  = field number
 $e<recno>  = record number
 $e<buffer> = errror buffer

=back

The CSV::Diag is also used by this Text::CSV method

error_diag error_diag

error-diag error-diag

$csv.error_diag;
 $error_code = +$csv.error_diag;
 $error_str  = ~$csv.error_diag;
 ($cde, $str, $pos, $rec, $fld) = $csv.error_diag;

This function returns the diagnostics of the most recent error.

If called in void context, this will print the internal error code and the associated error message along with the record number, the position of the failure and the buffer of failure with an eject symbol at that position:

$csv.parse(q{ "",})
 $csv.error_diag;

will print

EIF - Loose unescaped quote : error 2034 @ record 1, field 1, position 2
  "?",

If called in list context, this will return the error code, the error message, the location within the line that was being parsed, the record number, and the buffer itself in that order. Their values are 1-based. The position currently is index of the character at which the parsing failed in the current record. The record number the index of the record parsed by the csv instance. The field number is the index of the field the parser thinks it is currently trying to parse.

If called as +$csv.error_diag or $csv.error_diag.Num, it will return the error code. If called as ~$csv.error_diag or $csv.error_diag.Str it will return the error message.

record_number record_number

$recno = $csv.record_number;

Returns the records parsed by this csv instance. This value should be more accurate than $. when embedded newlines come in play. Records written by this instance are not counted.

set_diag set_diag

$csv.set_diag(0);
 $csv.set_diag(2025, pos => 12, fieldno => 4, recno => 99);

Use to (re)set the diagnostics if you are dealing with errors.

CSV::Field

The fields are internally represented as CSV::Field objects. Any methods that directly or indirectly supports the meta attribute controls weather the returned fields will be of this CSV::Field type or that the fields are simplified to a simple basic type.

If the fields are represented/returned as CSV::Field, it supports these methods:

new

my CSV::Field $f .= new;
 my $f = CSV::Field.new("foo");
 my $f = CSV::Field.new(1);

Instantiate a new field. Optionally takes a Cool.

Bool

?$f     and say "The field is true";
 $f.Bool and say "This field is true too";

Return the boolean value of the field. As CSV is text-only be design, this will also return False for "0", where raku sees "0" as True.

text

Str

$str = ~$f;
 $str = $f.Str;
 $str = $f.text;

Return the string representation of the field.

Buf

$buf = $f.Buf;

Return the field data as "utf8-c8" encoded Buf.

Numeric

$i = +$f;
 $i = $f.Int;
 $i = $f.Num;

Return the numeric representation of the field.

gist

$f.gist.say;

Will show a complete compressed representation of the field with properties. Q is quoted, q is unquoted. Likewise for B/b for binary, 8/7 for Unicode-ness and M/m for missing.

A field that was parsed as ,cat, would return

qb7m:"cat"

A field parsed as ,"Ħēłĺº", would return

QB8m:"Ħēłĺº"

add

$f.add($str);

Accepts a Str to be added to this field.

set_quoted

$f.set_quoted;

Set the fact the the field was/is quoted.

is_quoted

$f.is_quoted and say "The field was quoted in CSV";

Is True when the parsed field was quoted in the source.

undefined

$f.undefined and say "The field is undefined";

Returns True when the field is undefined. As CSV is all about strings, the various options that allow interpreting empty fields as undefined make this a required method.

is_binary

WIP: utf8-c8

$f.is_binary and say "Do we need a Buf instead?";

Returns true if the field has data beyond ASCII.

is_utf8

$f.is_utf8 or say "The field is empty or pure ACII";

Returns True if the field is beyond ASCII, but still valid UTF-8.

is_missing

WIP

$f.is_missing and fail;

Returns True if the field is missing.

CSV::Row CSV::Row

This class is a wrapper over the current row of fields just to add convenience methods.

This is the only argument to callbacks.

The fields in CSV::Row are always of class CSV::Field and thus contain all meta-information, even if the Text::CSV attribute meta_info is False.

methods

=over 2

  • new

my CSV::Row $row .= new;
 my CSV::Row $row .= new(csv => $csv, fields => @f.map({ CSV::Field.new(*) });
  • csv

The current Text::CSV object related to this row.

  • elems

Return the number of fields in the row.

  • fields

The fields (CSV::Field items) this row consist of.

  • push

$row.push(CSV::Field.new(1));
 $row.push(1);
 $row.push("foo");
 $row.push($another-row);

Pushing simple things onto the row will extend the row by converting these to CSV::Field objects.

Pushing a CSV::Row onto the row will extend that row with the fields of the row being pushed.

  • pop

my CSV::Field $f = $row.pop;
  • Str

my $str = $row.Str;
 $io.say(~$row);

The stringification of the CSV::Row object is like invoking the string method. This only works if there is a Text::CSV object known to the CSV::Row instance.

  • hash

my %h = $row.hash;

Returns the hash with .csv's column_names as keys and the .text of each matching fields entry as values.

  • strings

my @l = $row.strings;

Returns the .text part of each entry in .fields.

=back

The row allow direct indexing and iteration as well as hash addressing when /column_names are set.

my $field = $row[1];
 my $field = $row<foo>;

The last parsed row of a Text::CSV|/Text::CSV> can be acquired using

my CSV::Row $row = $csv.row;

################################################################################

FUNCTIONS

csv csv

This is an high-level function that aims at simple (user) interfaces. This can be used to read/parse a CSV file or stream (the default behavior) or to produce a file or write to a stream (define the out attribute). It returns an array- or hash-reference on parsing or the numeric value of /error_diag on writing. When this function fails you can get to the error using the class call to /error_diag

my $aoa = csv(in => "test.csv") or
     die Text::CSV.error_diag;

This function takes the arguments as key-value pairs. This can be passed as a list or as an anonymous hash:

my $aoa = csv(in => "test.csv", sep => ";");
 my $aoh = csv(in => $fh, :headers);

The arguments passed consist of two parts: the arguments to /csv itself and the optional attributes to the CSV object used inside the function as enumerated and explained in /new.

If not overridden, the default option used for CSV is

auto_diag => 1

The option that is always set and cannot be altered is

binary    => 1

in in

Used to specify the source. in can be a file name (e.g. "file.csv"), which will be opened for reading and closed when finished, a file handle (e.g. $fh or FH), a reference to a glob (e.g. \*ARGV), the glob itself (e.g. *STDIN), or a reference to a scalar (e.g. \q{1,2,"csv"}).

When used with /out, in should be a reference to a CSV structure (AoA or AoH) or a Callable (Sub, Routine, Code, or Block) that returns an array-reference or a hash-reference. The code-ref will be invoked with no arguments.

my $aoa = csv(in => "file.csv");
open my $fh, "<", "file.csv";
 my $aoa = csv(in => $fh);
my $csv = [[qw( Foo Bar )], [ 1, 2 ], [ 2, 3 ]];
 my $err = csv(in => $csv, out => "file.csv");

The in attribute supports a wide range of types, all of which can be combined with the use of fragment:

=over 2

  • Str

csv(in => "file.csv")

A plain string is interpreted as a file name to be opened for parsing.

  • IO

my $io = open "file.csv", :r;
 csv(in => $io);

Parse from the already opened data stream.

  • Capture

csv(in => \("str,ing"));

Parse from a single string.

  • Array of Strings

csv(in => ["a,b\n1,2\n3,4\n"]);
 csv(in => ["a,b", "1,2", "3,4"]);

Parse from the String(s)

  • Array of Data

csv(in => [[<a b>], [1, 2], [3, 4]]);
 csv(in => [{:a(1), :b(2)}, {:a(3), :b(4)}]);

Use the data as provided

  • Sub, Routine

sub provider {
     @data.elems == 0 and return False;
     return @data.pop;
     }
 csv(in => &provider);

While the providing Routine returns a data row, use that as is. Stop when the provider returns False.

  • Callable, Block

csv(in => { $sth.fetch });

While the providing Callable returns a data row, use that as is. Stop when the provider returns False.

  • Supply

my $supply = Supply.from-list(@data);
 csv(in => $supply);

Fetch data rows from the supply.

  • Channel

my $ch = Channel.new;
 start {
     $ch.send($_) for @data;
     $ch.close;
     }
 csv(in => $ch);

Fetch data from the Channel.

  • Iterator

csv(in => @data.iterator);

Fetch data rows from the iterator.

  • Any

csv(in => Any);

is a shortcut/fallback for

csv(in => $*IN);

=back

out out

In output mode, the default CSV options when producing CSV are

eol    => "\r\n"

The /fragment attribute is ignored in output mode.

out can be a file name (e.g. "file.csv"), which will be opened for writing and closed when finished, a file handle (e.g. $fh or IO::Handle), a Channel, a Supply, a Callable, or Nil.

csv(in => sub { $sth.fetch },        out => "dump.csv");
 csv(in => { $sth.fetchrow_hashref }, out => "dump.csv",
      headers => $sth.{NAME_lc});

When a code-ref is used for in, the output is generated per invocation, so no buffering is involved. This implies that there is no size restriction on the number of records. The csv function ends when the coderef returns False.

When a Callable is used for out, it is called on each row with the row as only argument.

When Nil is used for out, the output is completely suppressed. This can be useful when streaming, and the output is not required, but the side-effects of e.g. an on-in hook is dealing with the data.

=over 2

  • Str:U

my $str = csv(in => $in, out => Str);

Returns a single String of CSV data.

  • Str:D

my $str = csv(in => $in, out => "out.csv");

Writes the data as CSV to the named file.

  • Array:U

my $aoa = csv(in => $in, out => Array);

Returns an Array of Arrays.

  • Array:D

my $aoa = csv(in => $in, out => @data);

Adds the records to an existing Array. If the existing array is not empty, the type of the first record determines adding a list of Arrays or a list of Hashes.

  • Hash:U

my $aoh = csv(in => $in, out => Hash);

Returns an Array of Hashes

  • IO:D

my $io = open "file.csv", :w;
 csv(in => $in, out => $io);

Writes the data as CSV to the IO handle.

  • Callable, Block, Sub, Routine

my @d;
 csv(in => $in, out => { @d.push: $_ });

Passes the data rows to the Callable

  • Channel:U

my $ch = csv(in => $in, out => Channel, :!meta);
 react {
     whenever $ch -> \row {
         @d.push: row;
         LAST { done; }
         }
     }

Writes the data rows into a new Channel, which is returned.

  • Channel:D

my $ch = Channel.new;
 my $pr = start {
     react {
         whenever $ch -> \row {
             @d.push: row;
             LAST { done; }
             }
         }
     }
 csv(in => $in, out => $ch);
 await $pr;

Writes the data rows into the existing Channel.

  • Supplier:D

my $sup = Supplier.new;
 $sup.Supply.tap (-> \row { @d.push: row; });
 csv(in => $in, out => $sup, :!meta);

Writes the data rows into the Supplier.

  • Supply:U

my $sup = csv(in => $in, out => Supply, :!meta);
 $ch.tap (-> \row { @d.push: row; });

Writes the data rows into a new Supply.

=back

encoding encoding

If passed, it should be an encoding accepted by the :encoding() option to open. There is no default value.

If encoding is set to the literal value "auto", the method /header will be invoked on the opened stream to check if there is a BOM and set the encoding accordingly. This is equal to passing True in the option detect-bom.

detect-bom detect-bom detect_bom

NYI for various reasons. Also see /is-binary

If detect-bom is given, the method /header will be invoked on the opened stream to check if there is a BOM and set the encoding accordingly.

detect_bom can be abbreviated to bom.

This is the same as setting encoding to "auto".

Note that as the method /header is invoked, its default is to also set the headers.

headers headers

If this attribute is not given, the default behavior is to produce an array of arrays.

If headers is supplied, it should be an Array of column names, a Bool, a Hash, a Callable, or a literal flag: auto, lc, uc, or skip.

=over 2

  • skip

When skip is used, the header will not be included in the output.

my $aoa = csv(in => $fh, headers => "skip");
  • auto

If auto is used, the first line of the CSV source will be read as the list of field headers and used to produce an array of hashes.

my $aoh = csv(in => $fh, headers => "auto");
  • lc

If lc is used, the first line of the CSV source will be read as the list of field headers mapped to lower case and used to produce an array of hashes. This is a variation of auto.

my $aoh = csv(in => $fh, headers => "lc");
  • uc

If uc is used, the first line of the CSV source will be read as the list of field headers mapped to upper case and used to produce an array of hashes. This is a variation of auto.

my $aoh = csv(in => $fh, headers => "uc");
  • Bool

If True is passed, the method /header will be invoked with the default options on the opened stream to check if there is a BOM and set the encoding accordingly, detect and set sep, eol and column names.

  • Callable

If a Callable is used, the first line of the CSV source will be read as the list of mangled field headers in which each field is passed as the only argument to the coderef. This list is used to produce an array of hashes.

my $i = 0;
 my $aoh = csv(in => $fh, headers => { $^h.lc ~ $i++ });

this example is a variation of using lc where all headers are forced to be unique by adding an index.

  • ARRAY

If headers is an Array, the entries in the list will be used as field names. The first line is considered data instead of headers.

my $aoh = csv(in => $fh, headers => [< Foo Bar >]);
  • HASH

If headers is a Hash, this implies auto, but header fields for that exist as key in the Hash will be replaced by the value for that key. Given a CSV file like

post-kode,city,name,id number,fubble
 1234AA,Duckstad,Donald,13,"X313DF"

using

csv (headers => %{ "post-kode" => "pc", "id number" => "ID" }, ...

will return an entry like

{ pc     => "1234AA",
   city   => "Duckstad",
   name   => "Donald",
   ID     => "13",
   fubble => "X313DF",
   }

=back

See also munge-column-names and set-column-names.

munge-column-names munge-column-names munge_column_names

If munge-column-names is set, the method /header is invoked on the opened stream with all matching arguments to detect and set the headers.

munge-column-names can be abbreviated to munge.

key key

If passed, will default headers to "auto" and return a hashref instead of an array of hashes. Allowed values are simple strings or arrays where the first element is the joiner and the rest are the fields to join to combine the key

my $ref = csv(in => "test.csv", key =>        "code");
 my $ref = csv(in => "test.csv", key => [ ":", "code", "color" ]);

with test.csv like

code,product,price,color
 1,pc,850,gray
 2,keyboard,12,white
 3,mouse,5,black

the first example will return

{ 1   => {
       code    => 1,
       color   => 'gray',
       price   => 850,
       product => 'pc'
       },
   2   => {
       code    => 2,
       color   => 'white',
       price   => 12,
       product => 'keyboard'
       },
   3   => {
       code    => 3,
       color   => 'black',
       price   => 5,
       product => 'mouse'
       }
   }

the second example will return

{ "1:gray"    => {
       code    => 1,
       color   => 'gray',
       price   => 850,
       product => 'pc'
       },
   "2:white"   => {
       code    => 2,
       color   => 'white',
       price   => 12,
       product => 'keyboard'
       },
   "3:black"   => {
       code    => 3,
       color   => 'black',
       price   => 5,
       product => 'mouse'
       }
   }

If out points to an existing Hash, the data will be added to that instead (existing records will be overwritten on reading duplicate keys). It returns the value passed to out:

my $ref = csv(in => "test.csv", out => %hash, key =>        "code");
 my $ref = csv(in => "test.csv", out => %hash, key => [ ":", "code", "color" ]);

kh kh

When using hashes, keep the column names into the arrayref passed, so all headers are available after the call in the original order.

my $aoh = csv (in => "file.csv", kh => \my @hdr);

This attribute should be aliassed keep-header. keep_header, keep-headers, keep_headers keep_column_names, or keep-column-names but I couldn't get that to work.

This attribute implies a default of auto for the headers attribute.

fragment fragment

Only output the fragment as defined in the /fragment method. This option is ignored when generating CSV. See /out.

Combining all of them could give something like

use Text::CSV qw( csv );
 my @aoh = csv(
     in       => "test.txt",
     encoding => "utf-8",
     headers  => "auto",
     sep_char => "|",
     fragment => "row=3;6-9;15-*",
     );
 say @aoh[15]{Foo};

sep-set sep-set sep_set seps

If sep-set is set, the method /header is invoked on the opened stream to detect and set sep with the given set.

sep-set can be abbreviated to seps.

Note that as the method /header is invoked, its default is to also set the headers.

set_column_names set_column_names

If set_column_names is passed, the method /header is invoked on the opened stream with all arguments meant for /header.

Callbacks

Callbacks enable actions triggered from the inside of Text::CSV.

While most of what this enables can easily be done in an unrolled loop as described in the /SYNOPSIS callbacks, can be used to meet special demands or enhance the /csv function.

All callbacks except error are called with just one argument: the current CSV::Row.

=over 2

  • error error

$csv.callbacks(error => { $csv.SetDiag(0) });

the error callback is invoked when an error occurs, but only when /auto_diag is set to a true value. This callback is invoked with the values returned by /error_diag:

my ($c, $s);
sub ignore3006 (Int $err, Str $msg, Int $pos, Int $recno, Int $fldno) {
     if ($err == 3006) {
         # ignore this error
         ($c, $s) = (Str, Str);
         Text::CSV.SetDiag(0);
         }
     # Any other error
     return;
     } # ignore3006
$csv.callbacks(error => \&ignore3006);
 $csv.bind_columns(\$c, \$s);
 while ($csv.getline($fh)) {
     # Error 3006 will not stop the loop
     }
  • after_parse after_parse after-parse

sub add-new (CSV::Row $r) { $r.fields.push: "NEW"; }
 $csv.callbacks(after_parse => &add-new);
 while (my @row = $csv.getline($fh)) {
     @row[-1] eq "NEW";
     }

This callback is invoked after parsing with /getline only if no error occurred.

The return code of the callback is ignored.

sub add_from_db (CSV::Row $r) {
     $sth.execute($r[4]);
     push $r.fields: $sth.fetchrow_array;
     } # add_from_db
my $aoa = csv(in => "file.csv", callbacks => {
     after_parse => &add_from_db });
my $aoa = csv(in => "file.csv", after_parse => {
     $sth.execute($^row[4]); $^row.fields.push: $sth.fetchrow_array; });
  • before_print before_print before-print

my $idx = 1;
 $csv.callbacks(before_print => { $^row[0] = $idx++ });
 $csv.print($*OUT, [ 0, $_ ]) for @members;

This callback is invoked before printing with /print only if no error occurred.

The return code of the callback is ignored.

sub max_4_fields (CSV::Row $r) {
     $r.elems > 4 and $r.splice (4);
     } # max_4_fields
csv(in => csv(in => "file.csv"), out => $*OUT,
     callbacks => { before print => \&max_4_fields });
csv(in => csv(in => "file.csv"), out => $*OUT,
     before print => { $^row.elems > 4 and $^row.splice(4) });

This callback is not active for /combine.

=back

Callbacks for csv

The /csv allows for some callbacks that do not integrate in internals but only feature the /csv function. XXX: Is this still true?

csv(in        => "file.csv",
     callbacks => {
         after_parse  => { say "AFTER PARSE";  }, # first
         after_in     => { say "AFTER IN";     }, # second
         on_in        => { say "ON IN";        }, # third
         },
     );
csv(in        => $aoh,
     out       => "file.csv",
     callbacks => {
         on_in        => { say "ON IN";        }, # first
         before_out   => { say "BEFORE OUT";   }, # second
         before_print => { say "BEFORE PRINT"; }, # third
         },
     );

=over 2

  • filter filter

This callback can be used to filter records. It is called just after a new record has been scanned. The callback will be invoked with the current /CSV::Row and should return True for records to accept and False for records to reject.

csv (in => "file.csv", filter => {
            $^row[2] ~~ /a/ &&  # third field should contain an "a"
            $^row[4].chars > 4  # length of the 5th field minimal 5
            });
csv (in => "file.csv", filter => "not_blank");
 csv (in => "file.csv", filter => "not_empty");
 csv (in => "file.csv", filter => "filled");

If the filter is used to alter the content of a field, make sure that the sub, block or callable returns true in order not to have that record skipped:

filter => { $^row[1].text .= uc }

will upper-case the second field, and then skip it if the resulting content evaluates to false. To always accept, end with truth:

filter => { $^row[1].text .= uc; 1 }}

If the Callable uses a Hash as prototype, the output is set to Hash and the headers to auto if unset

filter => { %^row<foo> ~~ /a/ }

Predefined filters

Given a file like (line numbers prefixed for doc purpose only):

1:1,2,3
 2:
 3:,
 4:""
 5:,,
 6:, ,
 7:"",
 8:" "
 9:4,5,6

=over 2

  • not_blank

  • not-blank

Filter out the blank lines

This filter is a shortcut for

filter => { $^row.elems > 1 or
             $^row[0].defined && $^row[0] ne "" or
             $^row[0].is-quoted }

With the given example, line 2 will be skipped.

  • not_empty

  • not-empty

Filter out lines where all the fields are empty.

This filter is a shortcut for

filter => { $^row.first: { .defined && $_ ne ""   }}

A space is not regarded being empty, so given the example data, lines 2, 3, 4, 5, and 7 are skipped.

  • filled

Filter out lines that have no visible data

This filter is a shortcut for

filter => { $^row.first: { .defined && $_ ~~ /\S/ }}

This filter rejects all lines that not have at least one field that does not evaluate to the empty string.

With the given example data, this filter would skip lines 2 through 8.

=back

  • after_in after_in after-in

This callback is invoked for each record after all records have been parsed but before returning the reference to the caller.

This callback can also be passed as an attribute to /csv without the callbacks wrapper.

  • before_out before_out before-out

This callback is invoked for each record before the record is printed.

This callback can also be passed as an attribute to /csv without the callbacks wrapper.

  • on_in on_in on-in

This callback acts exactly as the /after_in or the /before_out hooks.

This callback can also be passed as an attribute to /csv without the callbacks wrapper.

=back

EXAMPLES

Reading a CSV file line by line:

my $csv = Text::CSV.new(:auto_diag);
 my $fh = open "file.csv", :r, :!chomp;
 while (my @row = $csv.getline($fh)) {
     # do something with @$row
     }
 $fh.close;

Reading only a single column

my $csv = Text::CSV.new(:auto_diag);
 my $fh = open "file.csv", :r, :!chomp;
 # get only the 4th column
 my @column = $csv.getline_all($fh).map(*[3]);
 $fh.close;

with /csv, you could do

my @column = csv(in => "file.csv", fragment => "col=4").map(*[0]);

or

my @column = csv(in => "file.csv", fragment => "col=4").map(*.flat);

Parsing CSV strings:

my $csv = Text::CSV.new(:keep_meta);
my $sample_input_string =
     q{"I said, ""Hi!""",Yes,"",2.34,,"1.09","\x[20ac]",};
 if ($csv.parse($sample_input_string)) {
     my @field = $csv.fields;
     for ^@field.elems -> $col {
         my $quo = $csv.is_quoted($col) ? $csv.{quote_char} : "";
         printf "%2d: %s%s%s\n", $col, $quo, $field[$col], $quo;
         }
     }
 else {
     print STDERR "parse failed on argument: ",
         $csv.error_input, "\n";
     $csv.error_diag;
     }

Printing CSV data

The fast way: using /print

An example for creating CSV files using the /print method:

my $csv = Text::CSV.new(eol => $*OUT.nl);
 open my $fh, ">", "foo.csv" or die "foo.csv: $!";
 for 1..10 -> $x {
     $csv.print($fh, [ $x, ~$x ]) or $csv.error_diag;
     }
 close $fh or die "$tbl.csv: $!";

The slow way: using /combine and /string

or using the slower /combine and /string methods:

my $csv = Text::CSV.new;
open my $csv_fh, ">", "hello.csv" or die "hello.csv: $!";
my @sample_input_fields = (
     'You said, "Hello!"',   5.67,
     '"Surely"',   '',   '3.14159');
 if ($csv.combine(@sample_input_fields)) {
     print $csv_fh $csv.string, "\n";
     }
 else {
     print "combine failed on argument: ",
         $csv.error_input, "\n";
     }
 close $csv_fh or die "hello.csv: $!";

Rewriting CSV

Rewrite CSV files with ; as separator character to well-formed CSV:

use Text::CSV qw( csv );
 csv(in => csv(in => "bad.csv", sep => ";"), out => $*OUT);

Dumping database tables to CSV

Dumping a database table can be simple as this (TIMTOWTDI):

my $dbh = DBI.connect(...);
 my $sql = "select * from foo";
# using your own loop
 open my $fh, ">", "foo.csv" or die "foo.csv: $!\n";
 my $csv = Text::CSV.new(eol => "\r\n");
 my $sth = $dbh.prepare($sql); $sth.execute;
 $csv.print($fh, $sth.{NAME_lc});
 while (my $row = $sth.fetch) {
     $csv.print($fh, $row);
     }
# using the csv function, all in memory
 csv(out => "foo.csv", in => $dbh.selectall_arrayref($sql));
# using the csv function, streaming with callbacks
 my $sth = $dbh.prepare($sql); $sth.execute;
 csv(out => "foo.csv", in => { $sth.fetch            });
 csv(out => "foo.csv", in => { $sth.fetchrow_hashref });

Note that this does not discriminate between "empty" values and NULL-values from the database, as both will be the same empty field in CSV. To enable distinction between the two, use quote_empty.

csv(out => "foo.csv", in => { $sth.fetch }, :quote_empty);

If the database import utility supports special sequences to insert NULL values into the database, like MySQL/MariaDB supports \N, use a filter or a map

csv(out => "foo.csv", in => { $sth.fetch },
                    on_in => { $_ //= "\\N" for @$_[1] }); # WIP
while (my @row = $sth.fetch) {
     $csv.print($fh, @row.map({ * // "\\N" }));
     }

these special sequences are not recognized by Text::CSV_XS on parsing the CSV generated like this, but map and filter are your friends again

while (my @row = $csv.getline($io)) {
     $sth.execute(@row.map({ $_ eq "\\N" ?? Nil !! $_ }));
     }
csv(in => "foo.csv", filter => { 1 => {
     $sth.execute(@{$_[1]}.map({ $_ eq "\\N" ?? Nil !! $_ }); False; }});

The examples folder

For more extended examples, see the 1) sub-directory in the original distribution or the git repository 2).

1. https://github.com/Tux/Text-CSV_XS/tree/master/examples/
 2. https://github.com/Tux/Text-CSV_XS/

The following files can be found there:

=over 2

  • csv-check csv-check

This is a command-line tool to check the CSV file and report on its content.

TODO

  • csv2xls csv2xls

A script to convert CSV to Microsoft Excel.

TODO

  • csvdiff csvdiff

A script that provides colorized diff on sorted CSV files, assuming first line is header and first field is the key. Output options include colorized ANSI escape codes or HTML.

TODO

=back

################################################################################

CAVEATS

Microsoft Excel

The import/export from Microsoft Excel is a risky task, according to the documentation in Text::CSV::Separator. Microsoft uses the system's list separator defined in the regional settings, which happens to be a semicolon for Dutch, German and Spanish (and probably some others as well). For the English locale, the default is a comma. In Windows however, the user is free to choose a predefined locale, and then change every individual setting in it, so checking the locale is no solution.

A lone first line with just

sep=;

will be recognized and honored: it will set sep to ; and skip that line.

TODO / WIP / NYI

=over 2

  • Real binary data

The solution would be a working utf8-c8 encoding.

  • BOM detection

There is no working solution yet for detection of BOM on the /header method. Besides that, not all encodings are supported in raku.

  • on-in and before-print callbacks

The /on-in callback currently is an alias for /after-parse if the latter is not specified.

  • Examples

Convert the perl5 example/tool files to raku versions

  • Metadata and CSV for the web

Metadata Vocabulary for Tabular Data (a W3C editor's draft) could be an example for supporting more metadata.

W3C's work CSV on the Web: Use Cases and Requirements is almost finished and worth looking at.

  • Cookbook

Write a document that has recipes for most known non-standard (and maybe some standard) CSV formats, including formats that use TAB, ;, |, or other non-comma separators.

Examples could be taken from W3C's CSV on the Web: Use Cases and Requirements

=back

DIAGNOSTICS

Still under construction ...

This section describes the error codes that are used in perl5's module Text::CSV_XS, and several of these errors are either not applicable in raku or changed slightly. Once all of the API is finished, this section will be cleaned up. The intention of the error coded however remains.

If an error occurs, < $csv.error_diag > can be used to get information on the cause of the failure. Note that for speed reasons the internal value is never cleared on success, so using the value returned by /error_diag in normal cases - when no error occurred - may cause unexpected results.

If the constructor failed, the cause will be thrown as an Exception that represents /error_diag.

The < $csv.error_diag > method is automatically invoked upon error when the contractor was called with auto_diag set to True.

Errors can be (individually) caught using the /error callback.

The errors as described below are available. I have tried to make the error itself explanatory enough, but more descriptions will be added. For most of these errors, the first three capitals describe the error category:

=over 2

  • * INI

Initialization error or option conflict.

  • * ECR

Carriage-Return related parse error.

  • * EOF

End-Of-File related parse error.

  • * EIQ

Parse error inside quotation.

  • * EIF

Parse error inside field.

  • * ECB

Combine error.

  • * EHR

Hash parse related error.

  • * EHK

Errors related to hooks/callbacks.

  • * CSV

Errors related to the csv function.

=back

And below should be the complete list of error codes that can be returned:

=over 2

  • * 1001 "INI - separator is equal to quote- or escape sequence" 1001

The separation sequence cannot be equal to the quotation sequence or to the escape sequence, as this would invalidate all parsing rules.

  • * 1002 "INI - allow_whitespace with escape_char or quote_char SP or TAB" 1002

Using the allow_whitespace attribute when either quote_char or escape_char is equal to SPACE or TAB is too ambiguous to allow.

  • * 1003 "INI - \r or \n in main attr not allowed" 1003

Using default eol sequences in either separation sequence, quotation sequence, or escape sequence is not allowed.

  • * 1004 "INI - callbacks should be undefined or a hashref" 1004

The callbacks attribute only allows one to be undefined or a hash reference.

  • * 1010 "INI - the header is empty" 1010

The header line parsed in the /header is empty.

  • * 1011 "INI - the header contains more than one valid separator" 1011

The header line parsed in the /header contains more than one (unique) separator character out of the allowed set of separators.

  • * 1012 "INI - the header contains an empty field" 1012

The header line parsed in the /header is contains an empty field.

  • * 1013 "INI - the header contains nun-unique fields" 1013

The header line parsed in the /header contains at least two identical fields.

  • * 2010 "ECR - QUO char inside quotes followed by CR not part of EOL" 2010

When eol has been set to anything but the default, like "\r\t\n", and the "\r" is following the second (closing) quote_char, where the characters following the "\r" do not make up the eol sequence, this is an error.

  • * 2011 "ECR - Characters after end of quoted field" 2011

Sequences like 1,foo,"bar"baz,22,1 are not allowed. "bar" is a quoted field and after the closing double-quote, there should be either a new-line sequence or a separation sequence.

  • * 2012 "EOF - End of data in parsing input stream" 2012

Self-explaining. End-of-file while inside parsing a stream. Can happen only when reading from streams with /getline, as using /parse is done on strings that are not required to have a trailing eol.

  • * 2013 "INI - Specification error for fragments RFC7111" 2013

Invalid specification for URI /fragment specification.

  • * 2021 "EIQ - NL char inside quotes, binary off" 2021

Sequences like 1,"foo\nbar",22,1 are allowed only when the binary option has been selected with the constructor.

  • * 2022 "EIQ - CR char inside quotes, binary off" 2022

Sequences like 1,"foo\rbar",22,1 are allowed only when the binary option has been selected with the constructor.

  • * 2023 "EIQ - QUO sequence not allowed" 2023

Sequences like "foo "bar" baz",qu and 2023,",2008-04-05,"Foo, Bar",\n will cause this error.

  • * 2024 "EIQ - EOF cannot be escaped, not even inside quotes" 2024

The escape sequence is not allowed as last item in an input stream.

  • * 2025 "EIQ - Loose unescaped escape" 2025

An escape sequence should escape only characters that need escaping.

Allowing the escape for other characters is possible with the attribute /allow_loose_escapes.

  • * 2026 "EIQ - Binary character inside quoted field, binary off" 2026

Binary characters are not allowed by default. Exceptions are fields that contain valid UTF-8, that will automatically be upgraded if the content is valid UTF-8. Set binary to 1 to accept binary data.

  • * 2027 "EIQ - Quoted field not terminated" 2027

When parsing a field that started with a quotation sequence, the field is expected to be closed with a quotation sequence. When the parsed line is exhausted before the quote is found, that field is not terminated.

  • * 2030 "EIF - NL char inside unquoted verbatim, binary off" 2030

  • * 2031 "EIF - CR char is first char of field, not part of EOL" 2031

  • * 2032 "EIF - CR char inside unquoted, not part of EOL" 2032

  • * 2034 "EIF - Loose unescaped quote" 2034

  • * 2035 "EIF - Escaped EOF in unquoted field" 2035

  • * 2036 "EIF - ESC error" 2036

  • * 2037 "EIF - Binary character in unquoted field, binary off" 2037

  • * 2110 "ECB - Binary character in Combine, binary off" 2110

  • * 2200 "EIO - print to IO failed. See errno" 2200

  • * 3001 "EHR - Unsupported syntax for column_names" 3001

  • * 3002 "EHR - getline_hr called before column_names" 3002

  • * 3003 "EHR - bind_columns and column_names fields count mismatch" 3003

  • * 3004 "EHR - bind_columns only accepts refs to scalars" 3004

  • * 3006 "EHR - bind_columns did not pass enough refs for parsed fields" 3006

  • * 3007 "EHR - bind_columns needs refs to writable scalars" 3007

  • * 3008 "EHR - unexpected error in bound fields" 3008

  • * 3009 "EHR - print_hr called before column_names" 3009

  • * 3010 "EHR - print_hr called with invalid arguments" 3010

  • * 3100 "EHK - Unsupported callback" 3100

  • * 4001 "PRM - The key does not exist as field in the data", 4001

You cannot set the key from a non-existing column.

If you were using a key list, all keys should exist.

  • * 5000 "CSV - Unsupported type for in" 5000

  • * 5001 "CSV - Unsupported type for out" 5001

=back

SEE ALSO

Modules in perl5:

IO::File, IO::Handle, IO::Wrap, Text::CSV_XS, Text::CSV, Text::CSV_PP, Text::CSV::Encoded, Text::CSV::Separator, Text::CSV::Slurp, Spreadsheet::CSV and Spreadsheet::Read;

AUTHOR

H.Merijn Brand wrote this based on the features provided by perl5's Text::CSV_XS.

Liz Mattijsen helped in getting the best out of raku.

COPYRIGHT AND LICENSE

Copyright (C) 2014-2018 H.Merijn Brand. All rights reserved.

This library is free software; you can redistribute and/or modify it under the same terms as Perl itself.

=cut

=for elvis :ex:se gw=75|color guide #ff0000:

Text::CSV v0.015

Handle CSV data. API based on Text::CSV_XS

Authors

  • H.Merijn Brand (Tux)

License

Artistic-2.0

Dependencies

Slang::TuxicFile::Temp

Test Dependencies

Provides

  • IO::String
  • Text::CSV

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.