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
|
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tango.CSV
{
public class CsvDynamicWriter
{
private sealed class ColumnInfo
{
public string Name;
public string Default;
public int Index;
public int CreationOrder;
}
private readonly Dictionary<string, ColumnInfo> _colMap = new Dictionary<string, ColumnInfo>();
private readonly List<ColumnInfo> _cols = new List<ColumnInfo>();
private int _creationCounter = 0;
private readonly List<Dictionary<string, string>> _rows = new List<Dictionary<string, string>>();
private Dictionary<string, string> _currentRow = new Dictionary<string, string>();
public void Write(int value, string columnName, int defaultValue, int index)
{
Write(value.ToString(), columnName, defaultValue.ToString(), index);
}
public void Write(double value, string columnName, double defaultValue, int index)
{
Write(value.ToString(), columnName, defaultValue.ToString(), index);
}
public void Write(bool value, string columnName, bool defaultValue, int index)
{
Write(value.ToString(), columnName, defaultValue.ToString(), index);
}
public void Write(Enum value, string columnName, Enum defaultValue, int index)
{
Write(value.ToString(), columnName, defaultValue.ToString(), index);
}
/// <summary>
/// Writes a value to the specified column in the current row.
/// If the column does not exist, it is created with the given default value and index.
/// Column order in the CSV header is by (Index asc, CreationOrder asc).
/// </summary>
public void Write(string value, string columnName, string defaultValue, int index)
{
ColumnInfo col;
if (!_colMap.TryGetValue(columnName, out col))
{
col = new ColumnInfo
{
Name = columnName,
Default = defaultValue,
Index = index,
CreationOrder = _creationCounter++
};
_colMap[columnName] = col;
_cols.Add(col);
// Backfill all prior rows with the column's default.
foreach (var row in _rows)
{
if (!row.ContainsKey(columnName))
row[columnName] = defaultValue;
}
}
// If the column already exists, we keep the original index/default.
// (If you need reindexing, add an explicit method to change col.Index.)
_currentRow[columnName] = value ?? "";
}
/// <summary>
/// Moves to the next row, finalizing the current one.
/// Missing cells get their column default value.
/// </summary>
public void Next()
{
if (_currentRow == null) _currentRow = new Dictionary<string, string>();
foreach (var col in _cols)
{
if (!_currentRow.ContainsKey(col.Name))
_currentRow[col.Name] = col.Default ?? "";
}
_rows.Add(_currentRow);
_currentRow = new Dictionary<string, string>();
}
/// <summary>
/// Saves the CSV to disk using the column order defined by (Index, CreationOrder).
/// </summary>
public void Save(string filePath)
{
// Auto-finalize last row if it has any values.
if (_currentRow != null && _currentRow.Count > 0)
Next();
var orderedCols = _cols
.OrderBy(c => c.Index)
.ThenBy(c => c.CreationOrder)
.ToList();
var sb = new StringBuilder();
// Header
sb.AppendLine(string.Join(",", orderedCols.Select(c => EscapeCsv(c.Name))));
// Rows
foreach (var row in _rows)
{
var values = new List<string>(orderedCols.Count);
for (int i = 0; i < orderedCols.Count; i++)
{
var name = orderedCols[i].Name;
string v;
if (!row.TryGetValue(name, out v))
{
// Shouldn't happen (Next() fills), but be defensive.
v = orderedCols[i].Default ?? "";
}
values.Add(EscapeCsv(v));
}
sb.AppendLine(string.Join(",", values));
}
File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
}
private static string EscapeCsv(string input)
{
if (input == null) return "";
if (input.IndexOfAny(new[] { ',', '"', '\n', '\r' }) >= 0)
return "\"" + input.Replace("\"", "\"\"") + "\"";
return input;
}
}
}
|