module csv // Copyright (c) 2019 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. // Once interfaces are further along the idea would be to have something similar to // go's io.reader & bufio.reader rather than reading the whole file into string, this // would then satisfy that interface. I designed it this way to be easily adapted. const ( err_comment_is_delim = error('encoding.csv: comment cannot be the same as delimiter') err_invalid_delim = error('encoding.csv: invalid delimiter') err_eof = error('encoding.csv: end of file') err_invalid_le = error('encoding.csv: could not find any valid line endings.') ) struct Reader { // not used yet // has_header bool // headings []string data string mut: delimiter byte comment byte is_mac_pre_osx_le bool row_pos int } pub fn new_reader(data string) *Reader { return &Reader{ delimiter: `,`, comment: `#`, data: data } } // read() reads one row from the csv file pub fn (r mut Reader) read() ?[]string { l := r.read_record() or { return error(err) } return l } // Once we have multi dimensional array // pub fn (r mut Reader) read_all() ?[][]string { // mut records := []string // for { // record := r.read_record() or { // if error(err).error == err_eof.error { // return records // } else { // return error(err) // } // } // records << record // } // return records // } fn (r mut Reader) read_line() ?string { // last record if r.row_pos == r.data.len { return err_eof } le := if r.is_mac_pre_osx_le { '\r' } else { '\n' } mut i := r.data.index_after(le, r.row_pos) if i == -1 { if r.row_pos == 0 { // check for pre osx mac line endings i = r.data.index_after('\r', r.row_pos) if i != -1 { r.is_mac_pre_osx_le = true } else { // no valid line endings found return err_invalid_le } } } mut line := r.data.substr(r.row_pos, i) r.row_pos = i+1 // normalize win line endings (remove extra \r) if !r.is_mac_pre_osx_le && (line.len >= 1 && line[line.len-1] == `\r`) { line = line.left(line.len-1) } return line } fn (r mut Reader) read_record() ?[]string { if r.delimiter == r.comment { return err_comment_is_delim } mut line := '' for { l := r.read_line() or { return error(err) } line = l // skip commented lines if line[0] == r.comment { continue } break } mut fields := []string mut i := -1 for { // not quoted if line[0] != `"` { i = line.index(r.delimiter.str()) if i == -1 { // last break } fields << line.left(i) line = line.right(i+1) continue } // quoted else { line = line.right(1) i = line.index('"') if i+1 == line.len { // last record fields << line.left(i) break } next := line[i+1] if next == r.delimiter { fields << line.left(i) line = line.right(i) continue } line = line.right(1) } if i <= -1 && fields.len == 0 { return err_invalid_delim } } return fields }