From f572df377eb7f5d2d4fc01b8a71deab72cf7f41f Mon Sep 17 00:00:00 2001 From: Michele Bastione Date: Thu, 15 May 2025 22:52:44 +0200 Subject: [PATCH] Solves issue 459 and removes redundant api methods --- samples/xlsx/TestIssue459.xlsx | Bin 0 -> 10454 bytes src/MiniExcel/MiniExcel.cs | 13 +----- .../ExcelOpenXmlTemplate.Impl.cs | 43 ++++++++++-------- tests/MiniExcelTests/MiniExcelIssueTests.cs | 25 ++++++++++ tests/MiniExcelTests/MiniExcelOpenXmlTests.cs | 6 +-- 5 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 samples/xlsx/TestIssue459.xlsx diff --git a/samples/xlsx/TestIssue459.xlsx b/samples/xlsx/TestIssue459.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..eb9875d454002af00ed2ebe3daa1c1eb17cbaf26 GIT binary patch literal 10454 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 GetSheetDimensions(this Stream stream) { return new ExcelOpenXmlSheetReader(stream, null).GetDimensions(); } - - public static IList GetSheetsDimensions(string path) - { - using (var stream = FileHelper.OpenSharedRead(path)) - return GetSheetsDimensions(stream); - } - - public static IList GetSheetsDimensions(this Stream stream) - { - return new ExcelOpenXmlSheetReader(stream, null).GetDimensions(); - } - + public static void ConvertCsvToXlsx(string csv, string xlsx) { using (var csvStream = FileHelper.OpenSharedRead(csv)) diff --git a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs index 953e306e..9695f508 100644 --- a/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/SaveByTemplate/ExcelOpenXmlTemplate.Impl.cs @@ -991,30 +991,38 @@ private static void ReplaceSharedStringsToStr(IDictionary sharedStr private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps, XmlDocument doc, XmlNodeList rows, bool changeRowIndex = true) { - // note : dimension need to put on the top (https://user-images.githubusercontent.com/12729184/114507911-5dd88400-9c66-11eb-94c6-82ed7bdb5aab.png) + string[] refs; + if (doc.SelectSingleNode("/x:worksheet/x:dimension", _ns) is XmlElement dimension) + { + refs = dimension.GetAttribute("ref").Split(':'); + } + else + { + var firstRow = rows[0].SelectNodes("x:c", _ns); + var lastRow = rows[rows.Count - 1].SelectNodes("x:c", _ns); + + var dimStart = ((XmlElement)firstRow?[0])?.GetAttribute("r"); + var dimEnd = ((XmlElement)lastRow?[lastRow.Count - 1])?.GetAttribute("r"); + + refs = new[] { dimStart, dimEnd }; - var dimension = doc.SelectSingleNode("/x:worksheet/x:dimension", _ns) as XmlElement; - if (dimension == null) - throw new NotImplementedException("Excel Dimension Xml is null, please file an issue for this problem: https://github.com/mini-software/MiniExcel/issues"); + dimension = (XmlElement)doc.CreateNode(XmlNodeType.Element, "dimension", null); + var worksheet = doc.SelectSingleNode("/x:worksheet", _ns); + worksheet?.InsertBefore(dimension, worksheet.FirstChild); + } var maxRowIndexDiff = 0; foreach (XmlElement row in rows) { // ==== get ienumerable infomation & maxrowindexdiff ==== - //Type ienumerableGenricType = null; - //IDictionary props = null; - //IEnumerable ienumerable = null; - var xRowInfo = new XRowInfo - { - Row = row - }; - + var xRowInfo = new XRowInfo { Row = row }; _xRowInfos.Add(xRowInfo); + foreach (XmlElement c in row.SelectNodes("x:c", _ns)) { var r = c.GetAttribute("r"); - + // ==== mergecells ==== if (_xMergeCellInfos.TryGetValue(r, out var merCell)) { @@ -1122,7 +1130,7 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps } // ==== get dimension max rowindex ==== - if (!first) //avoid duplicate add first one, this row not add status ![image](https://user-images.githubusercontent.com/12729184/114851829-d2512580-9e14-11eb-8e7d-520c89a7ebee.png) + if (!first) //avoid duplicate add first one, this row not add status (https://user-images.githubusercontent.com/12729184/114851829-d2512580-9e14-11eb-8e7d-520c89a7ebee.png) maxRowIndexDiff += xRowInfo.IEnumerableMercell?.Height ?? 1; first = false; } @@ -1183,7 +1191,7 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps foreach (var element in xRowInfo.CellIEnumerableValues) { // ==== get demension max rowindex ==== - if (!first) //avoid duplicate add first one, this row not add status ![image](https://user-images.githubusercontent.com/12729184/114851829-d2512580-9e14-11eb-8e7d-520c89a7ebee.png) + if (!first) //avoid duplicate add first one, this row not add status (https://user-images.githubusercontent.com/12729184/114851829-d2512580-9e14-11eb-8e7d-520c89a7ebee.png) maxRowIndexDiff++; first = false; } @@ -1249,20 +1257,17 @@ private void UpdateDimensionAndGetRowsInfo(IDictionary inputMaps } } - // e.g only need to update B6 to BMaxRowIndex - var refs = dimension.GetAttribute("ref").Split(':'); + // e.g we only need to update B6 to BMaxRowIndex if (refs.Length == 2) { var letter = StringHelper.GetLetters(refs[1]); var digit = StringHelper.GetNumber(refs[1]); - dimension.SetAttribute("ref", $"{refs[0]}:{letter}{digit + maxRowIndexDiff}"); } else { var letter = StringHelper.GetLetters(refs[0]); var digit = StringHelper.GetNumber(refs[0]); - dimension.SetAttribute("ref", $"A1:{letter}{digit + maxRowIndexDiff}"); } } diff --git a/tests/MiniExcelTests/MiniExcelIssueTests.cs b/tests/MiniExcelTests/MiniExcelIssueTests.cs index 88d88cca..e0eea3ac 100644 --- a/tests/MiniExcelTests/MiniExcelIssueTests.cs +++ b/tests/MiniExcelTests/MiniExcelIssueTests.cs @@ -3615,6 +3615,31 @@ public void Issue422() Assert.Equal(1, enumerableWithCount.GetEnumeratorCount); } + [Fact] + public void Issue459() + { + var template = PathHelper.GetFile("xlsx/TestIssue459.xlsx"); + using var ms = new MemoryStream(); + var values = new + { + title = "FooCompany", + managers = new[] + { + new { name = "Jack", department = "HR" }, + new { name = "Loan", department = "IT" } + }, + employees = new[] + { + new { name = "Wade", department = "HR" }, + new { name = "Felix", department = "HR" }, + new { name = "Eric", department = "IT" }, + new { name = "Keaton", department = "IT" } + } + }; + + ms.SaveAsByTemplate(template, values); + } + private class Issue585VO1 { public string Col1 { get; set; } diff --git a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs index 53f38215..2a10d3a5 100644 --- a/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs +++ b/tests/MiniExcelTests/MiniExcelOpenXmlTests.cs @@ -1549,7 +1549,7 @@ public void DateOnlySupportTest() public void SheetDimensionsTest() { var path1 = PathHelper.GetFile("xlsx/TestTypeMapping.xlsx"); - var dim1 = MiniExcel.GetSheetsDimensions(path1); + var dim1 = MiniExcel.GetSheetDimensions(path1); Assert.Equal("A1", dim1[0].StartCell); Assert.Equal("H101", dim1[0].EndCell); Assert.Equal(101, dim1[0].Rows.Count); @@ -1560,7 +1560,7 @@ public void SheetDimensionsTest() Assert.Equal(8, dim1[0].Columns.EndIndex); var path2 = PathHelper.GetFile("xlsx/TestNoDimension.xlsx"); - var dim2 = MiniExcel.GetSheetsDimensions(path2); + var dim2 = MiniExcel.GetSheetDimensions(path2); Assert.Equal(101, dim2[0].Rows.Count); Assert.Equal(7, dim2[0].Columns.Count); Assert.Equal(1, dim2[0].Rows.StartIndex); @@ -1573,7 +1573,7 @@ public void SheetDimensionsTest() public void SheetDimensionsTest_MultiSheet() { var path = PathHelper.GetFile("xlsx/TestMultiSheet.xlsx"); - var dim = MiniExcel.GetSheetsDimensions(path); + var dim = MiniExcel.GetSheetDimensions(path); Assert.Equal("A1", dim[0].StartCell); Assert.Equal("D12", dim[0].EndCell);