From 000d0547345e782b7528164b002801fe5e79d298 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:12:16 +0200 Subject: [PATCH 01/12] - goto/jumpmark --- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 34 ++++++++--------- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 38 +++++++++---------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index edd0117e..afea2257 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -47,27 +47,27 @@ private async Task CreateSheetXmlAsync(object value, string sheetPath, Cancellat if (value == null) { await WriteEmptySheetAsync(writer); - goto End; //for re-using code } - - //DapperRow - - switch (value) + else { - case IDataReader dataReader: - await GenerateSheetByIDataReaderAsync(writer, dataReader); - break; - case IEnumerable enumerable: - await GenerateSheetByEnumerableAsync(writer, enumerable); - break; - case DataTable dataTable: - await GenerateSheetByDataTableAsync(writer, dataTable); - break; - default: - throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); + //DapperRow + + switch (value) + { + case IDataReader dataReader: + await GenerateSheetByIDataReaderAsync(writer, dataReader); + break; + case IEnumerable enumerable: + await GenerateSheetByEnumerableAsync(writer, enumerable); + break; + case DataTable dataTable: + await GenerateSheetByDataTableAsync(writer, dataTable); + break; + default: + throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); + } } } - End: //for re-using code _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 07a7e267..18915bc8 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -79,29 +79,29 @@ private void CreateSheetXml(object value, string sheetPath) if (value == null) { WriteEmptySheet(writer); - goto End; //for re-using code - } - - //DapperRow - - if (value is IDataReader) - { - GenerateSheetByIDataReader(writer, value as IDataReader); - } - else if (value is IEnumerable) - { - GenerateSheetByEnumerable(writer, value as IEnumerable); - } - else if (value is DataTable) - { - GenerateSheetByDataTable(writer, value as DataTable); } else { - throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); + //DapperRow + + if (value is IDataReader) + { + GenerateSheetByIDataReader(writer, value as IDataReader); + } + else if (value is IEnumerable) + { + GenerateSheetByEnumerable(writer, value as IEnumerable); + } + else if (value is DataTable) + { + GenerateSheetByDataTable(writer, value as DataTable); + } + else + { + throw new NotImplementedException($"Type {value.GetType().FullName} is not implemented. Please open an issue."); + } } } - End: //for re-using code _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); } @@ -454,7 +454,7 @@ private int GenerateSheetByColumnInfo(MiniExcelStreamWriter writer, IEnumerat return yIndex - 1; } - + private void WriteCell(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo) { var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); From dee7364e38325867037096839702d747a498aaff Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Mon, 10 Jun 2024 21:27:13 +0200 Subject: [PATCH 02/12] + generate numberformats --- .../Attributes/ExcelColumnAttribute.cs | 3 +++ src/MiniExcel/OpenXml/Constants/ExcelXml.cs | 22 +++++++++++++++++-- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 2 +- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 9 ++++---- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 20 +++++++++++++++-- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs index d7337418..f6cb074d 100644 --- a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs @@ -9,6 +9,8 @@ public class ExcelColumnAttribute : Attribute private int _index = -1; private string _xName; + internal int FormatId { get; set; } + public string Name { get; set; } public string[] Aliases { get; set; } @@ -52,6 +54,7 @@ private void Init(int index, string columnName = null) public class DynamicExcelColumn : ExcelColumnAttribute { public string Key { get; set; } + public DynamicExcelColumn(string key) { Key = key; diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs index a4efba29..7afdfc3b 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs @@ -1,4 +1,8 @@ -using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.Attributes; +using MiniExcelLibs.OpenXml.Models; +using System.Collections.Generic; +using System.Linq; +using System.Text; namespace MiniExcelLibs.OpenXml.Constants { @@ -52,10 +56,12 @@ static ExcelXml() "; - internal static readonly string DefaultStylesXml = @" + internal const string NumFmtsToken = "{{numFmts}}"; + internal static readonly string DefaultStylesXml = $@" + {NumFmtsToken} @@ -231,5 +237,17 @@ internal static string DrawingXml(FileDto file, int fileIndex) internal static string Sheet(SheetDto sheetDto, int sheetId) => $@""; + + internal static string SetupStyleXml(string styleXml, ICollection dynamicColumns = null) + { + var sb = new StringBuilder(styleXml); + sb.Replace(NumFmtsToken, string.Join(string.Empty, dynamicColumns.Select(toNumFmt))); + return sb.ToString(); + + string toNumFmt(ExcelColumnAttribute col, int index) + { + return $@""; + } + } } } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index afea2257..a6569b0a 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -479,7 +479,7 @@ private async Task AddFilesToZipAsync(CancellationToken cancellationToken) /// private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) { - var styleXml = GetStylesXml(); + var styleXml = GetStylesXml(_configuration.DynamicColumns); await CreateZipEntryAsync( ExcelFileNames.Styles, diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 16ead3ae..ad4f7a68 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -1,4 +1,5 @@ -using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.Attributes; +using MiniExcelLibs.OpenXml.Constants; using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.Utils; using MiniExcelLibs.Zip; @@ -375,14 +376,14 @@ private string GetDimensionRef(int maxRowIndex, int maxColumnIndex) return dimensionRef; } - private string GetStylesXml() + private string GetStylesXml(ICollection columns) { switch (_configuration.TableStyles) { case TableStyles.None: - return ExcelXml.NoneStylesXml; + return ExcelXml.SetupStyleXml(ExcelXml.NoneStylesXml, columns); case TableStyles.Default: - return ExcelXml.DefaultStylesXml; + return ExcelXml.SetupStyleXml(ExcelXml.DefaultStylesXml, columns); default: return string.Empty; } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 18915bc8..1298d356 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -1,4 +1,5 @@ -using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.Attributes; +using MiniExcelLibs.OpenXml.Constants; using MiniExcelLibs.OpenXml.Models; using MiniExcelLibs.Utils; using MiniExcelLibs.Zip; @@ -51,6 +52,8 @@ public void SaveAs() { GenerateDefaultOpenXml(); + GenerateStyleIds(_configuration.DynamicColumns); + var sheets = GetSheets(); foreach (var sheet in sheets) @@ -64,6 +67,19 @@ public void SaveAs() _archive.Dispose(); } + private void GenerateStyleIds(DynamicExcelColumn[] dynamicColumns) + { + const int startIndex = 1000; + int index = 0; + foreach(var g in dynamicColumns.GroupBy(x => x.Format)) + { + foreach (var col in g) + col.FormatId = startIndex + index; + + index++; + } + } + internal void GenerateDefaultOpenXml() { CreateZipEntry(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels); @@ -508,7 +524,7 @@ private void AddFilesToZip() /// private void GenerateStylesXml() { - var styleXml = GetStylesXml(); + var styleXml = GetStylesXml(_configuration.DynamicColumns); CreateZipEntry(ExcelFileNames.Styles, ExcelContentTypes.Styles, styleXml); } From dc73c6d998f855563b9a3ffb2b7b1f18ffc94724 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:41:42 +0200 Subject: [PATCH 03/12] + `FormatId` --- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 35 ++++++++++++++++--- src/MiniExcel/Utils/CustomPropertyHelper.cs | 8 +++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index ad4f7a68..e4fa6c3c 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -109,6 +109,7 @@ private ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string columnName if (dynamicColumn.Format != null) { prop.ExcelFormat = dynamicColumn.Format; + prop.ExcelFormatId = dynamicColumn.FormatId; } if (dynamicColumn.Aliases != null) @@ -159,14 +160,40 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str)); } - if (columnInfo?.ExcelFormat != null && value is IFormattable formattableValue) + var type = GetValueType(value, columnInfo); + + if (type != typeof(DateTime) && columnInfo?.ExcelFormat != null && value is IFormattable formattableValue) { var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr)); } - var type = GetValueType(value, columnInfo); + if (type == typeof(DateTime)) + { + return GetDateTimeValue(value, columnInfo); + } + +#if NET6_0_OR_GREATER + if (type == typeof(DateOnly)) + { + if (_configuration.Culture != CultureInfo.InvariantCulture) + { + var cellValue = ((DateOnly)value).ToString(_configuration.Culture); + return Tuple.Create("2", "str", cellValue); + } + + if (columnInfo == null || columnInfo.ExcelFormat == null) + { + var oaDate = CorrectDateTimeValue((DateTime)value); + var cellValue = oaDate.ToString(CultureInfo.InvariantCulture); + return Tuple.Create("3", null, cellValue); + } + // TODO: now it'll lose date type information + var formattedCellValue = ((DateOnly)value).ToString(columnInfo.ExcelFormat, _configuration.Culture); + return Tuple.Create("2", "str", formattedCellValue); + } +#endif if (type.IsEnum) { var description = CustomPropertyHelper.DescriptionAttr(type, value); @@ -341,9 +368,7 @@ private Tuple GetDateTimeValue(object value, ExcelColumn return Tuple.Create("3", null, cellValue); } - // TODO: now it'll lose date type information - var formattedCellValue = ((DateTime)value).ToString(columnInfo.ExcelFormat, _configuration.Culture); - return Tuple.Create("2", "str", formattedCellValue); + return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, ((DateTime)value).ToOADate().ToString()); } private static double CorrectDateTimeValue(DateTime value) diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index 7fa06114..12ad19eb 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -23,6 +23,7 @@ internal class ExcelColumnInfo public double? ExcelColumnWidth { get; internal set; } public string ExcelIndexName { get; internal set; } public bool ExcelIgnore { get; internal set; } + public int ExcelFormatId { get; internal set; } } internal class ExcellSheetInfo @@ -178,7 +179,6 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro var gt = Nullable.GetUnderlyingType(p.PropertyType); var excelColumnName = p.GetAttribute(); var excludeNullableType = gt ?? p.PropertyType; - var excelFormat = p.GetAttribute()?.Format; var excelColumn = p.GetAttribute(); if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0) { @@ -204,7 +204,8 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro ExcelColumnIndex = p.GetAttribute()?.ExcelColumnIndex ?? excelColumnIndex, ExcelIndexName = p.GetAttribute()?.ExcelXName ?? excelColumn?.IndexName, ExcelColumnWidth = p.GetAttribute()?.ExcelColumnWidth ?? excelColumn?.Width, - ExcelFormat = excelFormat ?? excelColumn?.Format, + ExcelFormat = excelColumn?.Format, + ExcelFormatId = excelColumn?.Format == null ? excelColumn.FormatId : 0 }; }).Where(_ => _ != null); } @@ -292,7 +293,10 @@ internal static void SetDictionaryColumnInfo(List _props, objec p.Nullable = true; //p.ExcludeNullableType = item2[key]?.GetType(); if (dynamicColumn.Format != null) + { p.ExcelFormat = dynamicColumn.Format; + p.ExcelFormatId = dynamicColumn.FormatId; + } if (dynamicColumn.Aliases != null) p.ExcelColumnAliases = dynamicColumn.Aliases; if (dynamicColumn.IndexName != null) From e9372250b730ed3dc396f18444bff2e46d3134f9 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:58:23 +0200 Subject: [PATCH 04/12] ~ first working shot --- src/MiniExcel/OpenXml/Constants/ExcelXml.cs | 36 +++++++++++++++---- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 7 ++-- .../OpenXml/ExcelOpenXmlSheetWriter.cs | 6 ++-- src/MiniExcel/OpenXml/Models/StyleDto.cs | 8 +++++ tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 18 ++++++++-- 5 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 src/MiniExcel/OpenXml/Models/StyleDto.cs diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs index 7afdfc3b..836d642a 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs @@ -1,5 +1,6 @@ using MiniExcelLibs.Attributes; using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.Utils; using System.Collections.Generic; using System.Linq; using System.Text; @@ -57,9 +58,12 @@ static ExcelXml() "; internal const string NumFmtsToken = "{{numFmts}}"; + internal const string NumFmtsCountToken = "{{numFmtCount}}"; + internal const string cellXfsToken = "{{cellXfs}}"; + internal const string cellXfsCountToken = "{{cellXfsCount}}"; internal static readonly string DefaultStylesXml = $@" - + {NumFmtsToken} @@ -139,7 +143,7 @@ static ExcelXml() "; - internal const string NumFmtsToken = "{{numFmts}}"; - internal const string NumFmtsCountToken = "{{numFmtCount}}"; - internal const string cellXfsToken = "{{cellXfs}}"; - internal const string cellXfsCountToken = "{{cellXfsCount}}"; + #region StyleSheet + + private const int startUpNumFmts = 1; + private const string NumFmtsToken = "{{numFmts}}"; + private const string NumFmtsCountToken = "{{numFmtCount}}"; + + private const int startUpCellXfs = 5; + private const string cellXfsToken = "{{cellXfs}}"; + private const string cellXfsCountToken = "{{cellXfsCount}}"; + internal static readonly string DefaultStylesXml = $@" @@ -167,6 +173,8 @@ static ExcelXml() "; + #endregion + internal static readonly string DefaultWorkbookXml = @" @@ -245,31 +253,46 @@ internal static string Sheet(SheetDto sheetDto, int sheetId) internal static string SetupStyleXml(string styleXml, ICollection columns) { - var sb = new StringBuilder(styleXml); - var columnsToApply = columns - .Where(x => !string.IsNullOrWhiteSpace(x.Format)) - .Select(x => new { exf = new ExcelNumberFormat(x.Format), f = x }) - .Where(x => x.exf.IsValid) - .GroupBy(x => x.f.FormatId) - .Select(x => x.First()).ToArray(); + const int numFmtIndex = 166; + var sb = new StringBuilder(styleXml); + var columnsToApply = GenerateStyleIds(columns); - var numFmtXmls = columnsToApply.Select((x,i) => $@"").ToArray(); - sb.Replace(NumFmtsToken, string.Join(string.Empty, numFmtXmls)); - sb.Replace(NumFmtsCountToken, (1 + numFmtXmls.Length).ToString()); - - var cellXfsXmls = columnsToApply.Select((x, i) => + var numFmts = columnsToApply.Select((x, i) => { - return -$@" + return new + { + numFmt = +$@"", + + cellXfs = +$@" "; - } - ).ToArray(); - sb.Replace(cellXfsToken, string.Join(string.Empty, cellXfsXmls)); - sb.Replace(cellXfsCountToken, (5 + cellXfsXmls.Length).ToString()); +" + }; + }).ToArray(); + + sb.Replace(NumFmtsToken, string.Join(string.Empty, numFmts.Select(x => x.numFmt))); + sb.Replace(NumFmtsCountToken, (startUpNumFmts + numFmts.Length).ToString()); + + sb.Replace(cellXfsToken, string.Join(string.Empty, numFmts.Select(x => x.cellXfs))); + sb.Replace(cellXfsCountToken, (5 + numFmts.Length).ToString()); return sb.ToString(); } + + private static IEnumerable GenerateStyleIds(ICollection dynamicColumns) + { + int index = 0; + foreach (var g in dynamicColumns.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format)) + { + foreach (var col in g) + col.FormatId = startUpCellXfs + index; + + yield return g.First(); + index++; + } + } + } } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index f88245a3..63bc46cc 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -52,8 +52,6 @@ public void SaveAs() { GenerateDefaultOpenXml(); - GenerateStyleIds(_configuration.DynamicColumns); - var sheets = GetSheets(); foreach (var sheet in sheets) @@ -67,23 +65,11 @@ public void SaveAs() _archive.Dispose(); } - private void GenerateStyleIds(DynamicExcelColumn[] dynamicColumns) - { - const int startIndex = 5; - int index = 0; - foreach(var g in dynamicColumns.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format)) - { - foreach (var col in g) - col.FormatId = startIndex + index; - - index++; - } - } - internal void GenerateDefaultOpenXml() { CreateZipEntry(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels); CreateZipEntry(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString); + GenerateStylesXml(); } private void CreateSheetXml(object value, string sheetPath) @@ -500,8 +486,6 @@ private void GenerateEndXml() { AddFilesToZip(); - GenerateStylesXml(); - GenerateDrawinRelXml(); GenerateDrawingXml(); From 63832a195617c2ffd4eb6604b9186e3ce8498744 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:01:44 +0200 Subject: [PATCH 06/12] ~ fix last test issues --- src/MiniExcel/Attributes/ExcelColumnAttribute.cs | 2 +- src/MiniExcel/OpenXml/Constants/ExcelXml.cs | 5 ++++- .../OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 4 +++- src/MiniExcel/Utils/CustomPropertyHelper.cs | 5 +++-- tests/MiniExcelTests/MiniExcelIssueTests.cs | 4 ++-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs index f6cb074d..965f0579 100644 --- a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs @@ -9,7 +9,7 @@ public class ExcelColumnAttribute : Attribute private int _index = -1; private string _xName; - internal int FormatId { get; set; } + internal int FormatId { get; set; } = -1; public string Name { get; set; } diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs index eb4446c5..b1a0d7a5 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs @@ -283,8 +283,11 @@ internal static string SetupStyleXml(string styleXml, ICollection GenerateStyleIds(ICollection dynamicColumns) { + if (dynamicColumns == null) + yield break; + int index = 0; - foreach (var g in dynamicColumns.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format)) + foreach (var g in dynamicColumns?.Where(x => !string.IsNullOrWhiteSpace(x.Format) && new ExcelNumberFormat(x.Format).IsValid).GroupBy(x => x.Format)) { foreach (var col in g) col.FormatId = startUpCellXfs + index; diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 9ea905ca..d77c0437 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -162,7 +162,9 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, var type = GetValueType(value, columnInfo); - if (type != typeof(DateTime) && columnInfo?.ExcelFormat != null && value is IFormattable formattableValue) + + + if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue) { var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr)); diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index 12ad19eb..fa7f164f 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -179,6 +179,7 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro var gt = Nullable.GetUnderlyingType(p.PropertyType); var excelColumnName = p.GetAttribute(); var excludeNullableType = gt ?? p.PropertyType; + var excelFormat = p.GetAttribute()?.Format; var excelColumn = p.GetAttribute(); if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0) { @@ -204,8 +205,8 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro ExcelColumnIndex = p.GetAttribute()?.ExcelColumnIndex ?? excelColumnIndex, ExcelIndexName = p.GetAttribute()?.ExcelXName ?? excelColumn?.IndexName, ExcelColumnWidth = p.GetAttribute()?.ExcelColumnWidth ?? excelColumn?.Width, - ExcelFormat = excelColumn?.Format, - ExcelFormatId = excelColumn?.Format == null ? excelColumn.FormatId : 0 + ExcelFormat = excelFormat ?? excelColumn?.Format, + ExcelFormatId = excelColumn?.FormatId ?? -1 }; }).Where(_ => _ != null); } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 220d538f..498fa29f 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -309,7 +309,7 @@ public void TestIssue370() var rows = MiniExcel.Query(path, false).ToList(); Assert.Equal("createdate", rows[0].A); - Assert.Equal("2022-04-12", rows[1].A); + Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); Assert.Equal("name", rows[0].B); Assert.Equal("Jack", rows[1].B); Assert.Equal("Account Point", rows[0].C); @@ -334,7 +334,7 @@ public void TestIssue369() var rows = MiniExcel.Query(path, false).ToList(); Assert.Equal("createdate", rows[0].A); - Assert.Equal("2022-04-12", rows[1].A); + Assert.Equal(new DateTime(2022, 04, 12), rows[1].A); Assert.Equal("name", rows[0].B); Assert.Equal("Jack", rows[1].B); Assert.Equal("Account Point", rows[0].C); From 2bf1bb45163c8a76ff50efbe1e069ae6b9e8568b Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:14:21 +0200 Subject: [PATCH 07/12] + extend tests --- tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 5b42607b..7ced6082 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -1217,12 +1217,15 @@ public void SharedStringNoCacheTest() public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var date = DateTime.Now; var table = new DataTable(); { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(int)); - table.Rows.Add("MiniExcel", 1); - table.Rows.Add("Github", 2); + table.Columns.Add("Column3", typeof(DateTime)); + table.Columns.Add("Column4", typeof(DateTime)); + table.Rows.Add("MiniExcel", 1, date, date); + table.Rows.Add("Github", 2, date, date); } var configuration = new OpenXmlConfiguration @@ -1240,7 +1243,15 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() Name = "Its value", Index = 1, Width = 150 + }, + new DynamicExcelColumn("Column3") + { + Name = "Its Date", + Index = 2, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss", } + } }; var reader = table.CreateDataReader(); @@ -1255,13 +1266,21 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() Assert.Contains("Name of something", rows[0]); Assert.Contains("Its value", rows[0]); + Assert.Contains("Its Date", rows[0]); + Assert.Contains("Column4", rows[0]); Assert.Contains("Name of something", rows[1]); Assert.Contains("Its value", rows[1]); + Assert.Contains("Its Date", rows[1]); + Assert.Contains("Column4", rows[1]); Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal(1D, rows[0]["Its value"]); + Assert.Equal(date, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(date, (DateTime)rows[0]["Column4"], TimeSpan.FromMilliseconds(10d)); Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal(2D, rows[1]["Its value"]); + Assert.Equal(date, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(date, (DateTime)rows[1]["Column4"], TimeSpan.FromMilliseconds(10d)); } } @@ -1274,9 +1293,10 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(int)); - table.Columns.Add("Column3", typeof(DateTime)); - table.Rows.Add("MiniExcel", 1, date); - table.Rows.Add("Github", 2, date); + table.Columns.Add("Column3", typeof(DateTime)); + table.Columns.Add("Column4", typeof(DateTime)); + table.Rows.Add("MiniExcel", 1, date, date); + table.Rows.Add("Github", 2, date, date); } var configuration = new OpenXmlConfiguration @@ -1302,7 +1322,6 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() Width = 150, Format = "dd.mm.yyyy hh:mm:ss" } - } }; @@ -1318,17 +1337,21 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() Assert.Contains("Name of something", rows[0]); Assert.Contains("Its value", rows[0]); Assert.Contains("Its Date", rows[0]); + Assert.Contains("Column4", rows[0]); Assert.Contains("Name of something", rows[1]); Assert.Contains("Its value", rows[1]); Assert.Contains("Its Date", rows[1]); + Assert.Contains("Column4", rows[1]); Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal(1D, rows[0]["Its value"]); Assert.Equal(date, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(date, (DateTime)rows[0]["Column4"], TimeSpan.FromMilliseconds(10d)); Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal(2D, rows[1]["Its value"]); Assert.Equal(date, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(date, (DateTime)rows[1]["Column4"], TimeSpan.FromMilliseconds(10d)); } } } From e548e1aab48d04c4b1f48cd4aa1c37dc2c23d70a Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:25:46 +0200 Subject: [PATCH 08/12] ~ clean up --- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 27 ------------------- src/MiniExcel/OpenXml/Models/StyleDto.cs | 8 ------ 2 files changed, 35 deletions(-) delete mode 100644 src/MiniExcel/OpenXml/Models/StyleDto.cs diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index d77c0437..0901bac9 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -223,33 +223,6 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXML(base64)); } - if (type == typeof(DateTime)) - { - return GetDateTimeValue(value, columnInfo); - } - -#if NET6_0_OR_GREATER - if (type == typeof(DateOnly)) - { - if (_configuration.Culture != CultureInfo.InvariantCulture) - { - var cellValue = ((DateOnly)value).ToString(_configuration.Culture); - return Tuple.Create("2", "str", cellValue); - } - - if (columnInfo == null || columnInfo.ExcelFormat == null) - { - var oaDate = CorrectDateTimeValue((DateTime)value); - var cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - return Tuple.Create("3", null, cellValue); - } - - // TODO: now it'll lose date type information - var formattedCellValue = ((DateOnly)value).ToString(columnInfo.ExcelFormat, _configuration.Culture); - return Tuple.Create("2", "str", formattedCellValue); - } -#endif - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(value.ToString())); } diff --git a/src/MiniExcel/OpenXml/Models/StyleDto.cs b/src/MiniExcel/OpenXml/Models/StyleDto.cs deleted file mode 100644 index a7e64961..00000000 --- a/src/MiniExcel/OpenXml/Models/StyleDto.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace MiniExcelLibs.OpenXml.Models -{ - internal class StyleDto - { - - - } -} \ No newline at end of file From 68f78c9eb2d615ad05ecf05560f6ecc82a72fe40 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:26:20 +0200 Subject: [PATCH 09/12] ~ simplify `DateOnly` handling --- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 0901bac9..3ec3283f 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -161,7 +161,6 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, } var type = GetValueType(value, columnInfo); - if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue) @@ -172,28 +171,13 @@ private Tuple GetCellValue(int rowIndex, int cellIndex, if (type == typeof(DateTime)) { - return GetDateTimeValue(value, columnInfo); + return GetDateTimeValue((DateTime)value, columnInfo); } #if NET6_0_OR_GREATER if (type == typeof(DateOnly)) { - if (_configuration.Culture != CultureInfo.InvariantCulture) - { - var cellValue = ((DateOnly)value).ToString(_configuration.Culture); - return Tuple.Create("2", "str", cellValue); - } - - if (columnInfo == null || columnInfo.ExcelFormat == null) - { - var oaDate = CorrectDateTimeValue((DateTime)value); - var cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - return Tuple.Create("3", null, cellValue); - } - - // TODO: now it'll lose date type information - var formattedCellValue = ((DateOnly)value).ToString(columnInfo.ExcelFormat, _configuration.Culture); - return Tuple.Create("2", "str", formattedCellValue); + return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo); } #endif if (type.IsEnum) @@ -328,25 +312,21 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) return base64; } - private Tuple GetDateTimeValue(object value, ExcelColumnInfo columnInfo) + private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) { + string cellValue = null; if (_configuration.Culture != CultureInfo.InvariantCulture) { - var cellValue = ((DateTime)value).ToString(_configuration.Culture); + cellValue = (value).ToString(_configuration.Culture); return Tuple.Create("2", "str", cellValue); } + var oaDate = CorrectDateTimeValue(value); + cellValue = oaDate.ToString(CultureInfo.InvariantCulture); if (columnInfo == null || columnInfo.ExcelFormat == null) - { - var oaDate = CorrectDateTimeValue((DateTime)value); - var cellValue = oaDate.ToString(CultureInfo.InvariantCulture); return Tuple.Create("3", null, cellValue); - } - { - var oaDate = CorrectDateTimeValue((DateTime)value); - var cellValue = oaDate.ToString(CultureInfo.InvariantCulture); + else return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); - } } private static double CorrectDateTimeValue(DateTime value) From 4b6d0537198f35696309f6a7e70478da7fe428c3 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Wed, 12 Jun 2024 15:37:44 +0200 Subject: [PATCH 10/12] + `DateOnly` to Tests --- tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 7ced6082..5af73bb3 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -1217,15 +1217,16 @@ public void SharedStringNoCacheTest() public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var date = DateTime.Now; + var dateTime = DateTime.Now; + var onlyDate = DateOnly.FromDateTime(dateTime); var table = new DataTable(); { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(int)); table.Columns.Add("Column3", typeof(DateTime)); - table.Columns.Add("Column4", typeof(DateTime)); - table.Rows.Add("MiniExcel", 1, date, date); - table.Rows.Add("Github", 2, date, date); + table.Columns.Add("Column4", typeof(DateOnly)); + table.Rows.Add("MiniExcel", 1, dateTime, onlyDate); + table.Rows.Add("Github", 2, dateTime, onlyDate); } var configuration = new OpenXmlConfiguration @@ -1275,12 +1276,12 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal(1D, rows[0]["Its value"]); - Assert.Equal(date, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); - Assert.Equal(date, (DateTime)rows[0]["Column4"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]); Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal(2D, rows[1]["Its value"]); - Assert.Equal(date, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); - Assert.Equal(date, (DateTime)rows[1]["Column4"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]); } } @@ -1288,15 +1289,16 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var date = DateTime.Now; + var dateTime = DateTime.Now; + var onlyDate = DateOnly.FromDateTime(dateTime); var table = new DataTable(); { table.Columns.Add("Column1", typeof(string)); table.Columns.Add("Column2", typeof(int)); table.Columns.Add("Column3", typeof(DateTime)); - table.Columns.Add("Column4", typeof(DateTime)); - table.Rows.Add("MiniExcel", 1, date, date); - table.Rows.Add("Github", 2, date, date); + table.Columns.Add("Column4", typeof(DateOnly)); + table.Rows.Add("MiniExcel", 1, dateTime, onlyDate); + table.Rows.Add("Github", 2, dateTime, onlyDate); } var configuration = new OpenXmlConfiguration @@ -1346,12 +1348,12 @@ public void DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() Assert.Equal("MiniExcel", rows[0]["Name of something"]); Assert.Equal(1D, rows[0]["Its value"]); - Assert.Equal(date, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); - Assert.Equal(date, (DateTime)rows[0]["Column4"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]); Assert.Equal("Github", rows[1]["Name of something"]); Assert.Equal(2D, rows[1]["Its value"]); - Assert.Equal(date, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); - Assert.Equal(date, (DateTime)rows[1]["Column4"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]); } } } From fb4595dd6ef3882dce8f4b39896f40a25898b65d Mon Sep 17 00:00:00 2001 From: Gary Jia <35099424+jiaguangli@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:18:57 +0800 Subject: [PATCH 11/12] Update ExcelOpenXmlSheetWriter.cs Update GenerateSheetByIDataReader --- src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 63bc46cc..79e211d6 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -153,7 +153,7 @@ private void GenerateSheetByIDataReader(MiniExcelStreamWriter writer, IDataReade for (int i = 0; i < fieldCount; i++) { var cellValue = reader.GetValue(i); - WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: null); + WriteCell(writer, yIndex, xIndex, cellValue, columnInfo: props?.FirstOrDefault(x => x?.ExcelColumnIndex == xIndex - 1)); xIndex++; } writer.Write(WorksheetXml.EndRow); @@ -600,4 +600,4 @@ private void CreateZipEntry(string path, byte[] content) } } } -} \ No newline at end of file +} From 5dec6bd240212c302f07018c3520b75f125273a9 Mon Sep 17 00:00:00 2001 From: DancePanda42 <33690602+DancePanda42@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:57:57 +0200 Subject: [PATCH 12/12] + datetime format for async part --- .../OpenXml/ExcelOpenXmlSheetWriter.Async.cs | 3 +- .../MiniExcelOpenXmlAsyncTests.cs | 145 ++++++++++++++++++ 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs index a6569b0a..80e85e38 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.Async.cs @@ -36,6 +36,7 @@ internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationTo { await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken); await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken); + await GenerateStylesXmlAsync(cancellationToken); } private async Task CreateSheetXmlAsync(object value, string sheetPath, CancellationToken cancellationToken) @@ -455,8 +456,6 @@ private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) { await AddFilesToZipAsync(cancellationToken); - await GenerateStylesXmlAsync(cancellationToken); - await GenerateDrawinRelXmlAsync(cancellationToken); await GenerateDrawingXmlAsync(cancellationToken); diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs index d2cae10e..ecf4fd06 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlAsyncTests.cs @@ -1281,5 +1281,150 @@ await Assert.ThrowsAsync(async () => } }); } + + [Fact] + public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingIDataReader() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var dateTime = DateTime.Now; + var onlyDate = DateOnly.FromDateTime(dateTime); + var table = new DataTable(); + { + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(int)); + table.Columns.Add("Column3", typeof(DateTime)); + table.Columns.Add("Column4", typeof(DateOnly)); + table.Rows.Add("MiniExcel", 1, dateTime, onlyDate); + table.Rows.Add("Github", 2, dateTime, onlyDate); + } + + var configuration = new OpenXmlConfiguration + { + DynamicColumns = new[] + { + new DynamicExcelColumn("Column1") + { + Name = "Name of something", + Index = 0, + Width = 150 + }, + new DynamicExcelColumn("Column2") + { + Name = "Its value", + Index = 1, + Width = 150 + }, + new DynamicExcelColumn("Column3") + { + Name = "Its Date", + Index = 2, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss", + } + + } + }; + var reader = table.CreateDataReader(); + + await MiniExcel.SaveAsAsync(path, reader, configuration: configuration); + + using (var stream = File.OpenRead(path)) + { + var rows = stream.Query(useHeaderRow: true) + .Select(x => (IDictionary)x) + .ToList(); + + Assert.Contains("Name of something", rows[0]); + Assert.Contains("Its value", rows[0]); + Assert.Contains("Its Date", rows[0]); + Assert.Contains("Column4", rows[0]); + Assert.Contains("Name of something", rows[1]); + Assert.Contains("Its value", rows[1]); + Assert.Contains("Its Date", rows[1]); + Assert.Contains("Column4", rows[1]); + + Assert.Equal("MiniExcel", rows[0]["Name of something"]); + Assert.Equal(1D, rows[0]["Its value"]); + Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]); + Assert.Equal("Github", rows[1]["Name of something"]); + Assert.Equal(2D, rows[1]["Its value"]); + Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]); + } + } + + [Fact] + public async Task DynamicColumnsConfigurationIsUsedWhenCreatingExcelUsingDataTable() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var dateTime = DateTime.Now; + var onlyDate = DateOnly.FromDateTime(dateTime); + var table = new DataTable(); + { + table.Columns.Add("Column1", typeof(string)); + table.Columns.Add("Column2", typeof(int)); + table.Columns.Add("Column3", typeof(DateTime)); + table.Columns.Add("Column4", typeof(DateOnly)); + table.Rows.Add("MiniExcel", 1, dateTime, onlyDate); + table.Rows.Add("Github", 2, dateTime, onlyDate); + } + + var configuration = new OpenXmlConfiguration + { + DynamicColumns = new[] + { + new DynamicExcelColumn("Column1") + { + Name = "Name of something", + Index = 0, + Width = 150 + }, + new DynamicExcelColumn("Column2") + { + Name = "Its value", + Index = 1, + Width = 150 + }, + new DynamicExcelColumn("Column3") + { + Name = "Its Date", + Index = 2, + Width = 150, + Format = "dd.mm.yyyy hh:mm:ss" + } + } + }; + + await MiniExcel.SaveAsAsync(path, table, configuration: configuration); + + using (var stream = File.OpenRead(path)) + { + var rows = stream.Query(useHeaderRow: true) + .Select(x => (IDictionary)x) + .Select(x => (IDictionary)x) + .ToList(); + + Assert.Contains("Name of something", rows[0]); + Assert.Contains("Its value", rows[0]); + Assert.Contains("Its Date", rows[0]); + Assert.Contains("Column4", rows[0]); + Assert.Contains("Name of something", rows[1]); + Assert.Contains("Its value", rows[1]); + Assert.Contains("Its Date", rows[1]); + Assert.Contains("Column4", rows[1]); + + + Assert.Equal("MiniExcel", rows[0]["Name of something"]); + Assert.Equal(1D, rows[0]["Its value"]); + Assert.Equal(dateTime, (DateTime)rows[0]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[0]["Column4"]); + Assert.Equal("Github", rows[1]["Name of something"]); + Assert.Equal(2D, rows[1]["Its value"]); + Assert.Equal(dateTime, (DateTime)rows[1]["Its Date"], TimeSpan.FromMilliseconds(10d)); + Assert.Equal(onlyDate.ToDateTime(TimeOnly.MinValue), (DateTime)rows[1]["Column4"]); + } + } + } } \ No newline at end of file