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
|
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>();
/// <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;
}
}
}
|