csvReader & specifying separator problems...
Jon Degenhardt
jond at noreply.com
Fri Nov 15 03:25:19 UTC 2019
On Thursday, 14 November 2019 at 12:25:30 UTC, Robert M. Münch
wrote:
> Just trying a very simple thing and it's pretty hard: "Read a
> CSV file (raw_data) that has a ; separator so that I can
> iterate over the lines and access the fields."
>
> csv_data = raw_data.byLine.joiner("\n")
>
> From the docs, which I find extremly hard to understand:
>
> auto csvReader(Contents = string, Malformed ErrorLevel =
> Malformed.throwException, Range, Separator = char)(Range input,
> Separator delimiter = ',', Separator quote = '"')
>
> So, let's see if I can decyphre this, step-by-step by trying
> out:
>
> csv_records = csv_data.csvReader();
>
> Would split the CSV data into iterable CSV records using ','
> char as separator using UFCS syntax. When running this I get:
>
> [...]
Side comment - This code looks like it was taken from the first
example in the std.csv documentation. To me, the code in the
std.csv example is doing something that might not be obvious at
first glance and is potentially confusing.
In particular, 'byLine' is not reading individual CSV records.
CSV can have embedded newlines, these are identified by CSV
escape syntax. 'byLine' doesn't know the escape syntax. If there
are embedded newlines, 'byLine' will read partial records, which
may not be obvious at first glance. The .joiner("\n") step puts
the newline back, stitching fields and records back together
again in the process.
The effect is to create an input range of characters representing
the entire file, using 'byLine' to do buffered reads. This input
range is passed to CSVReader.
This could also be done using 'byChunk' and 'joiner' (with no
separator). This would use a fixed size buffer, no searching for
newlines while reading, so it should be faster.
An example:
==== csv_by_chunk.d ====
import std.algorithm;
import std.csv;
import std.conv;
import std.stdio;
import std.typecons;
import std.utf;
void main()
{
// Small buffer used to show it works. Normally would use a
larger buffer.
ubyte[16] buffer;
auto stdinBytes = stdin.byChunk(buffer).joiner;
auto stdinDChars = stdinBytes.map!((ubyte b) => cast(char)
b).byDchar;
writefln("--------------");
foreach (record; stdinDChars.csvReader!(Tuple!(string,
string, string)))
{
writefln("Field 0: |%s|", record[0]);
writefln("Field 1: |%s|", record[1]);
writefln("Field 2: |%s|", record[2]);
writefln("--------------");
}
}
Pass it csv data without embedded newlines:
$ echo $'abc,def,ghi\njkl,mno,pqr' | ./csv_by_chunk
--------------
Field 0: |abc|
Field 1: |def|
Field 2: |ghi|
--------------
Field 0: |jkl|
Field 1: |mno|
Field 2: |pqr|
--------------
Pass it csv data with embedded newlines:
$ echo $'abc,"LINE 1\nLINE 2",ghi\njkl,mno,pqr' | ./csv_by_chunk
--------------
Field 0: |abc|
Field 1: |LINE 1
LINE 2|
Field 2: |ghi|
--------------
Field 0: |jkl|
Field 1: |mno|
Field 2: |pqr|
--------------
An example like this may avoid the confusion about newlines.
Unfortunately, the need to do the odd looking conversion from
ubyte to char/dchar is undesirable in a code example. I haven't
found a cleaner way to write that. If there's a nicer way I'd
appreciate hearing about it.
--Jon
More information about the Digitalmars-d-learn
mailing list