I have expanded on @Joel's answer above following the comments as what he produced seemed the closest I had found to dealing with a CSV where the data also contains commas so simply doing a split on the string does not work as splits on commas in fields too: https://stackoverflow.com/a/63202295/915426
This version uses string builder for increased performance and also adds a new line character to the end of the string if it does not end with one to ensure the last line of data gets included but is otherwise the same:
static List<List<string>> ParseCsv(string csv)
{
var parsedCsv = new List<List<string>>();
var row = new List<string>();
StringBuilder field = new StringBuilder();
bool inQuotedField = false;
//If CSV does not end with a new line character then add one to ensure final line of data is included
if (csv.Substring(csv.Length - 1, 1) != "\n")
{
csv = csv += "\n";
}
for (int i = 0; i < csv.Length; i++)
{
char current = csv[i];
char next = i == csv.Length - 1 ? ' ' : csv[i + 1];
// if current character is not a quote or comma or carriage return or newline (or not a quote and currently in an a quoted field), just add the character to the current field text
if ((current != '"' && current != ',' && current != '\r' && current != '\n') || (current != '"' && inQuotedField))
{
field.Append(current);
}
else if (current == ' ' || current == '\t')
{
continue; // ignore whitespace outside a quoted field
}
else if (current == '"')
{
if (inQuotedField && next == '"')
{ // quote is escaping a quote within a quoted field
i++; // skip escaping quote
field.Append(current);
}
else if (inQuotedField)
{ // quote signifies the end of a quoted field
row.Add(field.ToString());
if (next == ',')
{
i++; // skip the comma separator since we've already found the end of the field
}
field = new StringBuilder(); //Clear value
inQuotedField = false;
}
else
{ // quote signifies the beginning of a quoted field
inQuotedField = true;
}
}
else if (current == ',')
{ //
row.Add(field.ToString());
field = new StringBuilder(); //Clear value
}
else if (current == '\n')
{
row.Add(field.ToString());
parsedCsv.Add(new List<string>(row));
field = new StringBuilder(); //Clear value
row.Clear();
}
}
return parsedCsv;
}