-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCsvExport.cs
208 lines (189 loc) · 6.1 KB
/
CsvExport.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Linq;
using System.Text;
namespace Jitbit.Utils
{
/// <summary>
/// Simple CSV export
/// Example:
/// CsvExport myExport = new CsvExport();
///
/// myExport.AddRow();
/// myExport["Region"] = "New York, USA";
/// myExport["Sales"] = 100000;
/// myExport["Date Opened"] = new DateTime(2003, 12, 31);
///
/// myExport.AddRow();
/// myExport["Region"] = "Sydney \"in\" Australia";
/// myExport["Sales"] = 50000;
/// myExport["Date Opened"] = new DateTime(2005, 1, 1, 9, 30, 0);
///
/// Then you can do any of the following three output options:
/// string myCsv = myExport.Export();
/// myExport.ExportToFile("Somefile.csv");
/// byte[] myCsvData = myExport.ExportToBytes();
/// </summary>
public class CsvExport
{
/// <summary>
/// To keep the ordered list of column names
/// </summary>
List<string> _fields = new List<string>();
/// <summary>
/// The list of rows
/// </summary>
List<Dictionary<string, object>> _rows = new List<Dictionary<string, object>>();
/// <summary>
/// The current row
/// </summary>
Dictionary<string, object> _currentRow { get { return _rows[_rows.Count - 1]; } }
/// <summary>
/// The string used to separate columns in the output
/// </summary>
private readonly string _columnSeparator;
/// <summary>
/// Whether to include the preamble that declares which column separator is used in the output
/// </summary>
private readonly bool _includeColumnSeparatorDefinitionPreamble;
/// <summary>
/// Initializes a new instance of the <see cref="Jitbit.Utils.CsvExport"/> class.
/// </summary>
/// <param name="columnSeparator">
/// The string used to separate columns in the output.
/// By default this is a comma so that the generated output is a CSV file.
/// </param>
/// <param name="includeColumnSeparatorDefinitionPreamble">
/// Whether to include the preamble that declares which column separator is used in the output.
/// By default this is <c>true</c> so that Excel can open the generated CSV
/// without asking the user to specify the delimiter used in the file.
/// </param>
public CsvExport(string columnSeparator=",", bool includeColumnSeparatorDefinitionPreamble=true)
{
_columnSeparator = columnSeparator;
_includeColumnSeparatorDefinitionPreamble = includeColumnSeparatorDefinitionPreamble;
}
/// <summary>
/// Set a value on this column
/// </summary>
public object this[string field]
{
set
{
// Keep track of the field names, because the dictionary loses the ordering
if (!_fields.Contains(field)) _fields.Add(field);
_currentRow[field] = value;
}
}
/// <summary>
/// Call this before setting any fields on a row
/// </summary>
public void AddRow()
{
_rows.Add(new Dictionary<string, object>());
}
/// <summary>
/// Add a list of typed objects, maps object properties to CsvFields
/// </summary>
public void AddRows<T>(IEnumerable<T> list)
{
if (list.Any())
{
foreach (var obj in list)
{
AddRow();
var values = obj.GetType().GetProperties();
foreach (var value in values)
{
this[value.Name] = value.GetValue(obj, null);
}
}
}
}
/// <summary>
/// Converts a value to how it should output in a csv file
/// If it has a comma, it needs surrounding with double quotes
/// Eg Sydney, Australia -> "Sydney, Australia"
/// Also if it contains any double quotes ("), then they need to be replaced with quad quotes[sic] ("")
/// Eg "Dangerous Dan" McGrew -> """Dangerous Dan"" McGrew"
/// </summary>
/// <param name="columnSeparator">
/// The string used to separate columns in the output.
/// By default this is a comma so that the generated output is a CSV document.
/// </param>
public static string MakeValueCsvFriendly(object value, string columnSeparator=",")
{
if (value == null) return "";
if (value is INullable && ((INullable)value).IsNull) return "";
if (value is DateTime)
{
if (((DateTime)value).TimeOfDay.TotalSeconds == 0)
return ((DateTime)value).ToString("yyyy-MM-dd");
return ((DateTime)value).ToString("yyyy-MM-dd HH:mm:ss");
}
string output = value.ToString().Trim();
if (output.Contains(columnSeparator) || output.Contains("\"") || output.Contains("\n") || output.Contains("\r"))
output = '"' + output.Replace("\"", "\"\"") + '"';
if (output.Length > 30000) //cropping value for stupid Excel
{
if (output.EndsWith("\""))
{
output = output.Substring(0, 30000);
if(output.EndsWith("\"") && !output.EndsWith("\"\"")) //rare situation when cropped line ends with a '"'
output += "\""; //add another '"' to escape it
output += "\"";
}
else
output = output.Substring(0, 30000);
}
return output;
}
/// <summary>
/// Outputs all rows as a CSV, returning one string at a time
/// </summary>
private IEnumerable<string> ExportToLines()
{
if (_includeColumnSeparatorDefinitionPreamble) yield return "sep=" + _columnSeparator;
// The header
yield return string.Join(_columnSeparator, _fields.Select(f => MakeValueCsvFriendly(f, _columnSeparator)));
// The rows
foreach (Dictionary<string, object> row in _rows)
{
foreach (string k in _fields.Where(f => !row.ContainsKey(f)))
{
row[k] = null;
}
yield return string.Join(_columnSeparator, _fields.Select(field => MakeValueCsvFriendly(row[field], _columnSeparator)));
}
}
/// <summary>
/// Output all rows as a CSV returning a string
/// </summary>
public string Export()
{
StringBuilder sb = new StringBuilder();
foreach (string line in ExportToLines())
{
sb.AppendLine(line);
}
return sb.ToString();
}
/// <summary>
/// Exports to a file
/// </summary>
public void ExportToFile(string path)
{
File.WriteAllLines(path, ExportToLines(), Encoding.UTF8);
}
/// <summary>
/// Exports as raw UTF8 bytes
/// </summary>
public byte[] ExportToBytes()
{
var data = Encoding.UTF8.GetBytes(Export());
return Encoding.UTF8.GetPreamble().Concat(data).ToArray();
}
}
}