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("\"", "\"\"")}"
""";
}
}