From 1ca486b5e76515b8ff0d4bc44a97f055686d87ef Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 17 Mar 2025 23:38:10 +0100 Subject: [PATCH 1/5] Added exception to warn for sheet name too long - When trying to save an excel file with a sheet name of more than 31 charachters in length the program now throws an exception instead of writing a potentially broken document - Some test and code cleanup --- src/MiniExcel/MiniExcel.cs | 2 + src/MiniExcel/Utils/ColumnHelper.cs | 40 ++--- tests/MiniExcelTests/MiniExcelIssueTests.cs | 183 +++++++++++--------- 3 files changed, 120 insertions(+), 105 deletions(-) diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 818026b2..cae14730 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -74,6 +74,8 @@ public static int[] SaveAs(string path, object value, bool printHeader = true, s public static int[] SaveAs(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null) { + if (sheetName.Length > 31 && excelType == ExcelType.XLSX) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); return ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAs(); } diff --git a/src/MiniExcel/Utils/ColumnHelper.cs b/src/MiniExcel/Utils/ColumnHelper.cs index 195210ff..8c2d4f8f 100644 --- a/src/MiniExcel/Utils/ColumnHelper.cs +++ b/src/MiniExcel/Utils/ColumnHelper.cs @@ -1,17 +1,19 @@ -namespace MiniExcelLibs.Utils +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; + +namespace MiniExcelLibs.Utils { - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.IO; // For Row/Column Index - internal static partial class ColumnHelper + internal static class ColumnHelper { private const int GENERAL_COLUMN_INDEX = 255; private const int MAX_COLUMN_INDEX = 16383; private static int _IntMappingAlphabetCount = 0; private static readonly ConcurrentDictionary _IntMappingAlphabet = new ConcurrentDictionary(); private static readonly ConcurrentDictionary _AlphabetMappingInt = new ConcurrentDictionary(); + static ColumnHelper() { _IntMappingAlphabetCount = _IntMappingAlphabet.Count; @@ -21,9 +23,8 @@ static ColumnHelper() public static string GetAlphabetColumnName(int columnIndex) { CheckAndSetMaxColumnIndex(columnIndex); - if (_IntMappingAlphabet.TryGetValue(columnIndex, out var value)) - return value; - throw new KeyNotFoundException(); + return _IntMappingAlphabet.TryGetValue(columnIndex, out var value) ? value + : throw new KeyNotFoundException(); } public static int GetColumnIndex(string columnName) @@ -35,23 +36,23 @@ public static int GetColumnIndex(string columnName) private static void CheckAndSetMaxColumnIndex(int columnIndex) { - if (columnIndex >= _IntMappingAlphabetCount) + if (columnIndex < _IntMappingAlphabetCount) + return; + if (columnIndex > MAX_COLUMN_INDEX) + throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index."); + + for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++) { - if (columnIndex > MAX_COLUMN_INDEX) - throw new InvalidDataException($"ColumnIndex {columnIndex} over excel vaild max index."); - for (int i = _IntMappingAlphabet.Count; i <= columnIndex; i++) - { - _IntMappingAlphabet.AddOrUpdate(i, IntToLetters(i), (a, b) => IntToLetters(i)); - _AlphabetMappingInt.AddOrUpdate(IntToLetters(i), i, (a, b) => i); - } - _IntMappingAlphabetCount = _IntMappingAlphabet.Count; + _IntMappingAlphabet.AddOrUpdate(i, IntToLetters(i), (a, b) => IntToLetters(i)); + _AlphabetMappingInt.AddOrUpdate(IntToLetters(i), i, (a, b) => i); } + _IntMappingAlphabetCount = _IntMappingAlphabet.Count; } internal static string IntToLetters(int value) { - value = value + 1; - string result = string.Empty; + value++; + var result = string.Empty; while (--value >= 0) { result = (char)('A' + value % 26) + result; @@ -60,5 +61,4 @@ internal static string IntToLetters(int value) return result; } } - } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 76a7a1a2..679e65d9 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -5,7 +5,6 @@ using MiniExcelLibs.OpenXml; using MiniExcelLibs.Tests.Utils; using Newtonsoft.Json; -using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using OfficeOpenXml; using System; @@ -26,7 +25,7 @@ namespace MiniExcelLibs.Tests { - public partial class MiniExcelIssueTests + public class MiniExcelIssueTests { private readonly ITestOutputHelper _output; @@ -41,7 +40,8 @@ public MiniExcelIssueTests(ITestOutputHelper output) [Fact] public void TestIssue549() { - var data = new[] { + var data = new[] + { new{id=1,name="jack"}, new{id=2,name="mike"}, }; @@ -51,8 +51,8 @@ public void TestIssue549() { using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { - XSSFWorkbook workbook = new XSSFWorkbook(stream); - ISheet sheet = workbook.GetSheetAt(0); + using var workbook = new XSSFWorkbook(stream); + var sheet = workbook.GetSheetAt(0); var a2 = sheet.GetRow(1).GetCell(0); var b2 = sheet.GetRow(1).GetCell(1); Assert.Equal((string)rows[0].id.ToString(), a2.NumericCellValue.ToString()); @@ -67,14 +67,14 @@ public void TestIssue24020201() { var path = PathHelper.GetTempFilePath(); var templatePath = PathHelper.GetFile("xlsx/TestIssue24020201.xlsx"); - var data = new Dictionary() + var data = new Dictionary { ["title"] = "This's title", - ["B"] = new List>() + ["B"] = new List> { - new Dictionary(){ { "specialMark", 1 } }, - new Dictionary(){ { "specialMark", 2 } }, - new Dictionary(){ { "specialMark", 3 } }, + new() { { "specialMark", 1 } }, + new() { { "specialMark", 2 } }, + new() { { "specialMark", 3 } }, } }; MiniExcel.SaveAsByTemplate(path, templatePath, data); @@ -88,9 +88,12 @@ public void TestIssue553() var templatePath = PathHelper.GetFile("xlsx/TestIssue553.xlsx"); var data = new { - B = new[] { - new{ ITM=1 },new{ ITM=2 }, new{ ITM=3 }, - } + B = new[] + { + new{ ITM=1 }, + new{ ITM=2 }, + new{ ITM=3 } + } }; MiniExcel.SaveAsByTemplate(path, templatePath, data); } @@ -111,11 +114,12 @@ public void TestPR10() public void TestIssue289() { var path = PathHelper.GetTempFilePath(); - var value = new[] { - new Issue289Dto { Name="0001", UserType=Issue289Type.V1 }, - new Issue289Dto { Name="0002", UserType=Issue289Type.V2 }, - new Issue289Dto { Name="0003", UserType=Issue289Type.V3 }, - }; + Issue289Dto[] value = + [ + new() { Name="0001", UserType=Issue289Type.V1 }, + new() { Name="0002", UserType=Issue289Type.V2 }, + new() { Name="0003", UserType=Issue289Type.V3 } + ]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); @@ -149,13 +153,14 @@ public void TestIssueI4X92G() { var path = PathHelper.GetTempFilePath("csv"); { - var value = new[] { + var value = new[] + { new { ID=1,Name ="Jack",InDate=new DateTime(2021,01,03)}, new { ID=2,Name ="Henry",InDate=new DateTime(2020,05,03)}, }; MiniExcel.SaveAs(path, value); var content = File.ReadAllText(path); - Assert.Contains(""" + Assert.Equal(""" ID,Name,InDate 1,Jack,"2021-01-03 00:00:00" 2,Henry,"2020-05-03 00:00:00" @@ -224,22 +229,20 @@ public class TestIssue430Dto [Fact] public void TestIssue_DataReaderSupportDimension() { - { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } - var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); - var config = new OpenXmlConfiguration() { FastMode = true }; - MiniExcel.SaveAs(path, reader, configuration: config); - var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); - Assert.Contains("", xml); - Assert.Contains("", xml); - } + using var table = new DataTable(); + + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; + using var reader = table.CreateDataReader(); + var config = new OpenXmlConfiguration { FastMode = true }; + MiniExcel.SaveAs(path, reader, configuration: config); + var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); + Assert.Contains("", xml); + Assert.Contains("", xml); } /// @@ -252,9 +255,10 @@ public void TestIssue413() var path = PathHelper.GetTempFilePath(); var value = new { - list = new List>{ - new Dictionary{ { "id","001"},{ "time",new DateTime(2022,12,25)} }, - new Dictionary{ { "id","002"},{ "time",new DateTime(2022,9,23)} }, + list = new List> + { + new() { { "id","001"},{ "time",new DateTime(2022,12,25)} }, + new() { { "id","002"},{ "time",new DateTime(2022,9,23)} }, } }; var templatePath = PathHelper.GetFile("xlsx/TestIssue413.xlsx"); @@ -286,17 +290,13 @@ public void TestIssue405() [Fact] public void TestIssueI57WMM() { - var sheets = new[] { - new Dictionary(){ - {"ID","0001"},{"Name","Jack"} - } - }; + Dictionary[] sheets = [ new() { ["ID"] = "0001", ["Name"] = "Jack" } ]; var stream = new MemoryStream(); stream.SaveAs(sheets, excelType: ExcelType.CSV); stream.Position = 0; // convert stream to string - StreamReader reader = new StreamReader(stream); + using var reader = new StreamReader(stream); string text = reader.ReadToEnd(); var expected = "ID,Name\r\n0001,Jack\r\n"; Assert.Equal(expected, text); @@ -359,7 +359,7 @@ public void TestIssue369() public void TestIssueI4ZYUU() { var path = PathHelper.GetTempPath(); - var value = new TestIssueI4ZYUUDto[] { new() { MyProperty = "1", MyProperty2 = new DateTime(2022, 10, 15) } }; + TestIssueI4ZYUUDto[] value = [new() { MyProperty = "1", MyProperty2 = new DateTime(2022, 10, 15) }]; MiniExcel.SaveAs(path, value); var rows = MiniExcel.Query(path).ToList(); Assert.Equal("2022-10", rows[1].B); @@ -416,41 +416,38 @@ public void TestIssue117() public void TestIssue352() { { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - table.Rows.Add(1, "Jack"); - table.Rows.Add(2, "Mike"); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + table.Rows.Add(1, "Jack"); + table.Rows.Add(2, "Mike"); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader, false); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; } { - DataTable table = new DataTable(); - { - table.Columns.Add("id", typeof(int)); - table.Columns.Add("name", typeof(string)); - } + using var table = new DataTable(); + table.Columns.Add("id", typeof(int)); + table.Columns.Add("name", typeof(string)); + var path = Path.GetTempPath() + Guid.NewGuid() + ".xlsx"; - DataTableReader reader = table.CreateDataReader(); + var reader = table.CreateDataReader(); MiniExcel.SaveAs(path, reader); var xml = Helpers.GetZipFileContent(path, "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "").Count; @@ -1420,10 +1417,13 @@ public void TestIssue292() MiniExcel.ConvertXlsxToCsv(xlsxPath, csvPath); var actualCotent = File.ReadAllText(csvPath); - Assert.Equal(@"Name,Age,Name,Age -Jack,22,Mike,25 -Henry,44,Jerry,44 -", actualCotent); + Assert.Equal( + """ + Name,Age,Name,Age + Jack,22,Mike,25 + Henry,44,Jerry,44 + + """, actualCotent); } { @@ -1463,13 +1463,13 @@ public void TestIssue293() public void TestIssueI49RYZ() { { - var values = new[] - { - new I49RYZDto(){Name="Jack",UserType=I49RYZUserType.V1}, - new I49RYZDto(){Name="Leo",UserType=I49RYZUserType.V2}, - new I49RYZDto(){Name="Henry",UserType=I49RYZUserType.V3}, - new I49RYZDto(){Name="Lisa",UserType=null}, - }; + I49RYZDto[] values = + [ + new() {Name="Jack",UserType=I49RYZUserType.V1}, + new() {Name="Leo",UserType=I49RYZUserType.V2}, + new() {Name="Henry",UserType=I49RYZUserType.V3}, + new() {Name="Lisa",UserType=null} + ]; var path = PathHelper.GetTempPath(); MiniExcel.SaveAs(path, values); var rows = MiniExcel.Query(path, true).ToList(); @@ -1795,7 +1795,7 @@ public class IssueI3X2ZLDTO public void TestIssue261() { var csvPath = PathHelper.GetFile("csv/TestCsvToXlsx.csv"); - var xlsxPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".xlsx"); + var xlsxPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); CsvToXlsx(csvPath, xlsxPath); var rows = MiniExcel.Query(xlsxPath).ToList(); Assert.Equal("Name", rows[0].A); @@ -2821,7 +2821,7 @@ public void Issue87() [Fact] public void Issue208() { - var path = @"../../../../../samples/xlsx/TestIssue208.xlsx"; + var path = "../../../../../samples/xlsx/TestIssue208.xlsx"; var columns = MiniExcel.GetColumns(path).ToList(); Assert.Equal(16384, columns.Count); Assert.Equal("XFD", columns[16383]); @@ -2835,7 +2835,7 @@ public void Issue206() { { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var templatePath = @"../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; + var templatePath = "../../../../../samples/xlsx/TestTemplateBasicIEmumerableFill.xlsx"; var dt = new DataTable(); { @@ -3886,6 +3886,21 @@ static IEnumerable GetTestData() } } + [Fact] + public void Test_Issue_693_SaveSheetWithLongName() + { + var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + try + { + List> data = [ new() { ["First"] = 1, ["Second"] = 2 }]; + Assert.Throws(() => MiniExcel.SaveAs(path, data, sheetName:"Some Really Looooooooooong Sheet Name")); + } + finally + { + File.Delete(path); + } + } + [Fact] public void Issue_710() { @@ -3893,11 +3908,10 @@ public void Issue_710() using var memoryStream = new MemoryStream(); memoryStream.SaveAs(values, configuration: new OpenXmlConfiguration { - FastMode = true, + FastMode = true }); memoryStream.Position = 0; - var dataReader = memoryStream.GetReader(useHeaderRow: false); dataReader.Read(); @@ -3905,7 +3919,6 @@ public void Issue_710() for (int i = 0; i < dataReader.FieldCount; i++) { var columnName = dataReader.GetName(i); - var ordinal = dataReader.GetOrdinal(columnName); Assert.Equal(i, ordinal); From 462812dd1488a3ee034d015d0ae2523af80452cc Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 17 Mar 2025 23:49:29 +0100 Subject: [PATCH 2/5] Added options for trimming column names and ignoring empty rows - Added options TrimColumnNames and IgnoreEmptyRows to OpenXmlConfiguration. The first one allows properties to be mapped to column names even if they have whitespace or newline padding, the second one skips rows whose values are all null instead of instantiating a class with all values set to default (in the case of strong type mapping) - Added tests and samples for afermonetioned options - Refactored the ExcelOpenXmlSheetReader logic that reads the headers of an excel file and moved it to the CustomPropertyHelper class - General code and test cleanup --- samples/xlsx/TestIssue697.xlsx | Bin 0 -> 5631 bytes samples/xlsx/TestTrimColumnNames.xlsx | Bin 0 -> 6630 bytes .../OpenXml/ExcelOpenXmlSheetReader.cs | 350 ++++++++---------- src/MiniExcel/OpenXml/OpenXmlConfiguration.cs | 2 + src/MiniExcel/Utils/CustomPropertyHelper.cs | 186 +++++----- tests/MiniExcelTests/MiniExcelIssueTests.cs | 18 + tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 197 +++++----- 7 files changed, 382 insertions(+), 371 deletions(-) create mode 100644 samples/xlsx/TestIssue697.xlsx create mode 100644 samples/xlsx/TestTrimColumnNames.xlsx diff --git a/samples/xlsx/TestIssue697.xlsx b/samples/xlsx/TestIssue697.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b025228f253c3c3ed133c51a0af6c98d6791fc11 GIT binary patch literal 5631 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%0AIm0JNUJR!)zfdEkR_{$cgS;!6 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 Query(bool useHeaderRow, string startRowIndex--; // if sheets count > 1 need to read xl/_rels/workbook.xml.rels - var sheets = _archive.entries.Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) - || w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) - ).ToArray(); + var sheets = _archive.entries + .Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || + w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) + ).ToArray(); ZipArchiveEntry sheetEntry = null; if (sheetName != null) { SetWorkbookRels(_archive.entries); - var sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName); + var sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); if (sheetRecord == null && _config.DynamicSheets != null) { var sheetConfig = _config.DynamicSheets.FirstOrDefault(ds => ds.Key == sheetName); if (sheetConfig != null) { - sheetRecord = _sheetRecords.SingleOrDefault(_ => _.Name == sheetConfig.Name); + sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetConfig.Name); } } if (sheetRecord == null) throw new InvalidOperationException("Please check sheetName/Index is correct"); - sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || w.FullName == $"/xl/{sheetRecord.Path}" || w.FullName == sheetRecord.Path || sheetRecord.Path == $"/{w.FullName}"); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" + || w.FullName == $"/xl/{sheetRecord.Path}" + || w.FullName == sheetRecord.Path + || $"/{w.FullName}" == sheetRecord.Path); } else if (sheets.Length > 1) { @@ -78,7 +82,9 @@ public IEnumerable> Query(bool useHeaderRow, string // fixed by argo@live.ca // s.Path = "/xl/sheets/sheet1.xml" s.FullName = "/xl/sheets/sheet1.xml" - sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName.TrimStart('/') == s.Path.TrimStart('/')); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" + || w.FullName == $"/xl/{s.Path}" + || w.FullName.TrimStart('/') == s.Path.TrimStart('/')); //#endif } else @@ -96,42 +102,42 @@ public IEnumerable> Query(bool useHeaderRow, string yield break; while (reader.Read()) { - if (XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) + if (!XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) + continue; + if (!XmlReaderHelper.ReadFirstContent(reader)) + yield break; + + while (!reader.EOF) { - if (!XmlReaderHelper.ReadFirstContent(reader)) - yield break; - while (!reader.EOF) + if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) { - if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) - { - var @ref = reader.GetAttribute("ref"); - var refs = @ref.Split(':'); - if (refs.Length == 1) - continue; + var refAttr = reader.GetAttribute("ref"); + var refs = refAttr.Split(':'); + if (refs.Length == 1) + continue; - ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); - ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); + ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); + ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); - _mergeCells.MergesValues.Add(refs[0], null); + _mergeCells.MergesValues.Add(refs[0], null); - // foreach range - var isFirst = true; - for (int x = x1; x <= x2; x++) + // foreach range + var isFirst = true; + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) { - for (int y = y1; y <= y2; y++) - { - if (!isFirst) - _mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); - isFirst = false; - } + if (!isFirst) + _mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); + isFirst = false; } - - XmlReaderHelper.SkipContent(reader); - } - else if (!XmlReaderHelper.SkipContent(reader)) - { - break; } + + XmlReaderHelper.SkipContent(reader); + } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; } } } @@ -173,19 +179,19 @@ public IEnumerable> Query(bool useHeaderRow, string //this method logic depends on dimension to get maxcolumnIndex, if without dimension then it need to foreach all rows first time to get maxColumn and maxRowColumn else if (XmlReaderHelper.IsStartElement(reader, "dimension", _ns)) { - var @ref = reader.GetAttribute("ref"); - if (string.IsNullOrEmpty(@ref)) + var refAttr = reader.GetAttribute("ref"); + if (string.IsNullOrEmpty(refAttr)) throw new InvalidOperationException("Without sheet dimension data"); - var rs = @ref.Split(':'); + + var rs = refAttr.Split(':'); + // issue : https://github.com/shps951023/MiniExcel/issues/102 - if (ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) - { - maxColumnIndex = cIndex - 1; - maxRowIndex = rIndex - 1; - break; - } - else + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) throw new InvalidOperationException("Invaild sheet dimension start data"); + + maxColumnIndex = cIndex - 1; + maxRowIndex = rIndex - 1; + break; } } } @@ -291,10 +297,10 @@ public IEnumerable> Query(bool useHeaderRow, string } // fill empty rows - var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; - if (!(expectedRowIndex < startRowIndex)) + if (!_config.IgnoreEmptyRows) { - if (expectedRowIndex < rowIndex) + var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; + if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) { for (int i = expectedRowIndex; i < rowIndex; i++) { @@ -302,69 +308,68 @@ public IEnumerable> Query(bool useHeaderRow, string } } } - - // Set Cells + + #region Set Cells + var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + var columnIndex = withoutCR ? -1 : 0; + + while (!reader.EOF) { - var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - var columnIndex = withoutCR ? -1 : 0; - while (!reader.EOF) + if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) - { - var aS = reader.GetAttribute("s"); - var aR = reader.GetAttribute("r"); - var aT = reader.GetAttribute("t"); - var cellValue = ReadCellAndSetColumnIndex(reader, ref columnIndex, withoutCR, startColumnIndex, aR, aT); + var aS = reader.GetAttribute("s"); + var aR = reader.GetAttribute("r"); + var aT = reader.GetAttribute("t"); + var cellValue = ReadCellAndSetColumnIndex(reader, ref columnIndex, withoutCR, startColumnIndex, aR, aT); - if (_config.FillMergedCells) + if (_config.FillMergedCells) + { + if (_mergeCells.MergesValues.ContainsKey(aR)) { - if (_mergeCells.MergesValues.ContainsKey(aR)) - { - _mergeCells.MergesValues[aR] = cellValue; - } - else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) - { - object mergeValue = null; - if (_mergeCells.MergesValues.TryGetValue(mergeKey, out var value)) - mergeValue = value; - cellValue = mergeValue; - } + _mergeCells.MergesValues[aR] = cellValue; } + else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) + { + object mergeValue = null; + if (_mergeCells.MergesValues.TryGetValue(mergeKey, out var value)) + mergeValue = value; + cellValue = mergeValue; + } + } - if (columnIndex < startColumnIndex) - continue; + if (columnIndex < startColumnIndex) + continue; - if (!string.IsNullOrEmpty(aS)) // if c with s meaning is custom style need to check type by xl/style.xml - { - int xfIndex = -1; - if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) - xfIndex = styleIndex; + // if c with s meaning is custom style need to check type by xl/style.xml + if (!string.IsNullOrEmpty(aS)) + { + int xfIndex = -1; + if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) + xfIndex = styleIndex; - // only when have s attribute then load styles xml data - if (_style == null) - _style = new ExcelOpenXmlStyles(_archive); + // only when have s attribute then load styles xml data + if (_style == null) + _style = new ExcelOpenXmlStyles(_archive); - cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } - else - { - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } + cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); } - else if (!XmlReaderHelper.SkipContent(reader)) - break; + SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } - - if (isFirstRow) + else if (!XmlReaderHelper.SkipContent(reader)) { - isFirstRow = false; // for startcell logic - if (useHeaderRow) - continue; + break; } + } + #endregion - yield return cell; + if (isFirstRow) + { + isFirstRow = false; // for startcell logic + if (useHeaderRow) + continue; } + + yield return cell; } else if (!XmlReaderHelper.SkipContent(reader)) { @@ -380,79 +385,59 @@ public IEnumerable> Query(bool useHeaderRow, string } } - private IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) + private static IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) { return useHeaderRow ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); } - private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary headRows, ref bool isFirstRow, ref IDictionary cell, int columnIndex) + private static void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Dictionary headRows, ref bool isFirstRow, ref IDictionary cell, int columnIndex) { - if (useHeaderRow) - { - if (isFirstRow) // for startcell logic - { - var cellValueString = cellValue?.ToString(); - if (!string.IsNullOrWhiteSpace(cellValueString)) - headRows.Add(columnIndex, cellValueString); - } - else - { - if (headRows.TryGetValue(columnIndex, out var key)) - { - cell[key] = cellValue; - } - } - } - else + if (!useHeaderRow) { //if not using First Head then using A,B,C as index cell[ColumnHelper.GetAlphabetColumnName(columnIndex)] = cellValue; + return; + } + + if (isFirstRow) // for startcell logic + { + var cellValueString = cellValue?.ToString(); + if (!string.IsNullOrWhiteSpace(cellValueString)) + headRows.Add(columnIndex, cellValueString); + } + else if (headRows.TryGetValue(columnIndex, out var key)) + { + cell[key] = cellValue; } } public IEnumerable Query(string sheetName, string startCell) where T : class, new() { - if (sheetName == null) - { - var sheetInfo = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), this._config); - if (sheetInfo != null) - { - sheetName = sheetInfo.ExcelSheetName; - } - } - return ExcelOpenXmlSheetReader.QueryImpl(Query(false, sheetName, startCell), startCell, this._config); + if (sheetName == null) + sheetName = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; + + return QueryImpl(Query(false, sheetName, startCell), startCell, _config); } public static IEnumerable QueryImpl(IEnumerable> values, string startCell, Configuration configuration) where T : class, new() { var type = typeof(T); - List props = null; //TODO:need to optimize - - string[] headers = null; - + List props = null; Dictionary headersDic = null; string[] keys = null; var first = true; var rowIndex = 0; + foreach (var item in values) { if (first) { - keys = item.Keys.ToArray();//.Select((s, i) => new { s,i}).ToDictionary(_=>_.s,_=>_.i); - headers = item?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:remove - headersDic = headers.Select((o, i) => new { o = (o == null ? "" : o), i }) - .OrderBy(x => x.i) - .GroupBy(x => x.o) - .Select(group => new { Group = group, Count = group.Count() }) - .SelectMany(groupWithCount => - groupWithCount.Group.Select(b => b) - .Zip( - Enumerable.Range(1, groupWithCount.Count), - (j, i) => new { key = (i == 1 ? j.o : $"{j.o}_____{i}"), idx = j.i, RowNumber = i } - ) - ).ToDictionary(_ => _.key, _ => _.idx); + keys = item.Keys.ToArray(); + var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + //TODO: alert don't duplicate column name props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; @@ -465,17 +450,17 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di { foreach (var alias in pInfo.ExcelColumnAliases) { - if (headersDic.TryGetValue(alias, out var columnId)) - { - object newV = null; - var columnName = keys[columnId]; - item.TryGetValue(columnName, out var itemValue); + if (!headersDic.TryGetValue(alias, out var columnId)) + continue; + + object newV = null; + var columnName = keys[columnId]; + item.TryGetValue(columnName, out var itemValue); - if (itemValue == null) - continue; + if (itemValue == null) + continue; - newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); - } + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); } } @@ -503,6 +488,7 @@ private void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, ref Di yield return v; } } + private void SetSharedStrings() { if (_sharedStrings != null) @@ -519,10 +505,9 @@ private void SetSharedStrings() foreach (var sharedString in XmlReaderHelper.GetSharedStrings(stream, _ns)) _sharedStrings[idx++] = sharedString; } - else + else if (_sharedStrings == null) { - if (_sharedStrings == null) - _sharedStrings = XmlReaderHelper.GetSharedStrings(stream, _ns).ToDictionary((x) => idx++, x => x); + _sharedStrings = XmlReaderHelper.GetSharedStrings(stream, _ns).ToDictionary((x) => idx++, x => x); } } } @@ -776,9 +761,9 @@ private void ConvertCellValue(string rawValue, string aT, int xfIndex, out objec } } - public async Task>> QueryAsync(bool UseHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) { - return await Task.Run(() => Query(UseHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } public async Task> QueryAsync(string sheetName, string startCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() @@ -818,7 +803,7 @@ protected virtual void Dispose(bool disposing) public IEnumerable> QueryRange(bool useHeaderRow, string sheetName, string startCell, string endCell) { - if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex) == false ? true : true) + if (ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex)) { //throw new InvalidDataException($"startCell {startCell} is Invalid"); startColumnIndex--; @@ -834,7 +819,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st } //2022-09-24 获取结束单元格的,行,列 - if (!ReferenceHelper.ParseReference(endCell, out var endColumnIndex, out var endRowIndex) == false ? true : true) + if (ReferenceHelper.ParseReference(endCell, out var endColumnIndex, out var endRowIndex)) { //throw new InvalidDataException($"endCell {endCell} is Invalid"); endColumnIndex--; @@ -849,10 +834,10 @@ public IEnumerable> QueryRange(bool useHeaderRow, st if (sheetName != null) { SetWorkbookRels(_archive.entries); - var s = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName); - if (s == null) + var sheet = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); + if (sheet == null) throw new InvalidOperationException("Please check sheetName/Index is correct"); - sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName == s.Path || s.Path == $"/{w.FullName}"); + sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheet.Path}" || w.FullName == $"/xl/{sheet.Path}" || w.FullName == sheet.Path || sheet.Path == $"/{w.FullName}"); } else if (sheets.Count > 1) { @@ -883,8 +868,8 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) { - var @ref = reader.GetAttribute("ref"); - var refs = @ref.Split(':'); + var refAttr = reader.GetAttribute("ref"); + var refs = refAttr.Split(':'); if (refs.Length == 1) continue; @@ -954,14 +939,14 @@ public IEnumerable> QueryRange(bool useHeaderRow, st { //2022-09-24 Range //var @ref = reader.GetAttribute("ref"); - var @ref = startCell + ":" + endCell; + var refAttr = $"{startCell}:{endCell}"; if (endCell == "" || startCell == "") { - @ref = reader.GetAttribute("ref"); + refAttr = reader.GetAttribute("ref"); } - if (string.IsNullOrEmpty(@ref)) + if (string.IsNullOrEmpty(refAttr)) throw new InvalidOperationException("Without sheet dimension data"); - var rs = @ref.Split(':'); + var rs = refAttr.Split(':'); // issue : https://github.com/shps951023/MiniExcel/issues/102 if (ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex) == false ? true : true) @@ -1130,12 +1115,8 @@ public IEnumerable> QueryRange(bool useHeaderRow, st _style = new ExcelOpenXmlStyles(_archive); cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); - } - else - { - SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } + SetCellsValueAndHeaders(cellValue, useHeaderRow, ref headRows, ref isFirstRow, ref cell, columnIndex); } else if (!XmlReaderHelper.SkipContent(reader)) break; @@ -1167,7 +1148,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st public IEnumerable QueryRange(string sheetName, string startCell, string endCell) where T : class, new() { - return ExcelOpenXmlSheetReader.QueryImplRange(QueryRange(false, sheetName, startCell, endCell), startCell, endCell, this._config); + return QueryImplRange(QueryRange(false, sheetName, startCell, endCell), startCell, endCell, this._config); } public static IEnumerable QueryImplRange(IEnumerable> values, string startCell, string endCell, Configuration configuration) where T : class, new() @@ -1177,29 +1158,19 @@ public IEnumerable> QueryRange(bool useHeaderRow, st List props = null; //TODO:need to optimize - string[] headers = null; - Dictionary headersDic = null; string[] keys = null; var first = true; var rowIndex = 0; + foreach (var item in values) { if (first) { keys = item.Keys.ToArray();//.Select((s, i) => new { s,i}).ToDictionary(_=>_.s,_=>_.i); - headers = item?.Values?.Select(s => s?.ToString())?.ToArray(); //TODO:remove - headersDic = headers.Select((o, i) => new { o = (o == null ? string.Empty : o), i }) - .OrderBy(x => x.i) - .GroupBy(x => x.o) - .Select(group => new { Group = group, Count = group.Count() }) - .SelectMany(groupWithCount => - groupWithCount.Group.Select(b => b) - .Zip( - Enumerable.Range(1, groupWithCount.Count), - (j, i) => new { key = (i == 1 ? j.o : $"{j.o}_____{i}"), idx = j.i, RowNumber = i } - ) - ).ToDictionary(_ => _.key, _ => _.idx); + var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + //TODO: alert don't duplicate column name props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); first = false; @@ -1220,8 +1191,7 @@ public IEnumerable> QueryRange(bool useHeaderRow, st if (itemValue == null) continue; - newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, - configuration); + newV = TypeHelper.TypeMapping(v, pInfo, newV, itemValue, rowIndex, startCell, configuration); } } } @@ -1246,9 +1216,9 @@ public IEnumerable> QueryRange(bool useHeaderRow, st } } - public async Task>> QueryAsyncRange(bool UseHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) + public async Task>> QueryAsyncRange(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) { - return await Task.Run(() => Query(UseHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); + return await Task.Run(() => Query(useHeaderRow, sheetName, startCell), cancellationToken).ConfigureAwait(false); } public async Task> QueryAsyncRange(string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default(CancellationToken)) where T : class, new() diff --git a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs index dc157d49..be111b07 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs @@ -14,6 +14,8 @@ public class OpenXmlConfiguration : Configuration public bool IgnoreTemplateParameterMissing { get; set; } = true; public bool EnableWriteNullValueCell { get; set; } = true; public bool WriteEmptyStringAsNull { get; set; } = false; + public bool TrimColumnNames { get; set; } = true; + public bool IgnoreEmptyRows { get; set; } = false; public bool EnableSharedStringCache { get; set; } = true; public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; public DynamicExcelSheet[] DynamicSheets { get; set; } diff --git a/src/MiniExcel/Utils/CustomPropertyHelper.cs b/src/MiniExcel/Utils/CustomPropertyHelper.cs index b4c41c86..c4eb2075 100644 --- a/src/MiniExcel/Utils/CustomPropertyHelper.cs +++ b/src/MiniExcel/Utils/CustomPropertyHelper.cs @@ -1,15 +1,15 @@ -namespace MiniExcelLibs.Utils +using MiniExcelLibs.Attributes; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.OpenXml.Models; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; + +namespace MiniExcelLibs.Utils { - using MiniExcelLibs.Attributes; - using MiniExcelLibs.OpenXml; - using MiniExcelLibs.OpenXml.Models; - using System; - using System.Collections; - using System.Collections.Generic; - using System.ComponentModel; - using System.Linq; - using System.Reflection; - internal class ExcelColumnInfo { public object Key { get; set; } @@ -33,14 +33,8 @@ internal class ExcellSheetInfo public object Key { get; set; } public string ExcelSheetName { get; set; } public SheetState ExcelSheetState { get; set; } - - private string ExcelSheetStateAsString - { - get - { - return ExcelSheetState.ToString().ToLower(); - } - } + + private string ExcelSheetStateAsString => ExcelSheetState.ToString().ToLower(); public SheetDto ToDto(int sheetIndex) { @@ -48,7 +42,7 @@ public SheetDto ToDto(int sheetIndex) } } - internal static partial class CustomPropertyHelper + internal static class CustomPropertyHelper { internal static IDictionary GetEmptyExpandoObject(int maxColumnIndex, int startCellIndex) { @@ -73,7 +67,7 @@ internal static IDictionary GetEmptyExpandoObject(Dictionary GetSaveAsProperties(this Type type, Configuration configuration) { - List props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance, configuration) + var props = GetExcelPropertyInfo(type, BindingFlags.Public | BindingFlags.Instance, configuration) .Where(prop => prop.Property.CanRead) .ToList() /*ignore without set*/; @@ -89,7 +83,7 @@ internal static List SortCustomProps(List prop //TODO: need optimize performance var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1).ToList(); - if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) + if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(x => x.Count() > 1)) throw new InvalidOperationException("Duplicate column name"); var maxColumnIndex = props.Count - 1; @@ -98,7 +92,7 @@ internal static List SortCustomProps(List prop var withoutCustomIndexProps = props.Where(w => w.ExcelColumnIndex == null || w.ExcelColumnIndex == -1).ToList(); - List newProps = new List(); + var newProps = new List(); var index = 0; for (int i = 0; i <= maxColumnIndex; i++) { @@ -127,7 +121,8 @@ internal static List SortCustomProps(List prop internal static List GetExcelCustomPropertyInfos(Type type, string[] keys, Configuration configuration) { - List props = GetExcelPropertyInfo(type, BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance, configuration) + var flags = BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance; + var props = GetExcelPropertyInfo(type, flags, configuration) .Where(prop => prop.Property.Info.GetSetMethod() != null // why not .Property.CanWrite? because it will use private setter && !prop.Property.Info.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) && !prop.Property.Info.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore)) @@ -137,19 +132,18 @@ internal static List GetExcelCustomPropertyInfos(Type type, str throw new InvalidOperationException($"{type.Name} un-ignore properties count can't be 0"); var withCustomIndexProps = props.Where(w => w.ExcelColumnIndex != null && w.ExcelColumnIndex > -1); - if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(_ => _.Count() > 1)) + if (withCustomIndexProps.GroupBy(g => g.ExcelColumnIndex).Any(x => x.Count() > 1)) throw new InvalidOperationException("Duplicate column name"); var maxkey = keys.Last(); var maxIndex = ColumnHelper.GetColumnIndex(maxkey); foreach (var p in props) { - if (p.ExcelColumnIndex != null) - { - if (p.ExcelColumnIndex > maxIndex) - throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {maxkey}"); - if (p.ExcelColumnName == null) - throw new InvalidOperationException($"{p.Property.Info.DeclaringType.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name"); - } + if (p.ExcelColumnIndex == null) + continue; + if (p.ExcelColumnIndex > maxIndex) + throw new ArgumentException($"ExcelColumnIndex {p.ExcelColumnIndex} over haeder max index {maxkey}"); + if (p.ExcelColumnName == null) + throw new InvalidOperationException($"{p.Property.Info.DeclaringType?.Name} {p.Property.Name}'s ExcelColumnIndex {p.ExcelColumnIndex} can't find excel column name"); } return props; @@ -157,24 +151,19 @@ internal static List GetExcelCustomPropertyInfos(Type type, str internal static string DescriptionAttr(Type type, object source) { - FieldInfo fi = type.GetField(source.ToString()); + var fi = type.GetField(source.ToString()); //For some database dirty data, there may be no way to change to the correct enumeration, will return NULL if (fi == null) return source.ToString(); - DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes( - typeof(DescriptionAttribute), false); - - if (attributes != null && attributes.Length > 0) - return attributes[0].Description; - else - return source.ToString(); + var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : source.ToString(); } private static IEnumerable ConvertToExcelCustomPropertyInfo(PropertyInfo[] props, Configuration configuration) { // solve : https://github.com/shps951023/MiniExcel/issues/138 - return props.Select(p => + var columnInfos = props.Select(p => { var gt = Nullable.GetUnderlyingType(p.PropertyType); var excelColumnName = p.GetAttribute(); @@ -185,11 +174,12 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro if (dynamicColumn != null) excelColumn = dynamicColumn; - var ignore = p.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) || p.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore) || (excelColumn != null && excelColumn.Ignore); + var ignore = p.GetAttributeValue((ExcelIgnoreAttribute x) => x.ExcelIgnore) || + p.GetAttributeValue((ExcelColumnAttribute x) => x.Ignore) || + (excelColumn?.Ignore ?? false); if (ignore) - { return null; - } + //TODO:or configulation Dynamic var excelColumnIndex = excelColumn?.Index > -1 ? excelColumn.Index : (int?)null; return new ExcelColumnInfo @@ -207,7 +197,9 @@ private static IEnumerable ConvertToExcelCustomPropertyInfo(Pro ExcelColumnType = excelColumn?.Type ?? ColumnType.Value, CustomFormatter = dynamicColumn?.CustomFormatter }; - }).Where(_ => _ != null); + }); + + return columnInfos.Where(x => x != null); } private static IEnumerable GetExcelPropertyInfo(Type type, BindingFlags bindingFlags, Configuration configuration) @@ -219,7 +211,7 @@ private static IEnumerable GetExcelPropertyInfo(Type type, Bind internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration configuration) { // default options - var sheetInfo = new ExcellSheetInfo() + var sheetInfo = new ExcellSheetInfo { Key = type.Name, ExcelSheetName = null, // will be generated automatically as Sheet @@ -227,18 +219,17 @@ internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration conf }; // options from ExcelSheetAttribute - ExcelSheetAttribute excelSheetAttribute = type.GetCustomAttribute(typeof(ExcelSheetAttribute)) as ExcelSheetAttribute; - if (excelSheetAttribute != null) + if (type.GetCustomAttribute(typeof(ExcelSheetAttribute)) is ExcelSheetAttribute excelSheetAttr) { - sheetInfo.ExcelSheetName = excelSheetAttribute.Name ?? type.Name; - sheetInfo.ExcelSheetState = excelSheetAttribute.State; + sheetInfo.ExcelSheetName = excelSheetAttr.Name ?? type.Name; + sheetInfo.ExcelSheetState = excelSheetAttr.State; } // options from DynamicSheets configuration - OpenXmlConfiguration openXmlCOnfiguration = configuration as OpenXmlConfiguration; - if (openXmlCOnfiguration != null && openXmlCOnfiguration.DynamicSheets != null && openXmlCOnfiguration.DynamicSheets.Length > 0) + var openXmlCOnfiguration = configuration as OpenXmlConfiguration; + if (openXmlCOnfiguration?.DynamicSheets?.Length > 0) { - var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(_ => _.Key == type.Name); + var dynamicSheet = openXmlCOnfiguration.DynamicSheets.SingleOrDefault(x => x.Key == type.Name); if (dynamicSheet != null) { sheetInfo.ExcelSheetName = dynamicSheet.Name; @@ -251,43 +242,32 @@ internal static ExcellSheetInfo GetExcellSheetInfo(Type type, Configuration conf internal static List GetDictionaryColumnInfo(IDictionary dicString, IDictionary dic, Configuration configuration) { - List props; - var _props = new List(); - if (dicString != null) - { - foreach (var key in dicString.Keys) - { - SetDictionaryColumnInfo(_props, key, configuration); - } - } - else if (dic != null) - { - foreach (var key in dic.Keys) - { - SetDictionaryColumnInfo(_props, key, configuration); - } - } - else + var props = new List(); + var keys = dicString?.Keys.ToList() ?? dic?.Keys + ?? throw new NotSupportedException(); + + foreach (var key in keys) { - throw new NotSupportedException("SetDictionaryColumnInfo Error"); + SetDictionaryColumnInfo(props, key, configuration); } - - props = SortCustomProps(_props); - return props; + return SortCustomProps(props); } - internal static void SetDictionaryColumnInfo(List _props, object key, Configuration configuration) + internal static void SetDictionaryColumnInfo(List props, object key, Configuration configuration) { - var p = new ExcelColumnInfo(); - p.ExcelColumnName = key?.ToString(); - p.Key = key; + var p = new ExcelColumnInfo + { + Key = key, + ExcelColumnName = key?.ToString() + }; + // TODO:Dictionary value type is not fiexed //var _t = //var gt = Nullable.GetUnderlyingType(p.PropertyType); var isIgnore = false; if (configuration.DynamicColumns != null && configuration.DynamicColumns.Length > 0) { - var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => _.Key == key.ToString()); + var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(x => x.Key == key?.ToString()); if (dynamicColumn != null) { p.Nullable = true; @@ -311,7 +291,7 @@ internal static void SetDictionaryColumnInfo(List _props, objec } } if (!isIgnore) - _props.Add(p); + props.Add(p); } internal static bool TryGetTypeColumnInfo(Type type, Configuration configuration, out List props) @@ -324,14 +304,9 @@ internal static bool TryGetTypeColumnInfo(Type type, Configuration configuration } if (type.IsValueType) - { - throw new NotImplementedException($"MiniExcel not support only {type.Name} value generic type"); - } - + throw new NotImplementedException($"MiniExcel does not support only {type.Name} value generic type"); if (type == typeof(string) || type == typeof(DateTime) || type == typeof(Guid)) - { - throw new NotImplementedException($"MiniExcel not support only {type.Name} generic type"); - } + throw new NotImplementedException($"MiniExcel does not support only {type.Name} generic type"); if (ValueIsNeededToDetermineProperties(type)) { @@ -370,11 +345,11 @@ internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string co if (configuration.DynamicColumns == null || configuration.DynamicColumns.Length <= 0) return prop; - var dynamicColumn = configuration.DynamicColumns.SingleOrDefault(_ => string.Equals(_.Key, columnName, StringComparison.OrdinalIgnoreCase)); + var dynamicColumn = configuration.DynamicColumns + .SingleOrDefault(col => string.Equals(col.Key, columnName, StringComparison.OrdinalIgnoreCase)); + if (dynamicColumn == null || dynamicColumn.Ignore) - { return prop; - } prop.Nullable = true; prop.ExcelIgnore = dynamicColumn.Ignore; @@ -406,5 +381,36 @@ internal static ExcelColumnInfo GetColumnInfosFromDynamicConfiguration(string co return prop; } + + internal static Dictionary GetHeaders(IDictionary item, bool trimNames = false) + { + return DictToNameWithIndex(item) + .GroupBy(x => x.Name) + .SelectMany(GroupToNameWithIndex) + .ToDictionary(kv => trimNames ? kv.Name.Trim() : kv.Name, kv => kv.Index); + } + + private static IEnumerable DictToNameWithIndex(IDictionary dict) + { + return dict.Values.Select((obj, idx) => new NameIndexPair(idx, obj?.ToString() ?? "")); + } + + private static IEnumerable GroupToNameWithIndex(IGrouping group) + { + return group.Select((grp, idx) => + new NameIndexPair(grp.Index, idx == 0 ? grp.Name : $"{grp.Name}_____{idx + 1}")); + } + + private class NameIndexPair + { + public int Index { get; } + public string Name { get; } + + public NameIndexPair(int index, string name) + { + Index = index; + Name = name; + } + } } } \ No newline at end of file diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 679e65d9..a5f3d3e2 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3886,6 +3886,24 @@ static IEnumerable GetTestData() } } + internal class Issue697 + { + public int First { get; set; } + public int Second { get; set; } + public int Third { get; set; } + public int Fourth { get; set; } + } + + [Fact] + public void Test_Issue_697_EmptyRowsStronglyTypedQuery() + { + var path = "../../../../../samples/xlsx/TestIssue697.xlsx"; + var rowsIgnoreEmpty = MiniExcel.Query(path, configuration: new OpenXmlConfiguration{IgnoreEmptyRows = true}).ToList(); + var rowsCountEmpty = MiniExcel.Query(path).ToList(); + Assert.Equal(4, rowsIgnoreEmpty.Count); + Assert.Equal(5, rowsCountEmpty.Count); + } + [Fact] public void Test_Issue_693_SaveSheetWithLongName() { diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 8b4add0e..4ff82d00 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -20,7 +20,7 @@ namespace MiniExcelLibs.Tests { - public partial class MiniExcelOpenXmlTests + public class MiniExcelOpenXmlTests { private readonly ITestOutputHelper output; public MiniExcelOpenXmlTests(ITestOutputHelper output) @@ -32,19 +32,19 @@ public MiniExcelOpenXmlTests(ITestOutputHelper output) public void GetColumnsTest() { { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; var columns = MiniExcel.GetColumns(path); - Assert.Equal(new[] { "A", "B", "C", "D", "E", "F", "G", "H" }, columns); + Assert.Equal(["A", "B", "C", "D", "E", "F", "G", "H"], columns); } { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; var columns = MiniExcel.GetColumns(path); Assert.Equal(8, columns.Count); } { - var path = @"../../../../../samples/xlsx/TestEmpty.xlsx"; + var path = "../../../../../samples/xlsx/TestEmpty.xlsx"; var columns = MiniExcel.GetColumns(path); Assert.Null(columns); } @@ -54,22 +54,21 @@ public void GetColumnsTest() public void SaveAsControlChracter() { string path = GetTempXlsxPath(); - char[] chars = new char[] {'\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', + char[] chars = + [ + '\u0000','\u0001','\u0002','\u0003','\u0004','\u0005','\u0006','\u0007','\u0008', '\u0009', // '\u000A', // '\u000B','\u000C', '\u000D', // '\u000E','\u000F','\u0010','\u0011','\u0012','\u0013','\u0014','\u0015','\u0016', '\u0017','\u0018','\u0019','\u001A','\u001B','\u001C','\u001D','\u001E','\u001F','\u007F' - }; + ]; var input = chars.Select(s => new { Test = s.ToString() }); MiniExcel.SaveAs(path, input); var rows2 = MiniExcel.Query(path, true).Select(s => s.Test).ToArray(); - var rows1 = MiniExcel.Query(path).Select(s => s.Test).ToArray(); - - } public class SaveAsControlChracterVO @@ -96,14 +95,14 @@ public class ExcelAttributeDemo [Fact] public void CustomAttributeWihoutVaildPropertiesTest() { - var path = @"../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; Assert.Throws(() => MiniExcel.Query(path).ToList()); } [Fact] public void QueryCustomAttributesTest() { - var path = @"../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestCustomExcelColumnAttribute.xlsx"; var rows = MiniExcel.Query(path).ToList(); Assert.Equal("Column1", rows[0].Test1); Assert.Equal("Column2", rows[0].Test2); @@ -157,7 +156,7 @@ public class CustomAttributesWihoutVaildPropertiesTestPoco - [Fact()] + [Fact] public void QueryCastToIDictionary() { var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; @@ -170,9 +169,9 @@ public void QueryCastToIDictionary() [Fact] public void QueryRangeToIDictionary() { - var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; + var path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; // tips:Only uppercase letters are effective - var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C") + var rows = MiniExcel.QueryRange(path, startCell: "A2", endCell: "C7") .Cast>() .ToList(); Assert.Equal(5, rows.Count); @@ -181,10 +180,10 @@ public void QueryRangeToIDictionary() Assert.Equal(null!, rows[2]["A"]); } - [Fact()] + [Fact] public void CenterEmptyRowsQueryTest() { - var path = @"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; + var path = "../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -218,7 +217,6 @@ public void CenterEmptyRowsQueryTest() Assert.Equal(2, rows[5].B); Assert.Equal(null, rows[5].C); Assert.Equal(4, rows[5].D); - } using (var stream = File.OpenRead(path)) @@ -251,11 +249,11 @@ public void CenterEmptyRowsQueryTest() Assert.Equal(4, rows[4].d); } } - + [Fact] public void TestEmptyRowsQuerySelfClosingTag() { - var path = @"../../../../../samples/xlsx/TestEmptySelfClosingRow.xlsx"; + var path = "../../../../../samples/xlsx/TestEmptySelfClosingRow.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -273,10 +271,10 @@ public void TestEmptyRowsQuerySelfClosingTag() } } - [Fact()] + [Fact] public void TestDynamicQueryBasic_WithoutHead() { - var path = @"../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; + var path = "../../../../../samples/xlsx/TestDynamicQueryBasic_WithoutHead.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -288,10 +286,10 @@ public void TestDynamicQueryBasic_WithoutHead() } } - [Fact()] + [Fact] public void TestDynamicQueryBasic_useHeaderRow() { - var path = @"../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; + var path = "../../../../../samples/xlsx/TestDynamicQueryBasic.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query(useHeaderRow: true).ToList(); @@ -325,13 +323,13 @@ public class UserAccount public int Age { get; set; } public bool VIP { get; set; } public decimal Points { get; set; } - public int IgnoredProperty { get { return 1; } } + public int IgnoredProperty => 1; } - [Fact()] + [Fact] public void QueryStrongTypeMapping_Test() { - var path = @"../../../../../samples/xlsx/TestTypeMapping.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -365,16 +363,16 @@ public void QueryStrongTypeMapping_Test() public class AutoCheckType { - public Guid? @guid { get; set; } - public bool? @bool { get; set; } - public DateTime? datetime { get; set; } - public string @string { get; set; } + public Guid? Guid { get; set; } + public bool? Bool { get; set; } + public DateTime? Datetime { get; set; } + public string String { get; set; } } - [Fact()] + [Fact] public void AutoCheckTypeTest() { - var path = @"../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; + var path = "../../../../../samples/xlsx/TestTypeMapping_AutoCheckFormat.xlsx"; using (var stream = FileHelper.OpenRead(path)) { var rows = stream.Query().ToList(); @@ -401,11 +399,30 @@ public void UriMappingTest() Assert.Equal(new Uri("https://friendly-utilization.net"), rows[1].Url); } } + + public class SimpleAccount + { + public string Name { get; set; } + public int Age { get; set; } + public string Mail { get; set; } + public decimal Points { get; set; } + } + [Fact] + public void TrimColumnNamesTest() + { + var path = "../../../../../samples/xlsx/TestTrimColumnNames.xlsx"; + var rows = MiniExcel.Query(path).ToList(); + + Assert.Equal("Raymond", rows[4].Name); + Assert.Equal(18, rows[4].Age); + Assert.Equal("sagittis.lobortis@leoMorbi.com", rows[4].Mail); + Assert.Equal(8209.76m, rows[4].Points); + } - [Fact()] + [Fact] public void TestDatetimeSpanFormat_ClosedXml() { - var path = @"../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; + var path = "../../../../../samples/xlsx/TestDatetimeSpanFormat_ClosedXml.xlsx"; using (var stream = FileHelper.OpenRead(path)) { var row = stream.Query().First(); @@ -416,10 +433,10 @@ public void TestDatetimeSpanFormat_ClosedXml() } } - [Fact()] + [Fact] public void LargeFileQueryStrongTypeMapping_Test() { - var path = @"../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; + var path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().Take(2).ToList(); @@ -435,9 +452,9 @@ public void LargeFileQueryStrongTypeMapping_Test() } } - [Theory()] - [InlineData(@"../../../../../samples/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] - [InlineData(@"../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] + [Theory] + [InlineData("../../../../../samples/xlsx/ExcelDataReaderCollections/TestChess.xlsx")] + [InlineData("../../../../../samples/xlsx/TestCenterEmptyRow/TestCenterEmptyRow.xlsx")] public void QueryExcelDataReaderCheckTest(string path) { #if NETCOREAPP3_1 || NET5_0 @@ -446,7 +463,7 @@ public void QueryExcelDataReaderCheckTest(string path) DataSet exceldatareaderResult; using (var stream = File.OpenRead(path)) - using (var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(stream)) + using (var reader = ExcelReaderFactory.CreateReader(stream)) { exceldatareaderResult = reader.AsDataSet(); } @@ -461,7 +478,7 @@ public void QueryExcelDataReaderCheckTest(string path) var keys = row.Keys; foreach (var key in keys) { - var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][MiniExcelLibs.Tests.Utils.Helpers.GetColumnIndex(key)]; + var eV = exceldatareaderResult.Tables[0].Rows[rowIndex][Helpers.GetColumnIndex(key)]; var v = row[key] == null ? DBNull.Value : row[key]; Assert.Equal(eV, v); } @@ -469,24 +486,24 @@ public void QueryExcelDataReaderCheckTest(string path) } } - [Fact()] + [Fact] public void QueryCustomStyle() { - var path = @"../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; using (var stream = File.OpenRead(path)) { } } - [Fact()] + [Fact] public void QuerySheetWithoutRAttribute() { - var path = @"../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; + var path = "../../../../../samples/xlsx/TestWihoutRAttribute.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); - var keys = (rows.First() as IDictionary).Keys; + var keys = (rows.First() as IDictionary)!.Keys; Assert.Equal(2, rows.Count); Assert.Equal(5, keys.Count); @@ -504,15 +521,15 @@ public void QuerySheetWithoutRAttribute() } } - [Fact()] + [Fact] public void FixDimensionJustOneColumnParsingError_Test() { { - var path = @"../../../../../samples/xlsx/TestDimensionC3.xlsx"; + var path = "../../../../../samples/xlsx/TestDimensionC3.xlsx"; using (var stream = File.OpenRead(path)) { var rows = stream.Query().ToList(); - var keys = (rows.First() as IDictionary).Keys; + var keys = (rows.First() as IDictionary)!.Keys; Assert.Equal(3, keys.Count); Assert.Equal(2, rows.Count); } @@ -524,17 +541,17 @@ public class SaveAsFileWithDimensionByICollectionTestType public string A { get; set; } public string B { get; set; } } - [Fact()] + [Fact] public void SaveAsFileWithDimensionByICollection() { //List { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List - { - new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, - new SaveAsFileWithDimensionByICollectionTestType{A="A",B="B"}, - }; + List values = + [ + new() { A = "A", B = "B" }, + new() { A = "A", B = "B" } + ]; MiniExcel.SaveAs(path, values); { using (var stream = File.OpenRead(path)) @@ -635,7 +652,7 @@ public void SaveAsFileWithDimensionByICollection() } } - [Fact()] + [Fact] public void SaveAsFileWithDimension() { { @@ -666,7 +683,7 @@ public void SaveAsFileWithDimension() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890); - table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now); + table.Rows.Add("Hello World", -1234567890, false, DateTime.Now); } MiniExcel.SaveAs(path, table); Assert.Equal("A1:D3", Helpers.GetFirstSheetDimensionRefValue(path)); @@ -695,8 +712,6 @@ public void SaveAsFileWithDimension() File.Delete(path); - - MiniExcel.SaveAs(path, table, printHeader: false); Assert.Equal("A1:D2", Helpers.GetFirstSheetDimensionRefValue(path)); File.Delete(path); @@ -709,8 +724,8 @@ public void SaveAsFileWithDimension() var table = new DataTable(); { table.Columns.Add("a", typeof(string)); - table.Rows.Add(@"A"); - table.Rows.Add(@"B"); + table.Rows.Add("A"); + table.Rows.Add("B"); } MiniExcel.SaveAs(path, table); Assert.Equal("A3", Helpers.GetFirstSheetDimensionRefValue(path)); @@ -718,7 +733,7 @@ public void SaveAsFileWithDimension() } } - [Fact()] + [Fact] public void SaveAsByDataTableTest() { { @@ -732,7 +747,7 @@ public void SaveAsByDataTableTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add(@"""<>+-*//}{\\n", 1234567890, true, now); - table.Rows.Add(@"Hello World", -1234567890, false, now.Date); + table.Rows.Add("Hello World", -1234567890, false, now.Date); } MiniExcel.SaveAs(path, table, sheetName: "R&D"); @@ -747,7 +762,7 @@ public void SaveAsByDataTableTest() Assert.True(ws.Cells["D1"].Value.ToString() == "d"); Assert.True(ws.Cells["A2"].Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(ws.Cells["B2"].Value.ToString() == @"1234567890"); + Assert.True(ws.Cells["B2"].Value.ToString() == "1234567890"); Assert.True(ws.Cells["C2"].Value.ToString() == true.ToString()); Assert.True(ws.Cells["D2"].Value.ToString() == now.ToString()); @@ -772,7 +787,7 @@ public void SaveAsByDataTableTest() } } - [Fact()] + [Fact] public void QueryByLINQExtensionsAvoidLargeFileOOMTest() { var path = "../../../../../benchmarks/MiniExcel.Benchmarks/Test1,000,000x10.xlsx"; @@ -801,7 +816,7 @@ public void EmptyTest() var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"with cte as (select 1 id,2 val) select * from cte where 1=2"); + var rows = connection.Query("with cte as (select 1 id,2 val) select * from cte where 1=2"); MiniExcel.SaveAs(path, rows); } using (var stream = File.OpenRead(path)) @@ -925,7 +940,7 @@ public void SaveAsFrozenRowsAndColumnsTest() table.Columns.Add("c", typeof(bool)); table.Columns.Add("d", typeof(DateTime)); table.Rows.Add("some text", 1234567890, true, DateTime.Now); - table.Rows.Add(@"Hello World", -1234567890, false, DateTime.Now.Date); + table.Rows.Add("Hello World", -1234567890, false, DateTime.Now.Date); } var pathTable = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); MiniExcel.SaveAs(pathTable, table, configuration: config); @@ -943,7 +958,7 @@ public void SaveAsFrozenRowsAndColumnsTest() } - [Fact()] + [Fact] public void SaveAsByDapperRows() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); @@ -952,7 +967,7 @@ public void SaveAsByDapperRows() // Dapper Query using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); + var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2"); MiniExcel.SaveAs(path, rows); } @@ -973,7 +988,7 @@ public void SaveAsByDapperRows() // Empty using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2").ToList(); + var rows = connection.Query("with cte as (select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2)select * from cte where 1=2").ToList(); MiniExcel.SaveAs(path, rows); } @@ -996,7 +1011,7 @@ public void SaveAsByDapperRows() // ToList using (var connection = Db.GetConnection("Data Source=:memory:")) { - var rows = connection.Query(@"select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2").ToList(); + var rows = connection.Query("select 'MiniExcel' as Column1,1 as Column2 union all select 'Github',2").ToList(); MiniExcel.SaveAs(path, rows); } @@ -1032,16 +1047,16 @@ public class Demo public string Column1 { get; set; } public decimal Column2 { get; set; } } - [Fact()] + [Fact] public void QueryByStrongTypeParameterTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List() - { - new Demo { Column1= "MiniExcel" ,Column2 = 1 }, - new Demo { Column1 = "Github", Column2 = 2 } - }; + List values = + [ + new() { Column1 = "MiniExcel", Column2 = 1 }, + new() { Column1 = "Github", Column2 = 2 } + ]; MiniExcel.SaveAs(path, values); @@ -1058,16 +1073,16 @@ public void QueryByStrongTypeParameterTest() File.Delete(path); } - [Fact()] + [Fact] public void QueryByDictionaryStringAndObjectParameterTest() { var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - var values = new List>() - { - new Dictionary{{ "Column1", "MiniExcel" }, { "Column2", 1 } }, - new Dictionary{{ "Column1", "Github" }, { "Column2", 2 } } - }; + List> values = + [ + new() { { "Column1", "MiniExcel" }, { "Column2", 1 } }, + new() { { "Column1", "Github" }, { "Column2", 2 } } + ]; MiniExcel.SaveAs(path, values); @@ -1084,7 +1099,7 @@ public void QueryByDictionaryStringAndObjectParameterTest() File.Delete(path); } - [Fact()] + [Fact] public void SQLiteInsertTest() { // Avoid SQL Insert Large Size Xlsx OOM @@ -1094,7 +1109,7 @@ public void SQLiteInsertTest() using (var connection = new SQLiteConnection(connectionString)) { - connection.Execute(@"create table T (A varchar(20),B varchar(20));"); + connection.Execute("create table T (A varchar(20),B varchar(20));"); } using (var connection = new SQLiteConnection(connectionString)) @@ -1277,7 +1292,7 @@ public void SavaAsClosedXmlCanReadTest() Assert.True(ws.Cell("C1").Value.ToString() == "c"); Assert.True(ws.Cell("A2").Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(ws.Cell("B2").Value.ToString() == @"1234567890"); + Assert.True(ws.Cell("B2").Value.ToString() == "1234567890"); Assert.True(ws.Cell("C2").Value.ToString() == true.ToString()); Assert.True(ws.Cell("D2").Value.ToString() == now.ToString()); @@ -1531,7 +1546,7 @@ public void InsertSheetTest() Assert.True(sheet1.Cells["D1"].Value.ToString() == "d"); Assert.True(sheet1.Cells["A2"].Value.ToString() == @"""<>+-*//}{\\n"); - Assert.True(sheet1.Cells["B2"].Value.ToString() == @"1234567890"); + Assert.True(sheet1.Cells["B2"].Value.ToString() == "1234567890"); Assert.True(sheet1.Cells["C2"].Value.ToString() == true.ToString()); Assert.True(sheet1.Cells["D2"].Value.ToString() == now.ToString()); @@ -1615,8 +1630,8 @@ public void InsertSheetTest() FastMode = true, AutoFilter = false, TableStyles = TableStyles.None, - DynamicColumns = new[] - { + DynamicColumns = + [ new DynamicExcelColumn("Column2") { Name = "Its Date", @@ -1624,7 +1639,7 @@ public void InsertSheetTest() Width = 150, Format = "dd.mm.yyyy hh:mm:ss", } - } + ] }); Assert.Equal(2, rowsWritten); From 6a6c08d1ce7c8bbf0b0dcc3c622857a8235390b3 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Mon, 17 Mar 2025 23:51:33 +0100 Subject: [PATCH 3/5] Bugfix of invalid cell values being mistakenly parsed as valid --- samples/xlsx/TestIssue686.xlsx | Bin 0 -> 4416 bytes .../OpenXml/ExcelOpenXmlSheetReader.cs | 12 +-- src/MiniExcel/Utils/ReferenceHelper.cs | 95 +++++++----------- tests/MiniExcelTests/MiniExcelIssueTests.cs | 11 ++ 4 files changed, 55 insertions(+), 63 deletions(-) create mode 100644 samples/xlsx/TestIssue686.xlsx diff --git a/samples/xlsx/TestIssue686.xlsx b/samples/xlsx/TestIssue686.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..5f54ba24dc0f6bd2cf084a063f1ef49b84faec95 GIT binary patch literal 4416 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 literal 0 HcmV?d00001 diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index bc156e25..8e18aeab 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -949,14 +949,12 @@ public IEnumerable> QueryRange(bool useHeaderRow, st var rs = refAttr.Split(':'); // issue : https://github.com/shps951023/MiniExcel/issues/102 - if (ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex) == false ? true : true) - { - maxColumnIndex = cIndex - 1; - maxRowIndex = rIndex - 1; - break; - } - else + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) throw new InvalidOperationException("Invaild sheet dimension start data"); + + maxColumnIndex = cIndex - 1; + maxRowIndex = rIndex - 1; + break; } } } diff --git a/src/MiniExcel/Utils/ReferenceHelper.cs b/src/MiniExcel/Utils/ReferenceHelper.cs index 50b2ad1d..b14a61fa 100644 --- a/src/MiniExcel/Utils/ReferenceHelper.cs +++ b/src/MiniExcel/Utils/ReferenceHelper.cs @@ -1,30 +1,23 @@ -namespace MiniExcelLibs.Utils -{ - using System; - using System.Globalization; +using System; +using System.Globalization; +using System.Linq; - internal static partial class ReferenceHelper +namespace MiniExcelLibs.Utils +{ + internal static class ReferenceHelper { public static string GetCellNumber(string cell) { - string cellNumber = string.Empty; - for (int i = 0; i < cell.Length; i++) - { - if (Char.IsDigit(cell[i])) - cellNumber += cell[i]; - } - return cellNumber; + return cell + .Where(char.IsDigit) + .Aggregate(string.Empty, (current, c) => current + c); } public static string GetCellLetter(string cell) { - string GetCellLetter = string.Empty; - for (int i = 0; i < cell.Length; i++) - { - if (Char.IsLetter(cell[i])) - GetCellLetter += cell[i]; - } - return GetCellLetter; + return cell + .Where(char.IsLetter) + .Aggregate(string.Empty, (current, c) => current + c); } /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) @@ -33,11 +26,9 @@ public static Tuple ConvertCellToXY(string cell) const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; const int mode = 26; - var x = 0; - var cellLetter = GetCellLetter(cell); //AA=27,ZZ=702 - for (int i = 0; i < cellLetter.Length; i++) - x = x * mode + keys.IndexOf(cellLetter[i]); + var cellLetter = GetCellLetter(cell); + var x = cellLetter.Aggregate(0, (idx, chr) => idx * mode + keys.IndexOf(chr)); var cellNumber = GetCellNumber(cell); return Tuple.Create(x, int.Parse(cellNumber)); @@ -47,23 +38,18 @@ public static Tuple ConvertCellToXY(string cell) public static string ConvertXyToCell(int x, int y) { int dividend = x; - string columnName = String.Empty; - int modulo; + string columnName = string.Empty; while (dividend > 0) { - modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo).ToString() + columnName; - dividend = (int)((dividend - modulo) / 26); + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; } return $"{columnName}{y}"; } - } - - internal static partial class ReferenceHelper - { - /**Below Code Copy&Modified from ExcelDataReader @MIT License**/ + /**The code below was copied and modified from ExcelDataReader - @MIT License**/ /// /// Logic for the Excel dimensions. Ex: A15 /// @@ -72,45 +58,42 @@ internal static partial class ReferenceHelper /// The row, 1-based. public static bool ParseReference(string value, out int column, out int row) { + row = 0; column = 0; var position = 0; const int offset = 'A' - 1; - if (value != null) + if (string.IsNullOrWhiteSpace(value)) + return false; + + while (position < value.Length) { - while (position < value.Length) + var c = char.ToUpperInvariant(value[position]); + if (c >= 'A' && c <= 'Z') { - var c = value[position]; - if (c >= 'A' && c <= 'Z') - { - position++; - column *= 26; - column += c - offset; - continue; - } + position++; + column *= 26; + column += c - offset; + continue; + } + if (!char.IsLetter(c)) + { if (char.IsDigit(c)) break; - - position = 0; - break; + return false; } - } - if (position == 0) - { - column = 0; row = 0; - return false; + column = 0; + position = 0; + break; } - if (!int.TryParse(value.Substring(position), NumberStyles.None, CultureInfo.InvariantCulture, out row)) - { + if (position == 0) return false; - } - return true; + return int.TryParse(value.Substring(position), NumberStyles.None, CultureInfo.InvariantCulture, out row); } } - } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index a5f3d3e2..de04dc80 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3886,6 +3886,17 @@ static IEnumerable GetTestData() } } + [Fact] + public void Issue_686() + { + var path = PathHelper.GetFile("xlsx/TestIssue686.xlsx"); + Assert.Throws(() => + MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF10", endCell:"ZZFF11").First()); + + Assert.Throws(() => + MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF@@10", endCell:"ZZFF@@11").First()); + } + internal class Issue697 { public int First { get; set; } From 6b56bd9529813f55d735dd2b92b1b7c5b3f04858 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Tue, 18 Mar 2025 19:19:21 +0100 Subject: [PATCH 4/5] Addendum Added exception throw for sheet name to long to Insert method as well. --- src/MiniExcel/MiniExcel.cs | 8 ++--- tests/MiniExcelTests/MiniExcelIssueTests.cs | 37 +++++++++++---------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index cae14730..460df19e 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -49,11 +49,13 @@ public static int Insert(string path, object value, string sheetName = "Sheet1", public static int Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) { + if (sheetName.Length > 31 && excelType == ExcelType.XLSX) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); + stream.Seek(0, SeekOrigin.End); - // reuse code if (excelType == ExcelType.CSV) { - var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}.AsEnumerable(); + var newValue = value is IEnumerable || value is IDataReader ? value : new[]{value}; return ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false).Insert(overwriteSheet); } else @@ -90,9 +92,7 @@ public static int[] SaveAs(this Stream stream, object value, bool printHeader = { using (var excelReader = ExcelReaderFactory.GetProvider(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration)) foreach (var item in excelReader.Query(sheetName, startCell)) - { yield return item; - } } public static IEnumerable Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) { diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index de04dc80..8270b9a2 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3896,7 +3896,26 @@ public void Issue_686() Assert.Throws(() => MiniExcel.QueryRange(path, useHeaderRow: false, startCell: "ZZFF@@10", endCell:"ZZFF@@11").First()); } - + + [Fact] + public void Test_Issue_693_SaveSheetWithLongName() + { + var path1 = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + var path2 = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); + try + { + List> data = [ new() { ["First"] = 1, ["Second"] = 2 }]; + Assert.Throws(() => MiniExcel.SaveAs(path1, data, sheetName:"Some Really Looooooooooong Sheet Name")); + MiniExcel.SaveAs(path2, new List>()); + Assert.Throws(() => MiniExcel.Insert(path1, data, sheetName:"Some Other Very Looooooong Sheet Name")); + } + finally + { + File.Delete(path1); + File.Delete(path2); + } + } + internal class Issue697 { public int First { get; set; } @@ -3904,7 +3923,6 @@ internal class Issue697 public int Third { get; set; } public int Fourth { get; set; } } - [Fact] public void Test_Issue_697_EmptyRowsStronglyTypedQuery() { @@ -3914,21 +3932,6 @@ public void Test_Issue_697_EmptyRowsStronglyTypedQuery() Assert.Equal(4, rowsIgnoreEmpty.Count); Assert.Equal(5, rowsCountEmpty.Count); } - - [Fact] - public void Test_Issue_693_SaveSheetWithLongName() - { - var path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.xlsx"); - try - { - List> data = [ new() { ["First"] = 1, ["Second"] = 2 }]; - Assert.Throws(() => MiniExcel.SaveAs(path, data, sheetName:"Some Really Looooooooooong Sheet Name")); - } - finally - { - File.Delete(path); - } - } [Fact] public void Issue_710() From ab84a4d52e39f7d0ea7ff760b49cc36e7cedd79a Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Wed, 19 Mar 2025 21:13:49 +0100 Subject: [PATCH 5/5] Moved sheet name check and fixed datetime formatting bug --- src/MiniExcel/ExcelFactory.cs | 18 ++++++++++-------- src/MiniExcel/MiniExcel.cs | 5 ----- .../ExcelOpenXmlSheetWriter.DefaultOpenXml.cs | 10 ++++------ 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/MiniExcel/ExcelFactory.cs b/src/MiniExcel/ExcelFactory.cs index d63998b7..b9a64711 100644 --- a/src/MiniExcel/ExcelFactory.cs +++ b/src/MiniExcel/ExcelFactory.cs @@ -6,7 +6,7 @@ using System; using System.IO; - internal class ExcelReaderFactory + internal static class ExcelReaderFactory { internal static IExcelReader GetProvider(Stream stream, ExcelType excelType, IConfiguration configuration) { @@ -17,19 +17,21 @@ internal static IExcelReader GetProvider(Stream stream, ExcelType excelType, ICo case ExcelType.XLSX: return new ExcelOpenXmlSheetReader(stream, configuration); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException("Please Issue for me"); } } } - internal class ExcelWriterFactory + internal static class ExcelWriterFactory { internal static IExcelWriter GetProvider(Stream stream, object value, string sheetName, ExcelType excelType, IConfiguration configuration, bool printHeader) { if (string.IsNullOrEmpty(sheetName)) - throw new InvalidDataException("Sheet name can not be empty or null"); + throw new ArgumentException("Sheet names can not be empty or null", nameof(sheetName)); + if (sheetName.Length > 31 && excelType == ExcelType.XLSX) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); if (excelType == ExcelType.UNKNOWN) - throw new InvalidDataException("Please specify excelType"); + throw new ArgumentException("Excel type cannot be ExcelType.UNKNOWN", nameof(excelType)); switch (excelType) { @@ -38,12 +40,12 @@ internal static IExcelWriter GetProvider(Stream stream, object value, string she case ExcelType.XLSX: return new ExcelOpenXmlSheetWriter(stream, value, sheetName, configuration, printHeader); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException($"The {excelType} Excel format is not supported"); } } } - internal class ExcelTemplateFactory + internal static class ExcelTemplateFactory { internal static IExcelTemplateAsync GetProvider(Stream stream, IConfiguration configuration, ExcelType excelType = ExcelType.XLSX) { @@ -53,7 +55,7 @@ internal static IExcelTemplateAsync GetProvider(Stream stream, IConfiguration co var valueExtractor = new InputValueExtractor(); return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor); default: - throw new NotSupportedException($"Please Issue for me"); + throw new NotSupportedException("Please Issue for me"); } } } diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index 460df19e..844fb215 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -49,9 +49,6 @@ public static int Insert(string path, object value, string sheetName = "Sheet1", public static int Insert(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false) { - if (sheetName.Length > 31 && excelType == ExcelType.XLSX) - throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); - stream.Seek(0, SeekOrigin.End); if (excelType == ExcelType.CSV) { @@ -76,8 +73,6 @@ public static int[] SaveAs(string path, object value, bool printHeader = true, s public static int[] SaveAs(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null) { - if (sheetName.Length > 31 && excelType == ExcelType.XLSX) - throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); return ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAs(); } diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index d90d3af3..d9904034 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -308,7 +308,7 @@ private string GetFileValue(int rowIndex, int cellIndex, object value) private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) { string cellValue = null; - if (!_configuration.Culture.Equals(CultureInfo.InvariantCulture)) + if (!ReferenceEquals(_configuration.Culture, CultureInfo.InvariantCulture)) { cellValue = value.ToString(_configuration.Culture); return Tuple.Create("2", "str", cellValue); @@ -316,11 +316,9 @@ private Tuple GetDateTimeValue(DateTime value, ExcelColu var oaDate = CorrectDateTimeValue(value); cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - - if (columnInfo?.ExcelFormat != null) - return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); - - return Tuple.Create("3", (string)null, cellValue); + var format = columnInfo?.ExcelFormat != null ? columnInfo.ExcelFormatId.ToString() : "3"; + + return Tuple.Create(format, (string)null, cellValue); } private static double CorrectDateTimeValue(DateTime value)