From ee2b198aff960af171be2a7add7849a838af9b43 Mon Sep 17 00:00:00 2001 From: eynarhaji <38832863+eynarhaji@users.noreply.github.com> Date: Wed, 9 Aug 2023 08:18:57 +0200 Subject: [PATCH] Add support for merge limit (#521) * add support for grouped cells * fix for rows after endgroup * minor fix for text after grouping * fix unit tests * fix unit tests and docs * minor fix * Add support to vertical merge cells * minor fixes in merge cells * minor fix * fix complex scenario * finalize changes * Add support for if/elseif/else statements * add merge tag * add merge-tag * update readme * minor fix * minor fix * minor fix * Update MiniExcelLibs.csproj * Add support for merge limit * update test cases * rollback --------- Co-authored-by: Wei Lin --- README.md | 9 +++++ docs/README.md | 5 +-- samples/xlsx/TestMergeWithLimitTag.xlsx | Bin 0 -> 9401 bytes src/MiniExcel/MiniExcelLibs.csproj | 2 +- .../OpenXml/ExcelOpenXmlTemplate.Impl.cs | 31 ++++++++++++------ .../MiniExcelTests/MiniExcelTemplateTests.cs | 17 ++++++++++ tests/MiniExcelTests/MiniExcelTests.csproj | 2 +- 7 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 samples/xlsx/TestMergeWithLimitTag.xlsx diff --git a/README.md b/README.md index 5b6ed4d8..f05055dc 100644 --- a/README.md +++ b/README.md @@ -529,6 +529,7 @@ Since 1.22.0, when value type is `byte[]` then system will save file path at cel #### 12. Merge same cells vertically This functionality is only supported in `xlsx` format and merges cells vertically between @merge and @endmerge tags. +You can use @mergelimit to limit boundaries of merging cells vertically. ```csharp var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); @@ -548,10 +549,18 @@ memoryStream.MergeSameCells(path); File content before and after merge: +Without merge limit: + Screenshot 2023-08-07 at 11 59 24 Screenshot 2023-08-07 at 11 59 57 +With merge limit: + +Screenshot 2023-08-08 at 18 21 00 + +Screenshot 2023-08-08 at 18 21 40 + #### 13. Skip null values New explicit option to write empty cells for null values: diff --git a/docs/README.md b/docs/README.md index d9af5553..cd36e1cb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,8 +22,9 @@ --- -### 1.31.1 -- [New] support automatic merge for same vertical cells between @merge and @endmerge tags (via @eynarhaji) +### 1.31.2 +- [New] Support automatic merge for same vertical cells between @merge and @endmerge tags (via @eynarhaji) +- [New] Limit merge tagged columns with @mergelimit column. First merge limited column and then merge other columns accordingly. (via @eynarhaji) ### 1.31.1 - [OPT] Support property cache #23 (via @RRQM_Home) diff --git a/samples/xlsx/TestMergeWithLimitTag.xlsx b/samples/xlsx/TestMergeWithLimitTag.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..855493f21ae571bd26f7a5ab4035814426391474 GIT binary patch literal 9401 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 U4zjuc021UE014paWIzA?AB2ZvZ~y=R literal 0 HcmV?d00001 diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index 9989f014..4cb83861 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -1,7 +1,7 @@  net45;netstandard2.0 - 1.31.1 + 1.31.2 MiniExcel diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs index 462c4483..6d03b610 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlTemplate.Impl.cs @@ -199,12 +199,11 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo ColIndex = StringHelper.GetLetter(att), RowIndex = StringHelper.GetNumber(att) }; - }).ToList(); + }).OrderBy(x=>x.RowIndex).ToList(); - var mergeColumns = columns.Where(s => s.InnerText.Contains("@merge")).OrderBy(s => s.RowIndex) - .ToList(); - var endMergeColumns = columns.Where(s => s.InnerText.Contains("@endmerge")).OrderBy(s => s.RowIndex) - .ToList(); + var mergeColumns = columns.Where(s => s.InnerText.Contains("@merge")).ToList(); + var endMergeColumns = columns.Where(s => s.InnerText.Contains("@endmerge")).ToList(); + var mergeLimitColumn = mergeColumns.FirstOrDefault(x=>x.InnerText.Contains("@mergelimit")); foreach (var mergeColumn in mergeColumns) { @@ -227,8 +226,7 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo x.ColIndex == taggedColumn.Key.ColIndex && x.RowIndex > taggedColumn.Key.RowIndex && x.RowIndex < taggedColumn.Value.RowIndex)); } - - + Dictionary lastMergeCellIndexes = new Dictionary(); @@ -240,16 +238,29 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo foreach (var childNode in childNodes) { - var childNodeAtt = StringHelper.GetLetter(childNode.GetAttribute("r")); + var att = childNode.GetAttribute("r"); + var childNodeLetter = StringHelper.GetLetter(att); + var childNodeNumber = StringHelper.GetNumber(att); if(!string.IsNullOrEmpty(childNode.InnerText)) { var xmlNodes = calculatedColumns - .Where(j => j.InnerText == childNode.InnerText && j.ColIndex == childNodeAtt) + .Where(j => j.InnerText == childNode.InnerText && j.ColIndex == childNodeLetter) .OrderBy(s => s.RowIndex).ToList(); if (xmlNodes.Count > 1) { + if (mergeLimitColumn != null) + { + var limitedNode = calculatedColumns.First(j => + j.ColIndex == mergeLimitColumn.ColIndex && j.RowIndex == childNodeNumber); + + var limitedMaxNode = calculatedColumns.Last(j => + j.ColIndex == mergeLimitColumn.ColIndex && j.InnerText == limitedNode.InnerText); + + xmlNodes = xmlNodes.Where(j => j.RowIndex >= limitedNode.RowIndex && j.RowIndex <= limitedMaxNode.RowIndex).ToList(); + } + var firstRow = xmlNodes.FirstOrDefault(); var lastRow = xmlNodes.LastOrDefault(s => s.RowIndex <= firstRow?.RowIndex + xmlNodes.Count && @@ -280,7 +291,7 @@ private void WriteSheetXml(Stream stream, XmlDocument doc, XmlNode sheetData, bo } } - childNode.SetAttribute("r", $"{childNodeAtt}{{{{$rowindex}}}}"); //TODO: + childNode.SetAttribute("r", $"{childNodeLetter}{{{{$rowindex}}}}"); //TODO: } } } diff --git a/tests/MiniExcelTests/MiniExcelTemplateTests.cs b/tests/MiniExcelTests/MiniExcelTemplateTests.cs index fb7fef0c..da514193 100644 --- a/tests/MiniExcelTests/MiniExcelTemplateTests.cs +++ b/tests/MiniExcelTests/MiniExcelTemplateTests.cs @@ -857,5 +857,22 @@ public void MergeSameCellsWithTagTest() Assert.Equal("A7:A8", mergedCells[2]); } } + + [Fact] + public void MergeSameCellsWithLimitTagTest() + { + var mergedFilePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid().ToString()}.xlsx"); + + var path = @"../../../../../samples/xlsx/TestMergeWithLimitTag.xlsx"; + + MiniExcel.MergeSameCells(mergedFilePath, path); + { + var mergedCells = Helpers.GetFirstSheetMergedCells(mergedFilePath); + + Assert.Equal("A3:A4", mergedCells[0]); + Assert.Equal("C3:C6", mergedCells[1]); + Assert.Equal("A5:A6", mergedCells[2]); + } + } } } diff --git a/tests/MiniExcelTests/MiniExcelTests.csproj b/tests/MiniExcelTests/MiniExcelTests.csproj index 587cb82d..44d607ab 100644 --- a/tests/MiniExcelTests/MiniExcelTests.csproj +++ b/tests/MiniExcelTests/MiniExcelTests.csproj @@ -1,7 +1,7 @@  - net5.0 + net5.0; false miniexcel.snk