// TODO: empty values might not be handled correctly // (e.g. a,,c) sclass ExtendedCSVParser implements Steppable { S csv; int numFields; long numRecords; long maxRecordsToLoad = -1; // set to >= 0 to limit int i, startOfValue; bool inQuotedValue; *() {} *(S *csv) {} run { stepAll(this); } public bool step() { if (i >= l(csv)) { foundValue(substring(csv, startOfValue)); endOfRecord(); false; } char c = csv.charAt(i); if (c == ',') { if (!inQuotedValue) { foundValue(substring(csv, startOfValue, i)); startOfValue = i+1; } } else if (c == '\r' || c == '\n') { if (!inQuotedValue) { foundValue(substring(csv, startOfValue, i)); startOfValue = i+1; if (endOfRecord()) false; } } else if (c == '"') { if (inQuotedValue) { if (charAt(csv, i+1) == '"') i++; // double quotes inside of a value else inQuotedValue = false; } else set inQuotedValue; } i++; true; } S unquoteValue(S value) { ret dropPrefix("\"", dropSuffix("\"", replace(trim(value), "\"\"", "\""))); } void foundValue(S value) { if (nempty(value)) { ++numFields; onValueFound(unquoteValue(value)); } } // return true if should exit bool endOfRecord() { if (numFields == 0) false; onEndOfRecord(); numFields = 0; ret ++numRecords >= maxRecordsToLoad && maxRecordsToLoad >= 0; } swappable void onValueFound(S value) {} swappable void onEndOfRecord() {} }