Wolfgang Thallergcc 15.1.0ada185d1 year agoHistory
1890 lines · 51.8 KB · D
RawDownload
//Written in the D programming language /** * Implements functionality to read Comma Separated Values and its variants * from an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of `dchar`. * * Comma Separated Values provide a simple means to transfer and store * tabular data. It has been common for programs to use their own * variant of the CSV format. This parser will loosely follow the * $(HTTP tools.ietf.org/html/rfc4180, RFC-4180). CSV input should adhere * to the following criteria (differences from RFC-4180 in parentheses): * * $(UL *     $(LI A record is separated by a new line (CRLF,LF,CR)) *     $(LI A final record may end with a new line) *     $(LI A header may be provided as the first record in input) *     $(LI A record has fields separated by a comma (customizable)) *     $(LI A field containing new lines, commas, or double quotes *          should be enclosed in double quotes (customizable)) *     $(LI Double quotes in a field are escaped with a double quote) *     $(LI Each record should contain the same number of fields) *   ) * * Example: * * ------- * import std.algorithm; * import std.array; * import std.csv; * import std.stdio; * import std.typecons; * * void main() * { *     auto text = "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"; * *     foreach (record; csvReader!(Tuple!(string, string, int))(text)) *     { *         writefln("%s works as a %s and earns $%d per year", *                  record[0], record[1], record[2]); *     } * *     // To read the same string from the file "filename.csv": * *     auto file = File("filename.csv", "r"); *     foreach (record; *         file.byLine.joiner("\n").csvReader!(Tuple!(string, string, int))) *     { *         writefln("%s works as a %s and earns $%d per year", *                  record[0], record[1], record[2]); *     } } * } * ------- * * When an input contains a header the `Contents` can be specified as an * associative array. Passing null to signify that a header is present. * * ------- * auto text = "Name,Occupation,Salary\r" ~ *     "Joe,Carpenter,300000\nFred,Blacksmith,400000\r\n"; * * foreach (record; csvReader!(string[string]) *         (text, null)) * { *     writefln("%s works as a %s and earns $%s per year.", *              record["Name"], record["Occupation"], *              record["Salary"]); * } * * // To read the same string from the file "filename.csv": * * auto file = File("filename.csv", "r"); * * foreach (record; csvReader!(string[string]) *         (file.byLine.joiner("\n"), null)) * { *     writefln("%s works as a %s and earns $%s per year.", *              record["Name"], record["Occupation"], *              record["Salary"]); * } * ------- * * This module allows content to be iterated by record stored in a struct, * class, associative array, or as a range of fields. Upon detection of an * error an CSVException is thrown (can be disabled). csvNextToken has been * made public to allow for attempted recovery. * * Disabling exceptions will lift many restrictions specified above. A quote * can appear in a field if the field was not quoted. If in a quoted field any * quote by itself, not at the end of a field, will end processing for that * field. The field is ended when there is no input, even if the quote was not * closed. * *   See_Also: *      $(HTTP en.wikipedia.org/wiki/Comma-separated_values, Wikipedia *      Comma-separated values) * *   Copyright: Copyright 2011 *   License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). *   Authors:   Jesse Phillips *   Source:    $(PHOBOSSRC std/csv.d) */module std.csv; import std.conv;import std.exception : basicExceptionCtors;import std.range.primitives;import std.traits; /** * Exception containing the row and column for when an exception was thrown. * * Numbering of both row and col start at one and corresponds to the location * in the file rather than any specified header. Special consideration should * be made when there is failure to match the header see $(LREF * HeaderMismatchException) for details. * * When performing type conversions, $(REF ConvException, std,conv) is stored in * the `next` field. */class CSVException : Exception{    ///    size_t row, col;     // FIXME: Use std.exception.basicExceptionCtors here once    // https://issues.dlang.org/show_bug.cgi?id=11500 is fixed     this(string msg, string file = __FILE__, size_t line = __LINE__,         Throwable next = null) @nogc @safe pure nothrow    {        super(msg, file, line, next);    }     this(string msg, Throwable next, string file = __FILE__,         size_t line = __LINE__) @nogc @safe pure nothrow    {        super(msg, file, line, next);    }     this(string msg, size_t row, size_t col, Throwable next = null,         string file = __FILE__, size_t line = __LINE__) @nogc @safe pure nothrow    {        super(msg, next, file, line);        this.row = row;        this.col = col;    }     override string toString() @safe pure const    {        return "(Row: " ~ to!string(row) ~              ", Col: " ~ to!string(col) ~ ") " ~ msg;    }} ///@safe unittest{    import std.exception : collectException;    import std.algorithm.searching : count;    string text = "a,b,c\nHello,65";    auto ex = collectException!CSVException(csvReader(text).count);    assert(ex.toString == "(Row: 0, Col: 0) Row 2's length 2 does not match previous length of 3.");} ///@safe unittest{    import std.exception : collectException;    import std.algorithm.searching : count;    import std.typecons : Tuple;    string text = "a,b\nHello,65";    auto ex = collectException!CSVException(csvReader!(Tuple!(string,int))(text).count);    assert(ex.toString == "(Row: 1, Col: 2) Unexpected 'b' when converting from type string to type int");} // https://issues.dlang.org/show_bug.cgi?id=24478@safe unittest{    import std.exception : collectException;    import std.algorithm.searching : count;    string text = "A, B\n1, 2, 3";    auto ex = collectException!CSVException(csvReader!(string[string])(text, null).count);    assert(ex.toString == "(Row: 1, Col: 3) row contains more values than header");} @safe pure unittest{    import std.string;    auto e1 = new Exception("Foobar");    auto e2 = new CSVException("args", e1);    assert(e2.next is e1);     size_t r = 13;    size_t c = 37;     auto e3 = new CSVException("argv", r, c);    assert(e3.row == r);    assert(e3.col == c);     auto em = e3.toString();    assert(em.indexOf("13") != -1);    assert(em.indexOf("37") != -1);} /** * Exception thrown when a Token is identified to not be completed: a quote is * found in an unquoted field, data continues after a closing quote, or the * quoted field was not closed before data was empty. */class IncompleteCellException : CSVException{    /**     * Data pulled from input before finding a problem     *     * This field is populated when using $(LREF csvReader)     * but not by $(LREF csvNextToken) as this data will have     * already been fed to the output range.     */    dstring partialData;     mixin basicExceptionCtors;} ///@safe unittest{    import std.exception : assertThrown;    string text = "a,\"b,c\nHello,65,2.5";    assertThrown!IncompleteCellException(text.csvReader(["a","b","c"]));} @safe pure unittest{    auto e1 = new Exception("Foobar");    auto e2 = new IncompleteCellException("args", e1);    assert(e2.next is e1);} /** * Exception thrown under different conditions based on the type of $(D * Contents). * * Structure, Class, and Associative Array * $(UL *     $(LI When a header is provided but a matching column is not found) *  ) * * Other * $(UL *     $(LI When a header is provided but a matching column is not found) *     $(LI Order did not match that found in the input) *  ) * * Since a row and column is not meaningful when a column specified by the * header is not found in the data, both row and col will be zero. Otherwise * row is always one and col is the first instance found in header that * occurred before the previous starting at one. */class HeaderMismatchException : CSVException{    mixin basicExceptionCtors;} ///@safe unittest{    import std.exception : assertThrown;    string text = "a,b,c\nHello,65,2.5";    assertThrown!HeaderMismatchException(text.csvReader(["b","c","invalid"]));} @safe pure unittest{    auto e1 = new Exception("Foobar");    auto e2 = new HeaderMismatchException("args", e1);    assert(e2.next is e1);} /** * Determines the behavior for when an error is detected. * * Disabling exception will follow these rules: * $(UL *     $(LI A quote can appear in a field if the field was not quoted.) *     $(LI If in a quoted field any quote by itself, not at the end of a *     field, will end processing for that field.) *     $(LI The field is ended when there is no input, even if the quote was *     not closed.) *     $(LI If the given header does not match the order in the input, the *     content will return as it is found in the input.) *     $(LI If the given header contains columns not found in the input they *     will be ignored.) *  )*/enum Malformed{    ignore,           /// No exceptions are thrown due to incorrect CSV.    throwException    /// Use exceptions when input has incorrect CSV.} ///@safe unittest{    import std.algorithm.comparison : equal;    import std.algorithm.searching : count;    import std.exception : assertThrown;     string text = "a,b,c\nHello,65,\"2.5";    assertThrown!IncompleteCellException(text.csvReader.count);     // ignore the exceptions and try to handle invalid CSV    auto firstLine = text.csvReader!(string, Malformed.ignore)(null).front;    assert(firstLine.equal(["Hello", "65", "2.5"]));} /**Returns an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)for iterating over records found in `input`. An optional `header` can be provided. The first record will be read inas the header. If `Contents` is a struct then the header provided isexpected to correspond to the fields in the struct. When `Contents` isnot a type which can contain the entire record, the `header` must beprovided in the same order as the input or an exception is thrown. Returns:       An input range R as defined by       $(REF isInputRange, std,range,primitives). When `Contents` is a       struct, class, or an associative array, the element type of R is       `Contents`, otherwise the element type of R is itself a range with       element type `Contents`.        If a `header` argument is provided,       the returned range provides a `header` field for accessing the header       from the input in array form. Throws:      $(LREF CSVException) When a quote is found in an unquoted field,      data continues after a closing quote, the quoted field was not      closed before data was empty, a conversion failed, or when the row's      length does not match the previous length.       $(LREF HeaderMismatchException)  when a header is provided but a      matching column is not found or the order did not match that found in      the input. Read the exception documentation for specific details of      when the exception is thrown for different types of `Contents`.*/auto csvReader(Contents = string,Malformed ErrorLevel = Malformed.throwException, Range, Separator = char)(Range input,                 Separator delimiter = ',', Separator quote = '"',                 bool allowInconsistentDelimiterCount = false)if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar)    && isSomeChar!(Separator)    && !is(Contents T : T[U], U : string)){    return CsvReader!(Contents,ErrorLevel,Range,                    Unqual!(ElementType!Range),string[])        (input, delimiter, quote, allowInconsistentDelimiterCount);} /// dittoauto csvReader(Contents = string,               Malformed ErrorLevel = Malformed.throwException,               Range, Header, Separator = char)                (Range input, Header header,                 Separator delimiter = ',', Separator quote = '"',                 bool allowInconsistentDelimiterCount = false)if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar)    && isSomeChar!(Separator)    && isForwardRange!Header    && isSomeString!(ElementType!Header)){    return CsvReader!(Contents,ErrorLevel,Range,                    Unqual!(ElementType!Range),Header)        (input, header, delimiter, quote, allowInconsistentDelimiterCount);} /// dittoauto csvReader(Contents = string,               Malformed ErrorLevel = Malformed.throwException,               Range, Header, Separator = char)                (Range input, Header header,                 Separator delimiter = ',', Separator quote = '"',                 bool allowInconsistentDelimiterCount = false)if (isInputRange!Range && is(immutable ElementType!Range == immutable dchar)    && isSomeChar!(Separator)    && is(Header : typeof(null))){    return CsvReader!(Contents,ErrorLevel,Range,                    Unqual!(ElementType!Range),string[])        (input, cast(string[]) null, delimiter, quote,         allowInconsistentDelimiterCount);}  /**The `Contents` of the input can be provided if all the records are thesame type such as all integer data:*/@safe unittest{    import std.algorithm.comparison : equal;    string text = "76,26,22";    auto records = text.csvReader!int;    assert(records.equal!equal([        [76, 26, 22],    ]));} /**Using a struct with modified delimiter:*/@safe unittest{    import std.algorithm.comparison : equal;    string text = "Hello;65;2.5\nWorld;123;7.5";    struct Layout    {        string name;        int value;        double other;    }     auto records = text.csvReader!Layout(';');    assert(records.equal([        Layout("Hello", 65, 2.5),        Layout("World", 123, 7.5),    ]));} /**Specifying `ErrorLevel` as $(LREF Malformed.ignore) will lift restrictionson the format. This example shows that an exception is not thrown whenfinding a quote in a field not quoted.*/@safe unittest{    string text = "A \" is now part of the data";    auto records = text.csvReader!(string, Malformed.ignore);    auto record = records.front;     assert(record.front == text);} /// Read only column "b"@safe unittest{    import std.algorithm.comparison : equal;    string text = "a,b,c\nHello,65,63.63\nWorld,123,3673.562";    auto records = text.csvReader!int(["b"]);     assert(records.equal!equal([        [65],        [123],    ]));} /// Read while rearranging the columns by specifying a header with a different order"@safe unittest{    import std.algorithm.comparison : equal;    string text = "a,b,c\nHello,65,2.5\nWorld,123,7.5";    struct Layout    {        int value;        double other;        string name;    }     auto records = text.csvReader!Layout(["b","c","a"]);    assert(records.equal([        Layout(65, 2.5, "Hello"),        Layout(123, 7.5, "World")    ]));} /**The header can also be left empty if the input contains a header rowand all columns should be iterated.The header from the input can always be accessed from the `header` field.*/@safe unittest{    string text = "a,b,c\nHello,65,63.63";    auto records = text.csvReader(null);     assert(records.header == ["a","b","c"]);} /**Handcrafted csv files tend to have an variable amount of columns. By default `std.csv` will throw if the number of columns on a lineis unequal to the number of columns of the first line.To allow, or disallow, a variable amount of columns a `bool` can be passed toall overloads of the `csvReader` function as shown below.*/@safe unittest{

Lines 1–500 of 1890.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t