aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs
blob: 1c460d5e3db904524082f8f238ea3495ab1e510d (plain)
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;
        }
    }
}