From 641df103ba285175b34f22d63f0cf212c9bd7522 Mon Sep 17 00:00:00 2001 From: izanhzh Date: Wed, 18 Jun 2025 10:55:59 +0800 Subject: [PATCH 1/2] Fix the issue of query multiple empty lines --- samples/xlsx/TestIssue809.xlsx | Bin 0 -> 8604 bytes .../OpenXml/ExcelOpenXmlSheetReader.cs | 181 ++++++++++-------- tests/MiniExcelTests/MiniExcelIssueTests.cs | 68 ++++--- 3 files changed, 137 insertions(+), 112 deletions(-) create mode 100644 samples/xlsx/TestIssue809.xlsx diff --git a/samples/xlsx/TestIssue809.xlsx b/samples/xlsx/TestIssue809.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..adde36faebf6e11fceb3678117448cc8112e18aa GIT binary patch literal 8604 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 K^q-Lq;Qs&(2(!Nc literal 0 HcmV?d00001 diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index dc8b81f3..6ae3724c 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -22,7 +22,6 @@ internal partial class ExcelOpenXmlSheetReader : IExcelReader private static readonly string[] _relationshiopNs = { Config.SpreadsheetmlXmlRelationshipns, Config.SpreadsheetmlXmlStrictRelationshipns }; private List _sheetRecords; internal IDictionary _sharedStrings; - private MergeCells _mergeCells; private ExcelOpenXmlStyles _style; internal readonly ExcelOpenXmlZip _archive; private readonly OpenXmlConfiguration _config; @@ -140,7 +139,6 @@ public IAsyncEnumerable> QueryRangeAsync(bool useHea return QueryImplAsync(QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken), ReferenceHelper.ConvertXyToCell(startColumnIndex, startRowIndex), hasHeader, _config, cancellationToken); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] internal async IAsyncEnumerable> InternalQueryRangeAsync(bool useHeaderRow, string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -165,8 +163,6 @@ internal async IAsyncEnumerable> InternalQueryRangeA yield break; } - _mergeCells = mergeCellsResult.MergeCells; - var maxRowColumnIndexResult = await TryGetMaxRowColumnIndexAsync(sheetEntry, cancellationToken).ConfigureAwait(false); if (!maxRowColumnIndexResult.IsSuccess) { @@ -222,89 +218,16 @@ internal async IAsyncEnumerable> InternalQueryRangeA break; } - // fill empty rows - if (!_config.IgnoreEmptyRows) - { - var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; - if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) - { - for (int i = expectedRowIndex; i < rowIndex; i++) - { - yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - } - } - } - - // row -> c, must after `if (nextRowIndex < rowIndex)` condition code, eg. The first empty row has no xml element,and the second row xml element is - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false) && !_config.IgnoreEmptyRows) - { - //Fill in case of self closed empty row tag eg. - yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - continue; - } - - #region Set Cells - - var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - var columnIndex = withoutCR ? -1 : 0; - while (!reader.EOF) + await foreach (var row in QueryRowAsync(reader, isFirstRow, startRowIndex, nextRowIndex, rowIndex, startColumnIndex, endColumnIndex, maxColumnIndex, withoutCR, useHeaderRow, headRows, mergeCellsResult.MergeCells, cancellationToken).ConfigureAwait(false)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + if (isFirstRow) { - var aS = reader.GetAttribute("s"); - var aR = reader.GetAttribute("r"); - var aT = reader.GetAttribute("t"); - var cellAndColumn = await ReadCellAndSetColumnIndexAsync(reader, columnIndex, withoutCR, - startColumnIndex, aR, aT, cancellationToken).ConfigureAwait(false); - - var cellValue = cellAndColumn.CellValue; - columnIndex = cellAndColumn.ColumnIndex; - - if (_config.FillMergedCells) - { - if (_mergeCells.MergesValues.ContainsKey(aR)) - { - _mergeCells.MergesValues[aR] = cellValue; - } - else if (_mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) - { - _mergeCells.MergesValues.TryGetValue(mergeKey, out cellValue); - } - } - - if (columnIndex < startColumnIndex || (endColumnIndex.HasValue && columnIndex > endColumnIndex.Value)) + isFirstRow = false; // for startcell logic + if (useHeaderRow) 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; - - // 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 if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - break; + yield return row; } - - #endregion - - if (isFirstRow) - { - isFirstRow = false; // for startcell logic - if (useHeaderRow) - continue; - } - - yield return cell; } else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) { @@ -320,6 +243,94 @@ internal async IAsyncEnumerable> InternalQueryRangeA } } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async IAsyncEnumerable> QueryRowAsync( + XmlReader reader, + bool isFirstRow, + int startRowIndex, + int nextRowIndex, + int rowIndex, + int startColumnIndex, + int? endColumnIndex, + int maxColumnIndex, + bool withoutCR, + bool useHeaderRow, + Dictionary headRows, + MergeCells mergeCells, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + // fill empty rows + if (!_config.IgnoreEmptyRows) + { + var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; + if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) + { + for (int i = expectedRowIndex; i < rowIndex; i++) + { + yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + } + } + } + + // row -> c, must after `if (nextRowIndex < rowIndex)` condition code, eg. The first empty row has no xml element,and the second row xml element is + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false) && !_config.IgnoreEmptyRows) + { + //Fill in case of self closed empty row tag eg. + yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + yield break; + } + + var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + var columnIndex = withoutCR ? -1 : 0; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + { + var aS = reader.GetAttribute("s"); + var aR = reader.GetAttribute("r"); + var aT = reader.GetAttribute("t"); + var cellAndColumn = await ReadCellAndSetColumnIndexAsync(reader, columnIndex, withoutCR, startColumnIndex, aR, aT, cancellationToken).ConfigureAwait(false); + + var cellValue = cellAndColumn.CellValue; + columnIndex = cellAndColumn.ColumnIndex; + + if (_config.FillMergedCells) + { + if (mergeCells.MergesValues.ContainsKey(aR)) + { + mergeCells.MergesValues[aR] = cellValue; + } + else if (mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) + { + mergeCells.MergesValues.TryGetValue(mergeKey, out cellValue); + } + } + + if (columnIndex < startColumnIndex || (endColumnIndex.HasValue && columnIndex > endColumnIndex.Value)) + 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; + + // 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, headRows, isFirstRow, cell, columnIndex); + } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + break; + } + yield return cell; + } + [Zomp.SyncMethodGenerator.CreateSyncVersion] public static async IAsyncEnumerable QueryImplAsync(IAsyncEnumerable> values, string startCell, bool hasHeader, Configuration configuration, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() { @@ -433,7 +444,7 @@ private static IDictionary GetCell(bool useHeaderRow, int maxCol return useHeaderRow ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); } - private static 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, Dictionary headRows, bool isFirstRow, IDictionary cell, int columnIndex) { if (!useHeaderRow) { @@ -698,7 +709,7 @@ private async Task ReadCellAndSetColumnIndexAsync(XmlReader reade } else if (XmlReaderHelper.IsStartElement(reader, "is", _ns)) { - var rawValue = await StringHelper.ReadStringItemAsync(reader,cancellationToken).ConfigureAwait(false); + var rawValue = await StringHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); if (!string.IsNullOrEmpty(rawValue)) ConvertCellValue(rawValue, aT, xfIndex, out value); } @@ -1087,7 +1098,7 @@ public MergeCellsResult(bool isSuccess) } public MergeCellsResult(bool isSuccess, MergeCells mergeCells) - : this (isSuccess) + : this(isSuccess) { MergeCells = mergeCells; } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 25cb62d4..d08241a7 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -20,6 +20,7 @@ using static MiniExcelLibs.Tests.MiniExcelOpenXmlTests; using MiniExcelLibs.Picture; using TableStyles = MiniExcelLibs.OpenXml.TableStyles; +using System.Threading.Tasks; namespace MiniExcelLibs.Tests; @@ -534,7 +535,7 @@ public void TestIssue401(bool autoFilter, int count) using (var connection = Db.GetConnection("Data Source=:memory:")) { connection.Open(); - + using var command = connection.CreateCommand(); command.CommandText = """ @@ -545,11 +546,11 @@ 1 as Column2 UNION ALL SELECT 'Github', 2 """; - + using var reader = command.ExecuteReader(); MiniExcel.SaveAs(path.ToString(), reader, configuration: config); } - + var xml = Helpers.GetZipFileContent(path.ToString(), "xl/worksheets/sheet1.xml"); var cnt = Regex.Matches(xml, "autoFilter").Count; Assert.Equal(count, cnt); @@ -2925,7 +2926,7 @@ public void Issue206() dt.Columns.Add("name"); dt.Columns.Add("department"); dt.Rows.Add("Jack", "HR"); - + var value = new Dictionary { ["employees"] = dt }; MiniExcel.SaveAsByTemplate(path.ToString(), templatePath, value); @@ -3644,12 +3645,12 @@ public void Issue459() var values = new { title = "FooCompany", - managers = new[] + managers = new[] { new { name = "Jack", department = "HR" }, new { name = "Loan", department = "IT" } }, - employees = new[] + employees = new[] { new { name = "Wade", department = "HR" }, new { name = "Felix", department = "HR" }, @@ -3657,10 +3658,10 @@ public void Issue459() new { name = "Keaton", department = "IT" } } }; - + ms.SaveAsByTemplate(template, values); } - + [Fact] public void Issue527() { @@ -3669,10 +3670,10 @@ public void Issue527() new() { Name = "Bill", UserType = DescriptionEnum.V1 }, new() { Name = "Bob", UserType = DescriptionEnum.V2 } ]; - + var value = new { t = row }; var template = PathHelper.GetFile("xlsx/Issue527Template.xlsx"); - + using var path = AutoDeletingPath.Create(); MiniExcel.SaveAsByTemplate(path.FilePath, template, value); @@ -3695,9 +3696,9 @@ public void TestIssue584() using var conn = Db.GetConnection(); conn.Open(); - + using var cmd = conn.CreateCommand(); - cmd.CommandText = + cmd.CommandText = """ WITH test('Id', 'Name') AS ( VALUES @@ -4248,10 +4249,10 @@ public void TestIssue768() var list = Enumerable.Range(0, 10) .Select(_ => new - { - value1 = Guid.NewGuid(), - value2 = Guid.NewGuid() - } + { + value1 = Guid.NewGuid(), + value2 = Guid.NewGuid() + } ) .ToList(); @@ -4266,7 +4267,7 @@ public void TestIssue768() Assert.Equal(list[0].value1.ToString(), rows[0].A.ToString()); Assert.Equal(list[1].value1.ToString(), rows[1].A.ToString()); } - + /// /// https://github.com/mini-software/MiniExcel/issues/186 /// @@ -4276,7 +4277,7 @@ public void TestIssue186() var originPath = PathHelper.GetFile("xlsx/TestIssue186_Template.xlsx"); using var path = AutoDeletingPath.Create(); File.Copy(originPath, path.FilePath); - + MiniExcelPicture[] images = [ new() @@ -4295,10 +4296,10 @@ public void TestIssue186() HeightPx = 100 } ]; - + MiniExcel.AddPicture(path.FilePath, images); } - + /// /// https://github.com/mini-software/MiniExcel/issues/771 /// @@ -4307,7 +4308,7 @@ public void TestIssue771() { var template = PathHelper.GetFile("xlsx/TestIssue771.xlsx"); using var path = AutoDeletingPath.Create(); - + var value = new { list = GetEnumerable(), @@ -4323,10 +4324,10 @@ public void TestIssue771() list11 = GetEnumerable(), list12 = GetEnumerable() }; - + MiniExcel.SaveAsByTemplate(path.FilePath, template, value); var rows = MiniExcel.Query(path.FilePath).ToList(); - + Assert.Equal("2025-1", rows[2].B); Assert.Equal(null, rows[3].B); Assert.Equal(null, rows[4].B); @@ -4335,7 +4336,7 @@ public void TestIssue771() IEnumerable GetEnumerable() => Enumerable.Range(0, 3).Select(s => new { ID = Guid.NewGuid(), level = s }); } - + /// /// https://github.com/mini-software/MiniExcel/issues/772 /// @@ -4343,10 +4344,10 @@ public void TestIssue771() public void TestIssue772() { var path = PathHelper.GetFile("xlsx/TestIssue772.xlsx"); - var rows = MiniExcel.Query(path, sheetName: "Supply plan(daily)", startCell:"A1") + var rows = MiniExcel.Query(path, sheetName: "Supply plan(daily)", startCell: "A1") .Cast>() .ToArray(); - + Assert.Equal("01108083-1Delta", (string)rows[19]["C"]); } @@ -4369,7 +4370,7 @@ public void TestIssue773() MiniExcel.SaveAsByTemplate(path.FilePath, templatePath, fill); var rows = MiniExcel.Query(path.FilePath).ToList(); - + Assert.Equal("H1", rows[4].AF); Assert.Equal("c3", rows[6].AA); Assert.Equal("Ram", rows[6].B); @@ -4393,4 +4394,17 @@ public void TestIssue789() Assert.Contains("", xml); } + + /// + /// https://github.com/mini-software/MiniExcel/issues/809 + /// + [Fact] + public void TestIssue809() + { + var path = PathHelper.GetFile("xlsx/TestIssue809.xlsx"); + var rows = MiniExcel.Query(path).ToList(); + Assert.Equal(3, rows.Count); + Assert.Equal(null, rows[0].A); + Assert.Equal(2, rows[2].B); + } } \ No newline at end of file From 5dd34969e8d4c2e8aeaba77f833c44e8fdb097b8 Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Fri, 20 Jun 2025 20:51:59 +0200 Subject: [PATCH 2/2] Fixed merge conflict --- .../OpenXml/ExcelOpenXmlSheetReader.cs | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 6b3a665f..6b868456 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -157,8 +157,7 @@ internal async IAsyncEnumerable> InternalQueryRangeA // TODO: need to optimize performance // Q. why need 3 times openstream merge one open read? A. no, zipstream can't use position = 0 - var mergeCellsContext = new MergeCellsContext { }; - + var mergeCellsContext = new MergeCellsContext(); if (_config.FillMergedCells && !await TryGetMergeCellsAsync(sheetEntry, mergeCellsContext, cancellationToken).ConfigureAwait(false)) { yield break; @@ -219,7 +218,7 @@ internal async IAsyncEnumerable> InternalQueryRangeA break; } - await foreach (var row in QueryRowAsync(reader, isFirstRow, startRowIndex, nextRowIndex, rowIndex, startColumnIndex, endColumnIndex, maxColumnIndex, withoutCR, useHeaderRow, headRows, mergeCellsResult.MergeCells, cancellationToken).ConfigureAwait(false)) + await foreach (var row in QueryRowAsync(reader, isFirstRow, startRowIndex, nextRowIndex, rowIndex, startColumnIndex, endColumnIndex, maxColumnIndex, withoutCR, useHeaderRow, headRows, mergeCellsContext.MergeCells, cancellationToken).ConfigureAwait(false)) { if (isFirstRow) { @@ -1090,19 +1089,7 @@ internal static async Task TryGetMaxRowColumnIndexAs internal class MergeCellsContext { - public bool IsSuccess { get; } - public MergeCells MergeCells { get; } - - public MergeCellsResult(bool isSuccess) - { - IsSuccess = isSuccess; - } - - public MergeCellsResult(bool isSuccess, MergeCells mergeCells) - : this(isSuccess) - { - MergeCells = mergeCells; - } + public MergeCells MergeCells { get; set; } } [Zomp.SyncMethodGenerator.CreateSyncVersion] @@ -1116,7 +1103,7 @@ internal static async Task TryGetMergeCellsAsync(ZipArchiveEntry sheetEntr #else true #endif - ); + ); var mergeCells = new MergeCells(); using (var sheetStream = sheetEntry.Open()) using (XmlReader reader = XmlReader.Create(sheetStream, xmlSettings))