Skip to content

Instantly share code, notes, and snippets.

@davetransom
Last active December 27, 2015 00:59
Show Gist options
  • Save davetransom/7241982 to your computer and use it in GitHub Desktop.
Save davetransom/7241982 to your computer and use it in GitHub Desktop.
A simple, lightweight and maintainable way to output arbitrary CSV/TSV - keeps header names close to the column/value definition.
// vars to be used inside column value funcs
DateTime utcnow = DateTime.UtcNow;
const decimal VIP_SPEND = 1000000M;
var fields = new CsvDefinition<MyRecord>
{
{ "Username", row => string.Concat(row.Prefix, ":", row.Username) },
{ "Email", row => row.Email },
{ "Status", row => row.TotalSpend >= VIP_SPEND ? "VIP" : "Pffft..., Peon" },
{ "Spend ($)", row => row.TotalSpend == null ? "None" : row.TotalSpend.ToString("n2") },
{ "Year of Birth", row => row.Yob > 1900 && row.Yob <= utcnow.Year ? row.Yob.Value.ToString() : null },
{ "Enabled", row => row.IsEnabled ? "Y" : "N" },
{ "Flags", row => string.Join(", ", new string[]
{
row.IsDemo ? "demo" : null,
row.IsManager ? "manager" : null,
row.IsAdmin ? "admin" : null
}.Where(x => x != null))
},
{ "Timestamp", row => DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff") }
};
IEnumerable<MyRecord> rows = someDataSource.AsEnumerable<MyRecord>();
using (var writer = new StringWriter())
{
// perhaps write out some additional notes or headers
// writer.WriteLine("# -- bof --")
fields.Write(writer, rows, "\t"); // tab delimited
// writer.WriteLine("# -- eof --")
return writer.ToString();
}
class CsvDefinition<T> : List<KeyValuePair<string, Func<T, string>>>
{
public CsvDefinition()
: base()
{ }
public CsvDefinition(int capacity)
: base(capacity)
{ }
public CsvDefinition(IEnumerable<KeyValuePair<string, Func<T, string>>> items)
: base(items)
{ }
/// <summary>
/// Adds a Key/Value pair to the definition - used for easy object initialisation
/// </summary>
/// <param name="key">The name of the column/header</param>
/// <param name="value">The Func to convert T into a string for the column value</param>
public void Add(string key, Func<T, string> value)
{
Add(new KeyValuePair<string, Func<T, string>>(key, value));
}
/// <summary>
/// Writes the CSV, outputs a header line, then iterates over rows, converting into column values using the defined func
/// </summary>
/// <param name="writer">The TextWriter to write the lines to</param>
/// <param name="rows">The enumerable of T tothat represents the lines</param>
/// <param name="delimiter">The string to delimit column values with. A single character delimiter isare also used to escape the value, multi-character strings are not escaped.</param>
public void Write(TextWriter writer, IEnumerable<T> rows, string delimiter = ",")
{
if(delimiter == null)
throw new ArgumentNullException("delimiter");
char[] escChars = delimiter.Length == 1 ? new[] { delimiter[0], '\n', '\r', '"' } : new[] { '\n', '\r', '"' };
string header = string.Join(delimiter, this.Select(x => escape(x.Key, escChars)));
writer.WriteLine(header);
foreach (T row in rows)
{
string line = string.Join(
delimiter,
this.Select(x => escape(x.Value(row), escChars)) // x.Value == Func<T, string>
);
writer.WriteLine(line);
}
}
// adapted from: http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters
static string escape(string value, char[] chars)
{
if (value == null)
return null;
if (value.IndexOfAny(chars) != -1)
return string.Concat("\"", value.Replace("\"", "\"\""), "\"");
return value;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment