diff --git a/src/Kafa/Kafa.Span.cs b/src/Kafa/Kafa.Span.cs index faa7f54..d4b6269 100644 --- a/src/Kafa/Kafa.Span.cs +++ b/src/Kafa/Kafa.Span.cs @@ -20,7 +20,7 @@ public static IEnumerable Read(string content, KafaOptions? options = null ArgumentNullException.ThrowIfNullOrEmpty(content, nameof(content)); var rows = Read(content.AsSpan(), options); var typeInfo = new KafaTypeInfo(typeof(T), options); - var reflection = new KafaReflection(typeInfo, rows.Headers); + var reflection = new KafaReflection(typeInfo); return reflection.SetProperties(rows); } diff --git a/src/Kafa/Kafa.Stream.cs b/src/Kafa/Kafa.Stream.cs index 9f5762f..d6f6bbd 100644 --- a/src/Kafa/Kafa.Stream.cs +++ b/src/Kafa/Kafa.Stream.cs @@ -1,5 +1,8 @@ using nyingi.Kafa.Reader; using nyingi.Kafa.Reflection; +using System.IO; +using System.Runtime.Serialization; +using System.Text; using System.Threading; using System.Xml; using static nyingi.Kafa.Reader.KafaReader; @@ -27,7 +30,7 @@ public static IEnumerable Read(Stream ioStream, KafaOptions? options = nul { var rowEnumerable = Read(ioStream, options); var typeInfo = new KafaTypeInfo(typeof(T), options); - var reflection = new KafaReflection(typeInfo, rowEnumerable.Headers); + var reflection = new KafaReflection(typeInfo); return reflection.SetProperties(rowEnumerable); } @@ -60,7 +63,7 @@ public static async ValueTask> ReadAsync(Stream ioStream, Kafa { var rows = await ReadAsync(ioStream, options, cancellationToken); var typeInfo = new KafaTypeInfo(typeof(T), options); - var reflection = new KafaReflection(typeInfo, rows.Headers); + var reflection = new KafaReflection(typeInfo); return reflection.SetProperties(rows); } @@ -73,5 +76,42 @@ private static async ValueTask ReadProcessorAsync(KafaReadState k return reader.GetRows(); } + + public static async ValueTask WriteAsync(List entities, KafaOptions options =null) + { + ArgumentNullException.ThrowIfNull(entities, nameof(entities)); + var reflection = SetupOptions(options); + using var strWriter = new StringWriter(new StringBuilder()); + return await reflection.GetProperties(entities, strWriter); + } + + public static async ValueTask WriteToStreamAsync(List entities, KafaOptions options = null) + { + ArgumentNullException.ThrowIfNull(entities, nameof(entities)); + var reflection = SetupOptions(options); + var memoryStream = new MemoryStream(); + using var strWriter = new StreamWriter(memoryStream, leaveOpen: true); + var textStream = await reflection.GetProperties(entities, strWriter); + textStream.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + + return memoryStream; + } + + public static async ValueTask WriteToFileAsync(List entities, string path, KafaOptions options = null) + { + ArgumentNullException.ThrowIfNull(entities, nameof(entities)); + var reflection = SetupOptions(options); + using var fs = new FileStream(path, FileMode.Create); + using var strWriter = new StreamWriter(fs, options.Encoding!, 512); + await reflection.GetProperties(entities, strWriter); + } + + private static KafaReflection SetupOptions(KafaOptions options) + { + options = KafaOptions.ResolveKafaOptions(options); + var typeInfo = new KafaTypeInfo(typeof(T), options); + return new KafaReflection(typeInfo); + } } } diff --git a/src/Kafa/Kafa.csproj b/src/Kafa/Kafa.csproj index 7f21ffe..63bd5c7 100644 --- a/src/Kafa/Kafa.csproj +++ b/src/Kafa/Kafa.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Kafa/KafaTypeInfo.cs b/src/Kafa/KafaTypeInfo.cs index a8fc39b..d7faee6 100644 --- a/src/Kafa/KafaTypeInfo.cs +++ b/src/Kafa/KafaTypeInfo.cs @@ -4,8 +4,8 @@ namespace nyingi.Kafa { internal class KafaTypeInfo { - public Type Type { get; } - public KafaOptions KafaOptions { get; } + public readonly Type Type; + public readonly KafaOptions KafaOptions; public bool HasAttributes { get; private set;} public KafaTypeInfo(Type type, KafaOptions kafaOptions) { diff --git a/src/Kafa/Reflection/KafaReflection.Reader.cs b/src/Kafa/Reflection/KafaReflection.Reader.cs new file mode 100644 index 0000000..bc6334b --- /dev/null +++ b/src/Kafa/Reflection/KafaReflection.Reader.cs @@ -0,0 +1,59 @@ +using System.Diagnostics.Metrics; +using System.IO; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; + +namespace nyingi.Kafa.Reflection +{ + internal partial class KafaReflection + { + public async Task GetProperties(List entities, TextWriter textWriter) + { + + bool readHeader = false; + int propertyCount = 0; + int count = 0; + PropertyInfo[] propertyInfos = default!; + foreach (var entity in entities) + { + propertyInfos = entity.GetType().GetProperties(); + + if(TypeInfo.KafaOptions.HasHeader && !readHeader) + { + int countHeader = 0; + foreach (var propertyInfo in propertyInfos) + { + await textWriter.WriteAsync(propertyInfo.Name); + + if (countHeader < propertyInfos.Length - 1) + { + await textWriter.WriteAsync((char)TypeInfo.KafaOptions.FileType); + + } + countHeader++; + } + + await textWriter.WriteLineAsync(); + readHeader= true; + } + + propertyCount = propertyInfos.Length; + foreach (var propertyInfo in propertyInfos) + { + await textWriter.WriteAsync($"{propertyInfo.GetValue(entity)}"); + + if (count < propertyCount - 1) + { + await textWriter.WriteAsync((char)TypeInfo.KafaOptions.FileType); + } + count++; + } + await textWriter.WriteLineAsync(); + count = 0; + } + + return textWriter; + } + } +} diff --git a/src/Kafa/Reflection/KafaReflection.cs b/src/Kafa/Reflection/KafaReflection.Writer.cs similarity index 79% rename from src/Kafa/Reflection/KafaReflection.cs rename to src/Kafa/Reflection/KafaReflection.Writer.cs index 1403750..0c0898c 100644 --- a/src/Kafa/Reflection/KafaReflection.cs +++ b/src/Kafa/Reflection/KafaReflection.Writer.cs @@ -1,48 +1,52 @@ using System.Collections.Specialized; using System.Reflection; -using System.Runtime.CompilerServices; +using System.Reflection.PortableExecutable; using static nyingi.Kafa.Reader.KafaReader; namespace nyingi.Kafa.Reflection { - internal class KafaReflection + internal partial class KafaReflection { - public Dictionary properties= default; + private Dictionary properties= default; public readonly KafaTypeInfo TypeInfo; - public KafaReflection(KafaTypeInfo typeInfo, OrderedDictionary Headers = default) + public KafaReflection(KafaTypeInfo typeInfo) { // match propertyName with header TypeInfo = typeInfo; - // store all the properties - properties = new Dictionary(TypeInfo.Type.GetProperties().Length); + } + + private void ReadHeader(OrderedDictionary headers = null) + { + properties = new Dictionary(TypeInfo.Type.GetProperties().Length); // ahead + int count = 0; foreach (var property in TypeInfo.Type.GetProperties()) { - if(Headers != null) + if (headers != null) { var kafa = property.GetCustomAttribute(false); if (kafa != null) { - if(!string.IsNullOrEmpty(kafa.FieldName)) + if (!string.IsNullOrEmpty(kafa.FieldName)) { - properties.Add((int)Headers[kafa.FieldName], property); + properties.Add((int)headers[kafa.FieldName], property); } else { - properties.Add((int)Headers[kafa.FieldIndex], property); + properties.Add((int)headers[kafa.FieldIndex], property); } } - else if (Headers.Contains(property.Name)) + else if (headers.Contains(property.Name)) { - properties.Add((int)Headers[property.Name], property); + properties.Add((int)headers[property.Name], property); } } else { - properties.Add(count, property); + properties.Add(count, property); count++; } } @@ -50,6 +54,9 @@ public KafaReflection(KafaTypeInfo typeInfo, OrderedDictionary Headers = default public IEnumerable SetProperties(RowEnumerable rows) { + // process header first + ReadHeader(rows.Headers); + if(properties.Count == 0) { throw new Exception("{0} class is empty"); @@ -81,8 +88,11 @@ public IEnumerable SetProperties(RowEnumerable rows) return (List)instance; } + + private object? TypeResolver(Type type, Col col) { + if (type == typeof(string)) { return col.Value.ToString(); diff --git a/src/KafaTests/KafaReadToTypeTests.cs b/src/KafaTests/KafaReadToTypeTests.cs index a4c68d0..1590738 100644 --- a/src/KafaTests/KafaReadToTypeTests.cs +++ b/src/KafaTests/KafaReadToTypeTests.cs @@ -1,4 +1,6 @@ -namespace KafaTests +using System.IO; + +namespace KafaTests { class CsvData { @@ -41,6 +43,17 @@ public void ReadFromStreamToTypeOf() var rows = Kafa.Read(stream); Assert.NotNull(rows); Assert.NotEmpty(rows); + + foreach (var row in rows) + { + Assert.NotEqual(DateTime.MinValue, row.Date); + Assert.NotEqual(default, row.Open); + Assert.NotEqual(default, row.High); + Assert.NotEqual(default, row.Low); + Assert.NotEqual(default, row.Close); + Assert.NotEqual(default, row.Volume); + Assert.False(string.IsNullOrEmpty(row.Name)); + } } [Fact] @@ -96,5 +109,53 @@ public void ReadToTypeWithAttributes() } } + [Fact] + public async Task WriteCSVNoHeaderAsync() + { + var csvs = new List() + { + new CsvData{ Date = DateTime.Parse("10/10/2023 4:09:45 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512}, + new CsvData{ Date = DateTime.Parse("10/10/2023 4:09:45 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512} + }; + + var rowmem = await Kafa.WriteAsync(csvs, new KafaOptions() { HasHeader=false, FileType= FileType.CSV}); + var expected = "10/10/2023 4:09:45 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n10/10/2023 4:09:45 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n"; + var str = rowmem.ToString(); + Assert.NotNull(str); + Assert.NotEmpty(str); + Assert.Equal(expected, str); + } + + [Fact] + public async Task WriteCSVWithHeaderAsync() + { + var csvs = new List() + { + new CsvData{ Date = DateTime.Parse("10/10/2023 4:08:38 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512}, + new CsvData{ Date = DateTime.Parse("10/10/2023 4:08:38 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512} + }; + string expected = "Date,Open,High,Low,Close,Volume,Name\r\n10/10/2023 4:08:38 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n10/10/2023 4:08:38 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n"; + var rowmem = await Kafa.WriteAsync(csvs); + var str = rowmem.ToString(); + Assert.NotNull(str); + Assert.NotEmpty(str); + Assert.Equal(expected, str); + } + + [Fact] + public async Task WriteCSVToStreamAsync() + { + var csvs = new List() + { + new CsvData{ Date = DateTime.Parse("10/10/2023 4:08:38 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512}, + new CsvData{ Date = DateTime.Parse("10/10/2023 4:08:38 PM"), Open=12.45, Close=12.99, High=13.00, Low=12.1, Name="AMZN", Volume=1233435512} + }; + string expected = "Date,Open,High,Low,Close,Volume,Name\r\n10/10/2023 4:08:38 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n10/10/2023 4:08:38 PM,12.45,13,12.1,12.99,1233435512,AMZN\r\n"; + using var stream = await Kafa.WriteToStreamAsync(csvs); + Assert.NotNull(stream); + + var result = Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); + Assert.Equal(expected, result); + } } }