csvToMap method

Map<int, List<String>> csvToMap(
  1. String input
)

Parses CSV text into a row-indexed map. The parser is RFC 4180 compliant: it preserves fields exactly (no whitespace trimming), supports both \n and \r\n line terminators, supports quoted fields that contain commas, quotes (escaped as "") and newlines, and does NOT silently renumber rows when the input contains blank lines — blank lines are preserved as empty rows so row indices match the source.

Implementation

Map<int, List<String>> csvToMap(String input) {
  final rows = <List<String>>[];
  var field = StringBuffer();
  var row = <String>[];
  var inQuotes = false;
  var i = 0;
  final n = input.length;
  while (i < n) {
    final ch = input[i];
    if (inQuotes) {
      if (ch == '"') {
        if (i + 1 < n && input[i + 1] == '"') {
          // Escaped quote inside a quoted field.
          field.write('"');
          i += 2;
          continue;
        }
        inQuotes = false;
        i++;
        continue;
      }
      field.write(ch);
      i++;
      continue;
    }
    if (ch == '"') {
      inQuotes = true;
      i++;
      continue;
    }
    if (ch == ',') {
      row.add(field.toString());
      field = StringBuffer();
      i++;
      continue;
    }
    if (ch == '\r') {
      // Consume CR (and an optional following LF) as a row terminator.
      row.add(field.toString());
      rows.add(row);
      field = StringBuffer();
      row = <String>[];
      if (i + 1 < n && input[i + 1] == '\n') {
        i += 2;
      } else {
        i++;
      }
      continue;
    }
    if (ch == '\n') {
      row.add(field.toString());
      rows.add(row);
      field = StringBuffer();
      row = <String>[];
      i++;
      continue;
    }
    field.write(ch);
    i++;
  }
  // Flush the final field/row if input did not end with a line terminator.
  // If the input ended cleanly with a terminator and there's no pending
  // content, we don't emit a phantom trailing empty row.
  if (field.isNotEmpty || row.isNotEmpty) {
    row.add(field.toString());
    rows.add(row);
  }
  final res = <int, List<String>>{};
  for (var r = 0; r < rows.length; r++) {
    res[r] = rows[r];
  }
  return res;
}