/// 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> parseCsv(String text) { final rows = _parseRows(text.startsWith(_bom) ? text.substring(1) : text); if (rows.isEmpty) { return >[]; } final header = rows.removeAt(0); final records = >[]; for (final row in rows) { if (row.length == 1 && row.first.isEmpty) { continue; } final record = {}; for (var i = 0; i < header.length; i++) { record[header[i]] = i < row.length ? row[i] : ''; } records.add(record); } return records; } List> _parseRows(String text) { final rows = >[]; final field = StringBuffer(); var row = []; 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 = []; 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; }