From 1ba701e03658a6c5de49137500bc152533c90c24 Mon Sep 17 00:00:00 2001 From: joe-conigliaro Date: Sat, 17 Aug 2019 22:51:20 +1000 Subject: [PATCH] encoding.csv: add write support --- vlib/encoding/csv/reader.v | 37 +++++++++++----- vlib/encoding/csv/writer.v | 89 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 vlib/encoding/csv/writer.v diff --git a/vlib/encoding/csv/reader.v b/vlib/encoding/csv/reader.v index 6ee69124ec..44b63ccc78 100644 --- a/vlib/encoding/csv/reader.v +++ b/vlib/encoding/csv/reader.v @@ -1,9 +1,9 @@ -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. +module csv + // 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. @@ -21,6 +21,7 @@ struct Reader { // has_header bool // headings []string data string +pub: mut: delimiter byte comment byte @@ -92,6 +93,9 @@ fn (r mut Reader) read_record() ?[]string { if r.delimiter == r.comment { return err_comment_is_delim } + if !valid_delim(r.delimiter) { + return err_invalid_delim + } mut line := '' for { l := r.read_line() or { @@ -122,16 +126,18 @@ fn (r mut Reader) read_record() ?[]string { 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 + if i != -1 { + 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) } @@ -142,3 +148,10 @@ fn (r mut Reader) read_record() ?[]string { return fields } + +fn valid_delim(b byte) bool { + return b != 0 && + b != `"` && + b != `\r` && + b != `\n` +} diff --git a/vlib/encoding/csv/writer.v b/vlib/encoding/csv/writer.v new file mode 100644 index 0000000000..d4773d6d83 --- /dev/null +++ b/vlib/encoding/csv/writer.v @@ -0,0 +1,89 @@ +// 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. + +module csv + +import strings + +struct Writer { + sb strings.Builder +pub: +mut: + use_crlf bool + delimiter byte +} + +pub fn new_writer() *Writer { + return &Writer{ + delimiter: `,`, + sb: strings.new_builder(200) + } +} + +// write writes a single record +pub fn (w mut Writer) write(record []string) ?bool { + if !valid_delim(w.delimiter) { + return err_invalid_delim + } + le := if w.use_crlf { '\r\n' } else { '\n' } + for n, _field in record { + mut field := _field + if n > 0 { + w.sb.write(w.delimiter.str()) + } + + if !w.field_needs_quotes(field) { + w.sb.write(field) + continue + } + + w.sb.write('"') + + for field.len > 0 { + mut i := field.index_any('"\r\n') + if i < 0 { + i = field.len + } + + w.sb.write(field.left(i)) + field = field.right(i) + + if field.len > 0 { + z := field[0] + switch z { + case `"`: + w.sb.write('""') + case `\r` || `\n`: + w.sb.write(le) + } + field = field.right(1) + } + } + w.sb.write('"') + } + + w.sb.write(le) + return true +} + +// Once we have multi dimensional array +// pub fn (w &Writer) write_all(records [][]string) { +// for _, record in records { +// w.write(record) +// } +// } + +fn (w &Writer) field_needs_quotes(field string) bool { + if field == '' { + return false + } + if field.contains(w.delimiter.str()) || (field.index_any('"\r\n') != -1) { + return true + } + return false +} + +pub fn (w &Writer) str() string { + return w.sb.str() +}