From c7713c5f058d8d1d5873e109e856403ffcb4ed39 Mon Sep 17 00:00:00 2001 From: Steven Date: Sun, 1 Sep 2024 01:33:32 -0400 Subject: [PATCH] Create CSVObjectCollection --- CSharp.Nixill/src/Grid/CSVObjectCollection.cs | 292 ++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 CSharp.Nixill/src/Grid/CSVObjectCollection.cs diff --git a/CSharp.Nixill/src/Grid/CSVObjectCollection.cs b/CSharp.Nixill/src/Grid/CSVObjectCollection.cs new file mode 100644 index 0000000..72c5a41 --- /dev/null +++ b/CSharp.Nixill/src/Grid/CSVObjectCollection.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Nixill.Utils; + +namespace Nixill.Collections.Grid.CSV; + +public class CSVObjectCollection : IList +{ + readonly List _Columns = []; + readonly List _Contents = []; + + public IEnumerable Columns => _Columns.AsReadOnly(); + public IEnumerable Contents => _Contents.AsReadOnly(); + + public CSVObjectCollection() { } + + public CSVObjectCollection(IEnumerable contents) + { + _Contents = [.. contents]; + } + + public CSVObjectCollection(IEnumerable columns, IEnumerable contents) + { + _Columns = [.. columns]; + _Contents = [.. contents]; + } + + public CSVObjectCollection(CSVObjectCollection clones) + { + _Columns = [.. clones._Columns]; + _Contents = [.. clones]; + } + + public static CSVObjectCollection ParseObjectsFromFile(string path, Func, T> deserializer) + => ParseObjects(FileUtils.FileCharEnumerator(path), deserializer); + + public static CSVObjectCollection ParseObjectsFromStream(StreamReader reader, + Func, T> deserializer) + => ParseObjects(FileUtils.StreamCharEnumerator(reader), deserializer); + + public static CSVObjectCollection ParseObjects(IEnumerable input, + Func, T> deserializer) + { + IEnumerable> rows = CSVParser.EnumerableToRows(input); + bool isHeaderRow = true; + List columns = []; + List objects = []; + + foreach (var row in rows) + { + if (isHeaderRow) + { + columns = [.. row]; + isHeaderRow = false; + } + else + { + IDictionary properties = columns.Zip(row).ToPropertyDictionary(); + objects.Add(deserializer(properties)); + } + } + + return new CSVObjectCollection(columns, objects); + } + + public string NewRow(T item, Func> serializer) + { + _Contents.Add(item); + + var properties = serializer(item); + var columnValues = _Columns.Select(c => + { + if (properties.TryGetValue(c, out string v)) return v; + return null; + }); + return columnValues.Select(CSVParser.CSVEscape).SJoin(","); + } + + public string ItemToRow(T item, Func> serializer) + { + var properties = serializer(item); + var columnValues = _Columns.Select(c => + { + if (properties.TryGetValue(c, out string v)) return v; + return null; + }); + return columnValues.Select(CSVParser.CSVEscape).SJoin(","); + } + + public IEnumerable RowsAsCSV(Func> serializer) + { + yield return _Columns.Select(CSVParser.CSVEscape).SJoin(","); + foreach (string str in _Contents.Select(i => ItemToRow(i, serializer))) + yield return str; + } + + public string FormatCSV(Func> serializer) + => RowsAsCSV(serializer).SJoin("\n"); + + public void FormatCSVToFile(string path, Func> serializer) + => File.WriteAllText(path, FormatCSV(serializer)); + + #region IList: Contents + public int Count => _Contents.Count; + + bool ICollection.IsReadOnly => ((ICollection)_Contents).IsReadOnly; + + public T this[int index] { get => _Contents[index]; set => _Contents[index] = value; } + + public int IndexOf(T item) + { + return ((IList)_Contents).IndexOf(item); + } + + public void Insert(int index, T item) + { + ((IList)_Contents).Insert(index, item); + } + + public void RemoveAt(int index) + { + ((IList)_Contents).RemoveAt(index); + } + + public void Add(T item) + { + ((ICollection)_Contents).Add(item); + } + + public void Clear() + { + ((ICollection)_Contents).Clear(); + } + + public bool Contains(T item) + { + return ((ICollection)_Contents).Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + ((ICollection)_Contents).CopyTo(array, arrayIndex); + } + + public bool Remove(T item) + { + return ((ICollection)_Contents).Remove(item); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)_Contents).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_Contents).GetEnumerator(); + } + #endregion + + #region IList: Columns + public int IndexOfColumn(string item) + { + return ((IList)_Columns).IndexOf(item); + } + + public void InsertColumn(int index, string item) + { + ((IList)_Columns).Insert(index, item); + } + + public void RemoveColumnAt(int index) + { + ((IList)_Columns).RemoveAt(index); + } + + public void AddColumn(string item) + { + ((ICollection)_Columns).Add(item); + } + + public void ClearColumns() + { + ((ICollection)_Columns).Clear(); + } + + public bool ContainsColumn(string item) + { + return ((ICollection)_Columns).Contains(item); + } + + public void CopyColumnsTo(string[] array, int arrayIndex) + { + ((ICollection)_Columns).CopyTo(array, arrayIndex); + } + + public bool RemoveColumn(string item) + { + return ((ICollection)_Columns).Remove(item); + } + + public IEnumerator GetColumnEnumerator() + { + return ((IEnumerable)_Columns).GetEnumerator(); + } + #endregion +} + +public static class CSVObjectCollection +{ + public static CSVObjectCollection ParseObjectsFromFile(string path, Func, T> deserializer) + => CSVObjectCollection.ParseObjects(FileUtils.FileCharEnumerator(path), deserializer); + + public static CSVObjectCollection ParseObjectsFromStream(StreamReader reader, + Func, T> deserializer) + => CSVObjectCollection.ParseObjects(FileUtils.StreamCharEnumerator(reader), deserializer); + + public static CSVObjectCollection ParseObjects(IEnumerable input, + Func, T> deserializer) + => CSVObjectCollection.ParseObjects(input, deserializer); +} + +file class PropertyDictionary(Dictionary backing) : IDictionary +{ + readonly Dictionary Backing = backing; + + public string this[string key] + { + get => Backing.TryGetValue(key, out string value) ? value != "" ? value : null : null; + set => throw new InvalidOperationException("This dictionary is read-only."); + } + + public ICollection Keys => Backing.Keys; + + public ICollection Values => Backing.Values; + + public int Count => Backing.Count; + + public bool IsReadOnly => true; + + public void Add(string key, string value) + => throw new InvalidOperationException("This dictionary is read-only."); + + public void Add(KeyValuePair item) + => throw new InvalidOperationException("This dictionary is read-only."); + + public void Clear() + => throw new InvalidOperationException("This dictionary is read-only."); + + public bool Contains(KeyValuePair item) + => Backing.Contains(item); + + public bool ContainsKey(string key) + => Backing.ContainsKey(key); + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + => ((IDictionary)Backing).CopyTo(array, arrayIndex); + + public IEnumerator> GetEnumerator() + => Backing.GetEnumerator(); + + public bool Remove(string key) + => throw new InvalidOperationException("This dictionary is read-only."); + + public bool Remove(KeyValuePair item) + => throw new InvalidOperationException("This dictionary is read-only."); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) + { + bool success = Backing.TryGetValue(key, out value); + + if (value == "") + { + value = null; + return false; + } + + return success; + } + + IEnumerator IEnumerable.GetEnumerator() => Backing.GetEnumerator(); +} + +file static class PropertyDictionaryExtensions +{ + public static PropertyDictionary ToPropertyDictionary(this IEnumerable<(string, string)> dict) + => new PropertyDictionary(dict.ToDictionary()); +} \ No newline at end of file