diff --git a/Elwig/Helpers/Export/Ods.cs b/Elwig/Helpers/Export/Ods.cs new file mode 100644 index 0000000..1a92c14 --- /dev/null +++ b/Elwig/Helpers/Export/Ods.cs @@ -0,0 +1,187 @@ +using Elwig.Models.Dtos; +using System; +using System.Collections.Generic; +using System.Data.Entity.Core.Common.CommandTrees.ExpressionBuilder; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace Elwig.Helpers.Export { + public class OdsFile : IDisposable, IAsyncDisposable { + + protected readonly string FileName; + protected readonly ZipArchive ZipArchive; + protected StreamWriter? Content; + + public OdsFile(string filename) { + FileName = filename; + File.Delete(filename); + ZipArchive = ZipFile.Open(FileName, ZipArchiveMode.Create); ; + Content = null; + } + + public void Dispose() { + DisposeAsync().GetAwaiter().GetResult(); + } + + public async ValueTask DisposeAsync() { + await AddTrailer(); + Content?.Close(); + Content?.DisposeAsync(); + ZipArchive?.Dispose(); + GC.SuppressFinalize(this); + } + + private async Task AddHeader() { + var mimetype = ZipArchive.CreateEntry("mimetype", CompressionLevel.NoCompression); + using (var stream = mimetype.Open()) { + using var writer = new StreamWriter(stream, Utils.UTF8); + await writer.WriteAsync("application/vnd.oasis.opendocument.spreadsheet"); + } + + var manifest = ZipArchive.CreateEntry("META-INF/manifest.xml"); + using (var stream = manifest.Open()) { + using var writer = new StreamWriter(stream, Utils.UTF8); + await writer.WriteAsync(""" + + + + + + + + + """); + } + + var styles = ZipArchive.CreateEntry("styles.xml"); + using (var stream = styles.Open()) { + using var writer = new StreamWriter(stream, Utils.UTF8); + await writer.WriteAsync(""" + + + + + + + + + + + + + + + + + + + + + + + """); + } + + var meta = ZipArchive.CreateEntry("meta.xml"); + using (var stream = meta.Open()) { + using var writer = new StreamWriter(stream, Utils.UTF8); + var now = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); + await writer.WriteAsync($""" + + + + Elwig {App.Version} + Elwig + Elwig + {now} + {now} + + + + """); + } + + var content = ZipArchive.CreateEntry("content.xml"); + Content = new StreamWriter(content.Open(), Utils.UTF8); + await Content.WriteAsync(""" + + + + + + + """); + } + + private async Task AddTrailer() { + if (Content == null) await AddHeader(); + if (Content == null) return; + await Content.WriteAsync(" \r\n \r\n\r\n"); + } + + public async Task AddTable(DataTable table) { + if (Content == null) await AddHeader(); + if (Content == null) return; + var totalSpan = table.ColumnSpans.Sum(s => s.Item2); + + await Content.WriteAsync( + $" \r\n" + + $" \r\n" + + $" \r\n" + + FormatCell(table.FullName, colSpan: totalSpan, style: "header") + + $" \r\n" + + $" \r\n" + + $" \r\n" + + $" \r\n" + + $" \r\n"); + foreach (var (name, span) in table.ColumnSpans) { + await Content.WriteAsync(FormatCell(name, colSpan: span, style: "th")); + } + await Content.WriteAsync(" \r\n"); + + foreach (var row in table.GetData()) { + await FormatRow(row); + } + + await Content.WriteAsync(" \r\n"); + } + + protected async Task FormatRow(IEnumerable row) { + if (Content == null) throw new InvalidOperationException(); + var arrays = row.Where(c => c is Array).Cast().Select(c => c.Length).ToArray(); + int rowNum = Math.Max(1, arrays.Length > 0 ? arrays.Max() : 0); + for (int i = 0; i < rowNum; i++) { + await Content.WriteAsync(" \r\n"); + foreach (var data in row) { + if (data is Array a) { + await Content.WriteAsync(i < a.Length ? FormatCell(a.GetValue(i)) : " \r\n"); + } else { + await Content.WriteAsync(i == 0 ? FormatCell(data, rowNum) : " \r\n"); + } + } + await Content.WriteAsync(" \r\n"); + } + } + + protected static string FormatCell(object? data, int rowSpan = 1, int colSpan = 1, string? style = "default", bool forceString = false) { + if (data?.GetType().IsValueType == true && data.GetType().Name.StartsWith("ValueTuple")) + return string.Join("", data.GetType().GetFields().Select(p => FormatCell(p.GetValue(data), rowSpan, colSpan, style, forceString))); + + var add = (style != null ? $" table:style-name=\"{style}\"" : "") + (rowSpan > 1 || colSpan > 1 ? $" table:number-rows-spanned=\"{rowSpan}\" table:number-columns-spanned=\"{colSpan}\"" : ""); + string c; + if (!forceString && data == null) { + c = $""; + } else if (!forceString && (data is float || data is double || data is byte || data is char || + data is short || data is ushort || data is int || data is uint || data is long || data is ulong)) { + c = $"{data}"; + } else { + c = $"{data}"; + } + + return c = $" {c}\r\n" + (colSpan > 1 ? $" \r\n" : ""); + } + } +} diff --git a/Elwig/Models/Dtos/DataTable.cs b/Elwig/Models/Dtos/DataTable.cs index b594490..9a95fd1 100644 --- a/Elwig/Models/Dtos/DataTable.cs +++ b/Elwig/Models/Dtos/DataTable.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -9,15 +10,27 @@ namespace Elwig.Models.Dtos { public string FullName { get; set; } public IEnumerable Rows { get; private set; } public int RowNum => Rows.Count(); - public IEnumerable ColumnNames => _fieldMap.Select(m => m.Item2); + public IEnumerable<(string, Type?)> ColumnDefs => _map.Select(m => (m.Item1, m.Item2?.PropertyType ?? m.Item3?.FieldType)); + public IEnumerable ColumnNames => ColumnDefs.Select(m => m.Item1); + public IEnumerable ColumnTypes => ColumnDefs.Select(m => m.Item2); + public IEnumerable<(string, int)> ColumnSpans => ColumnDefs.Select(c => { + var type = c.Item2; + var elType = type?.GetElementType(); + return (c.Item1, + type != null && type.IsValueType && type.Name.StartsWith("ValueTuple") ? type.GetFields().Length : + type != null && elType != null && type.IsArray && elType.IsValueType && elType.Name.StartsWith("ValueTuple") ? elType.GetFields().Length : 1 + ); + }).ToList(); public int ColNum => ColumnNames.Count(); + private readonly PropertyInfo[] _properties; private readonly FieldInfo[] _fields; - private readonly (FieldInfo, string)[] _fieldMap; + private readonly (string, PropertyInfo?, FieldInfo?)[] _map; public DataTable(string name, string fullName, IEnumerable rows, IEnumerable<(string, string)>? colNames = null) { _fields = typeof(T).GetFields(); - var dict = colNames?.ToDictionary(n => n.Item1, n => n.Item2); - _fieldMap = (dict == null ? _fields.Select(f => (f, f.Name)) : _fields.Select(f => (f, dict[f.Name]))).ToArray(); + _properties = typeof(T).GetProperties(); + colNames ??= _properties.Select(p => p.Name).Union(_fields.Select(f => f.Name)).Select(i => (i, i)).ToList(); + _map = colNames.Select(n => (n.Item2, _properties.FirstOrDefault(p => p?.Name == n.Item1, null), _fields.FirstOrDefault(f => f?.Name == n.Item1, null))).ToArray(); Name = name; FullName = fullName; Rows = rows; @@ -27,7 +40,7 @@ namespace Elwig.Models.Dtos { this(name, name, rows, colNames) { } protected IEnumerable<(string, object?)> GetNamedRowData(T row) { - return _fieldMap.Select(i => (i.Item2, i.Item1.GetValue(row))); + return _map.Select(i => (i.Item1, i.Item2?.GetValue(row) ?? i.Item3?.GetValue(row))); } protected IEnumerable GetRowData(T row) { diff --git a/Elwig/Models/Dtos/DeliveryConfirmationData.cs b/Elwig/Models/Dtos/DeliveryConfirmationData.cs index 3177b14..5be0325 100644 --- a/Elwig/Models/Dtos/DeliveryConfirmationData.cs +++ b/Elwig/Models/Dtos/DeliveryConfirmationData.cs @@ -7,23 +7,23 @@ using System.Threading.Tasks; namespace Elwig.Models.Dtos { public class DeliveryConfirmationData : DataTable { - private static readonly (string, string)[] _fields = new[] { + private static readonly (string, string)[] _fieldNames = new[] { ("LsNr", "LsNr."), ("DPNr", "Pos."), ("Variant", "Sorte"), ("Attribute", "Attribut"), ("Modifiers", "Zu-/Abschläge"), ("QualityLevel", "Qualitätsstufe"), - ("GradationOe", "°Oe"), - ("GradationKmw", "°KMW"), - ("Commitment", "Flächenbindung"), + ("Oe", "°Oe"), + ("Kmw", "°KMW"), + ("Buckets", "Flächenbindung"), ("Weight", "Gewicht"), }; public int MgNr { get; private set; } private DeliveryConfirmationData(IEnumerable rows, int mgnr) : - base("Anl.-Best.", "Anlieferungsbestätigung", rows, _fields) { + base("Anl.-Best.", "Anlieferungsbestätigung", rows, _fieldNames) { MgNr = mgnr; } @@ -61,16 +61,16 @@ namespace Elwig.Models.Dtos { public class DeliveryConfirmationRow { - public string LsNr { get; set; } - public int DPNr { get; set; } - public string Variant { get; set; } - public string? Attribute { get; set; } - public string QualityLevel { get; set; } - public double Oe { get; set; } - public double Kmw { get; set; } - public string[] Modifiers { get; set; } - public int Weight { get; set; } - public (string, int)[] Buckets { get; set; } + public string LsNr; + public int DPNr; + public string Variant; + public string? Attribute; + public string QualityLevel; + public double Oe; + public double Kmw; + public string[] Modifiers; + public int Weight; + public (string, int)[] Buckets; public DeliveryConfirmationRow(DeliveryPart p) { var d = p.Delivery; diff --git a/Elwig/Windows/TestWindow.xaml b/Elwig/Windows/TestWindow.xaml index 0d4442e..bd6cd4f 100644 --- a/Elwig/Windows/TestWindow.xaml +++ b/Elwig/Windows/TestWindow.xaml @@ -18,6 +18,8 @@