using System.Text; namespace QuestionsApi.Services.Flashcards; // Minimal RFC 4180 CSV reader/writer (quoted fields, embedded commas/newlines, "" escaping). // UTF-8, text-only, matches the flashcard CSV contract exactly. public static class FlashcardCsv { public static readonly string[] Headers = ["stable_id", "deck", "type", "prompt", "answer", "extra", "tags", "source_question_external_id"]; public static List ParseRows(string text) { var rows = new List(); var field = new StringBuilder(); var row = new List(); var inQuotes = false; var i = 0; var n = text.Length; while (i < n) { var c = text[i]; if (inQuotes) { if (c == '"') { if (i + 1 < n && text[i + 1] == '"') { field.Append('"'); i += 2; continue; } inQuotes = false; i++; continue; } field.Append(c); i++; continue; } switch (c) { case '"': inQuotes = true; i++; break; case ',': row.Add(field.ToString()); field.Clear(); i++; break; case '\r': i++; break; case '\n': row.Add(field.ToString()); field.Clear(); rows.Add(row.ToArray()); row = []; i++; break; default: field.Append(c); i++; break; } } if (field.Length > 0 || row.Count > 0) { row.Add(field.ToString()); rows.Add(row.ToArray()); } return rows; } public static string WriteRow(IEnumerable fields) => string.Join(",", fields.Select(Escape)); private static string Escape(string field) => field.IndexOfAny(['"', ',', '\n', '\r']) >= 0 ? "\"" + field.Replace("\"", "\"\"") + "\"" : field; }