// TODO: empty values might not be handled correctly // (e.g. a,,c) sclass ExtendedCSVParser { S csv; int numFields; *() {} *(S *csv) {} run { int i = 0, startOfValue = 0; bool inQuotedValue; while ping (i < l(csv)) { char c = csv.charAt(i); if (c == ',') { foundValue(substring(csv, startOfValue, i)); startOfValue = i+1; } else if (c == '\r' || c == '\n') { if (!inQuotedValue) { foundValue(substring(csv, startOfValue, i)); startOfValue = i+1; endOfRecord(); } } else if (c == '"') { if (inQuotedValue) { if (charAt(csv, i+1) == '"') i++; // double quotes inside of a value else inQuotedValue = false; } else set inQuotedValue; } i++; } foundValue(substring(csv, startOfValue)); endOfRecord(); } S unquoteValue(S value) { ret dropPrefix("\"", dropSuffix("\"", replace(trim(value), "\"\"", "\""))); } void foundValue(S value) { if (nempty(value)) { ++numFields; onValueFound(unquoteValue(value)); } } void endOfRecord { if (numFields != 0) onEndOfRecord(); numFields = 0; } swappable void onValueFound(S value) {} swappable void onEndOfRecord() {} }