Sign in

    csv_parser

    A robust RFC 4180 compliant CSV parser with support for custom delimiters, quoted fields, and serialization

    csv
    parser
    tsv
    data
    serialization
    rfc4180
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    11 months ago
    Downloads
    1K

    No dependencies

    #CSV Parser

    A robust and feature-rich CSV (Comma-Separated Values) parser written in MoonBit. This library supports RFC 4180 standard CSV format with configurable options for different delimiters, quote characters, and parsing behaviors.

    #Features

    • ✅ RFC 4180 Compliant: Follows the standard CSV specification
    • ✅ Quoted Fields: Handles fields with commas, newlines, and quotes inside
    • ✅ Escape Sequences: Supports escaped quotes (double quotes)
    • ✅ Multiple Delimiters: Configurable delimiter (comma, tab, semicolon, etc.)
    • ✅ CRLF and LF: Handles both Windows and Unix line endings
    • ✅ Error Handling: Comprehensive error reporting with position information
    • ✅ Serialization: Convert data back to CSV format
    • ✅ Unicode Support: Full UTF-16 string support
    • ✅ Zero-Copy Parsing: Efficient parsing without unnecessary allocations

    #Quick Start

    ///|
    test "basic csv parsing" {
    let csv = "name,age,city\nAlice,30,NYC\nBob,25,LA"
    let doc = @csv_parser.parse(csv).unwrap()
    @json.inspect(doc[0], content=["name", "age", "city"])
    @json.inspect(doc[1], content=["Alice", "30", "NYC"])
    @json.inspect(doc[2], content=["Bob", "25", "LA"])
    }

    #Installation

    Add the dependency to your moon.mod.json:

    { "deps": { "maria/csv_parser": "*" } }

    Then import in your moon.pkg.json:

    { "import": [ "maria/csv_parser" ] }

    #API Overview

    #Parsing Functions

    ///|
    test "parsing functions" {
    // Parse with default config (comma delimiter)
    let csv1 = "a,b,c\n1,2,3"
    let doc1 = @csv_parser.parse(csv1).unwrap()
    @json.inspect(doc1, content=[["a", "b", "c"], ["1", "2", "3"]])

    // Parse TSV (tab-separated values)
    let tsv = "a\tb\tc\n1\t2\t3"
    let doc2 = @csv_parser.parse_tsv(tsv).unwrap()
    @json.inspect(doc2, content=[["a", "b", "c"], ["1", "2", "3"]])

    // Parse with custom delimiter
    let csv3 = "a;b;c\n1;2;3"
    let config = @csv_parser.CsvConfig::with_delimiter(';')
    let doc3 = @csv_parser.parse_with_config(csv3, config).unwrap()
    @json.inspect(doc3, content=[["a", "b", "c"], ["1", "2", "3"]])
    }

    #Serialization

    ///|
    test "serialization" {
    let doc : @csv_parser.Document = [
    ["name", "email"],
    ["Alice", "alice@example.com"],
    ["Bob", "bob@example.com"],
    ]
    let csv = @csv_parser.document_to_string(doc)
    inspect(
    csv,
    content="name,email\nAlice,alice@example.com\nBob,bob@example.com",
    )
    }

    #Advanced Features

    #Quoted Fields

    The parser correctly handles fields with special characters:

    ///|
    test "quoted fields with commas" {
    let csv =
    #|"product","description","price"
    #|"Widget A","A useful widget, very popular","19.99"
    #|
    let doc = @csv_parser.parse(csv).unwrap()
    @json.inspect(doc[1], content=[
    "Widget A", "A useful widget, very popular", "19.99",
    ])
    }

    #Escaped Quotes

    Quotes within fields are escaped by doubling them:

    ///|
    test "escaped quotes" {
    let csv = "\"She said \"\"hello\"\"\",\"End\""
    let doc = @csv_parser.parse(csv).unwrap()
    @json.inspect(doc[0], content=["She said \"hello\"", "End"])
    }

    #Multiline Fields

    Fields can contain newlines when quoted:

    ///|
    test "multiline fields" {
    let csv = "\"field1\",\"line1\nline2\nline3\",\"field3\""
    let doc = @csv_parser.parse(csv).unwrap()
    inspect(doc[0][1], content="line1\nline2\nline3")
    }

    #Error Handling

    The parser provides detailed error information:

    ///|
    test "error handling" {
    let invalid_csv = "\"unterminated quote"
    let result = @csv_parser.parse(invalid_csv)
    match result {
    Err(@csv_parser.CsvError::UnterminatedQuote(row, col)) => {
    inspect(row, content="1")
    inspect(col, content="20")
    }
    _ => fail("Expected UnterminatedQuote error")
    }
    }

    #Custom Configuration

    Create custom parsers with different settings:

    ///|
    test "custom configuration" {
    let config : @csv_parser.CsvConfig = {
    delimiter: '|',
    quote: '\'',
    skip_empty_lines: false,
    }
    let csv = "name|age\n'Alice'|30\n\n'Bob'|25"
    let doc = @csv_parser.parse_with_config(csv, config).unwrap()
    inspect(doc.length(), content="4")
    @json.inspect(doc[0], content=["name", "age"])
    @json.inspect(doc[1], content=["Alice", "30"])
    @json.inspect(doc[2], content=[""])
    @json.inspect(doc[3], content=["Bob", "25"])
    }

    #Roundtrip Conversion

    Parse CSV and convert it back:

    ///|
    test "roundtrip conversion" {
    let original = "a,b,c\n1,2,3\n\"hello, world\",test,end"
    let doc = @csv_parser.parse(original).unwrap()
    let serialized = @csv_parser.document_to_string(doc)
    let reparsed = @csv_parser.parse(serialized).unwrap()
    @json.inspect(reparsed, content=[
    ["a", "b", "c"],
    ["1", "2", "3"],
    ["hello, world", "test", "end"],
    ])
    }

    #Types

    #CsvError

    Error types that can occur during parsing:

    • UnexpectedEof(Int, Int) - Unexpected end of file at (row, col)
    • UnterminatedQuote(Int, Int) - Unterminated quote at (row, col)
    • InvalidQuoteEscape(Int, Int) - Invalid quote escape at (row, col)

    #CsvConfig

    Configuration for the CSV parser:

    • delimiter : Char - Field delimiter (default: ',')
    • quote : Char - Quote character (default: '"')
    • skip_empty_lines : Bool - Skip empty lines (default: true)

    #Type Aliases

    • Row = Array[String] - A single CSV row
    • Document = Array[Row] - A complete CSV document

    #Performance Tips

    1. Reuse buffers: When building CSV programmatically, reuse the same buffer
    2. Use appropriate config: Set skip_empty_lines: false if you need to preserve empty lines
    3. Avoid unnecessary conversions: Work with the parsed Document directly when possible

    #License

    Apache-2.0

    Document

    type Document = Array[Array[String]]

    A parsed CSV document (array of rows)

    Row

    type Row = Array[String]

    A parsed CSV row

    CsvConfig

    pub(all) struct CsvConfig {
    delimiter : Char
    quote : Char
    skip_empty_lines : Bool
    }

    CSV parser configuration
    impl Eq for CsvConfig
    impl Show for CsvConfig

    CsvConfig::default

    fn CsvConfig::default() -> CsvConfig

    Default CSV configuration (comma-delimited, double-quote, skip empty lines)

    CsvConfig::with_delimiter

    fn CsvConfig::with_delimiter(delimiter : Char) -> CsvConfig

    Create a CSV configuration with custom delimiter

    CsvError

    pub(all) enum CsvError {
    UnexpectedEof(Int, Int)
    UnterminatedQuote(Int, Int)
    InvalidQuoteEscape(Int, Int)
    }

    CSV parsing error type
    impl Eq for CsvError
    impl Show for CsvError
    impl ToJson for CsvError

    document_to_string

    fn document_to_string(doc : Array[Array[String]], config? : CsvConfig) -> String

    Convert a document to a CSV string

    parse

    fn parse(input : String) -> Result[Array[Array[String]], CsvError]

    Parse CSV string with default configuration

    parse_csv

    fn parse_csv(input : String) -> Result[Array[Array[String]], CsvError]

    Parse CSV from comma-separated values

    parse_tsv

    fn parse_tsv(input : String) -> Result[Array[Array[String]], CsvError]

    Parse TSV (tab-separated values)

    parse_with_config

    fn parse_with_config(input : String, config : CsvConfig) -> Result[Array[Array[String]], CsvError]

    Parse CSV string with custom configuration

    row_to_string

    fn row_to_string(row : Array[String], config? : CsvConfig) -> String

    Convert a row to a CSV string

    Source Files