using System.Globalization; namespace AIStudio.Tools; /// /// Writes rows of character-separated values. Fields are quoted according to RFC 4180 using the /// separator of the respective file. /// public static class CsvWriter { /// /// The separator a spreadsheet expects from a CSV file written for the given language. /// /// /// Wherever a comma separates the decimals of a number, it cannot separate the columns of a /// file as well: German Excel therefore expects a semicolon and puts a comma-separated file /// into a single column. This is the same rule Excel itself follows when it writes a CSV, so /// we ask the culture rather than keeping a list of languages of our own. /// /// The IETF tag of the language, for example "de-DE". /// The separator to write with. public static char SeparatorFor(string ietfTag) { if (string.IsNullOrWhiteSpace(ietfTag)) return ','; try { var culture = CultureInfo.GetCultureInfo(ietfTag); return culture.NumberFormat.NumberDecimalSeparator is "," ? ';' : ','; } catch (CultureNotFoundException) { return ','; } } /// /// Joins the given fields into one row. /// /// The separator between two fields. /// The fields of the row. /// The row, without a line ending. public static string ToRow(char separator, params string[] fields) => string.Join(separator, fields.Select(field => ToField(field, separator))); /// /// Quotes one field according to RFC 4180. /// private static string ToField(string text, char separator) { if (string.IsNullOrEmpty(text)) return string.Empty; // Quoting the complete field is important for long and multi-line AI // answers: neither separators nor line breaks within an answer may // create another column or row. if (!text.Contains(separator) && !text.Contains('"') && !text.Contains('\n') && !text.Contains('\r')) return text; return $""" "{text.Replace("\"", "\"\"")}" """; } }