diff options
Diffstat (limited to 'lib/src/csv.dart')
| -rw-r--r-- | lib/src/csv.dart | 86 |
1 files changed, 86 insertions, 0 deletions
diff --git a/lib/src/csv.dart b/lib/src/csv.dart new file mode 100644 index 0000000..ab2f0be --- /dev/null +++ b/lib/src/csv.dart @@ -0,0 +1,86 @@ +/// A minimal RFC 4180 reader, for the `/csv` batch endpoint. +/// +/// Values may contain commas and are then double-quoted with `""` escaping, so +/// splitting on `,` corrupts every row that carries a street name like +/// `Rua Um, Dois`. This is here because the Dart SDK ships no CSV parser and +/// this library keeps its dependency list to one entry. +library; + +const String _bom = '\u{FEFF}'; + +/// Parses CSV text into records keyed by its header row. +/// +/// A leading byte-order mark is dropped, `CRLF` and `LF` are both accepted, and +/// a row shorter than the header has its remaining columns filled with the +/// empty string. An empty input yields no records. +List<Map<String, String>> parseCsv(String text) { + final rows = _parseRows(text.startsWith(_bom) ? text.substring(1) : text); + if (rows.isEmpty) { + return <Map<String, String>>[]; + } + + final header = rows.removeAt(0); + final records = <Map<String, String>>[]; + for (final row in rows) { + if (row.length == 1 && row.first.isEmpty) { + continue; + } + final record = <String, String>{}; + for (var i = 0; i < header.length; i++) { + record[header[i]] = i < row.length ? row[i] : ''; + } + records.add(record); + } + return records; +} + +List<List<String>> _parseRows(String text) { + final rows = <List<String>>[]; + final field = StringBuffer(); + var row = <String>[]; + var quoted = false; + var started = false; + + for (var i = 0; i < text.length; i++) { + final char = text[i]; + + if (quoted) { + if (char == '"') { + if (i + 1 < text.length && text[i + 1] == '"') { + field.write('"'); + i++; + } else { + quoted = false; + } + } else { + field.write(char); + } + continue; + } + + if (char == '"') { + quoted = true; + started = true; + } else if (char == ',') { + row.add(field.toString()); + field.clear(); + started = true; + } else if (char == '\n') { + row.add(field.toString()); + field.clear(); + rows.add(row); + row = <String>[]; + started = false; + } else if (char != '\r') { + field.write(char); + started = true; + } + } + + if (started || field.isNotEmpty || row.isNotEmpty) { + row.add(field.toString()); + rows.add(row); + } + + return rows; +} |