diff --git a/main/HSSF/UserModel/HSSFSheet.cs b/main/HSSF/UserModel/HSSFSheet.cs index 880468918..0599f1de7 100644 --- a/main/HSSF/UserModel/HSSFSheet.cs +++ b/main/HSSF/UserModel/HSSFSheet.cs @@ -3006,13 +3006,27 @@ public ISheet CopySheet(String Name, Boolean copyStyle) HSSFRow destRow = (HSSFRow)newSheet.CreateRow(i); if (srcRow != null) { - CopyRow(this, newSheet, srcRow, destRow, styleMap, new Dictionary(), true); + // avoid O(N^2) performance scanning through all regions for each row + // merged regions will be copied after all the rows have been copied + CopyRow(this, newSheet, srcRow, destRow, styleMap, new Dictionary(), true, keepMergedRegions: false); if (srcRow.LastCellNum > maxColumnNum) { maxColumnNum = srcRow.LastCellNum; } } } + + // Copying merged regions + foreach (var srcRegion in this.MergedRegions) + { + var destRegion = srcRegion.Copy(); + // Additional check here as Sheet.CloneSheet() should already have copied the merged regions + if (!newSheet.IsMergedRegion(destRegion)) + { + newSheet.AddMergedRegion(destRegion); + } + } + for (int i = 0; i <= maxColumnNum; i++) { newSheet.SetColumnWidth(i, GetColumnWidth(i)); @@ -3062,13 +3076,27 @@ public void CopyTo(IWorkbook dest, String name, Boolean copyStyle, Boolean keepF HSSFRow destRow = (HSSFRow)newSheet.CreateRow(i); if (srcRow != null) { - CopyRow(this, newSheet, srcRow, destRow, styleMap, paletteMap, keepFormulas); + // avoid O(N^2) performance scanning through all regions for each row + // merged regions will be copied after all the rows have been copied + CopyRow(this, newSheet, srcRow, destRow, styleMap, paletteMap, keepFormulas, keepMergedRegions: false); if (srcRow.LastCellNum > maxColumnNum) { maxColumnNum = srcRow.LastCellNum; } } } + + // Copying merged regions + foreach (var srcRegion in this.MergedRegions) + { + var destRegion = srcRegion.Copy(); + // Additional check here as Sheet.CloneSheet() should already have copied the merged regions + if (!newSheet.IsMergedRegion(destRegion)) + { + newSheet.AddMergedRegion(destRegion); + } + } + for (int i = 0; i < maxColumnNum; i++) { newSheet.SetColumnWidth(i, GetColumnWidth(i)); @@ -3222,7 +3250,7 @@ private static Dictionary MergePalettes(HSSFWorkbook source, HSSFWo } return retval; } - private static void CopyRow(HSSFSheet srcSheet, HSSFSheet destSheet, HSSFRow srcRow, HSSFRow destRow, IDictionary styleMap, Dictionary paletteMap, bool keepFormulas) + private static void CopyRow(HSSFSheet srcSheet, HSSFSheet destSheet, HSSFRow srcRow, HSSFRow destRow, IDictionary styleMap, Dictionary paletteMap, bool keepFormulas, bool keepMergedRegions) { List mergedRegions = destSheet.Sheet.MergedRecords.MergedRegions; destRow.Height = srcRow.Height; @@ -3243,17 +3271,20 @@ private static void CopyRow(HSSFSheet srcSheet, HSSFSheet destSheet, HSSFRow src newCell = (HSSFCell)destRow.CreateCell(j); } HSSFCellUtil.CopyCell(oldCell, newCell, styleMap, paletteMap, keepFormulas); - CellRangeAddress mergedRegion = GetMergedRegion(srcSheet, srcRow.RowNum, (short)oldCell.ColumnIndex); - if (mergedRegion != null) + + if (keepMergedRegions) { - CellRangeAddress newMergedRegion = new CellRangeAddress(mergedRegion.FirstRow, - mergedRegion.LastRow, mergedRegion.FirstColumn, mergedRegion.LastColumn); - if (IsNewMergedRegion(newMergedRegion, mergedRegions)) + CellRangeAddress mergedRegion = GetMergedRegion(srcSheet, srcRow.RowNum, (short)oldCell.ColumnIndex); + if (mergedRegion != null) { - mergedRegions.Add(newMergedRegion); + CellRangeAddress newMergedRegion = new CellRangeAddress(mergedRegion.FirstRow, + mergedRegion.LastRow, mergedRegion.FirstColumn, mergedRegion.LastColumn); + if (IsNewMergedRegion(newMergedRegion, mergedRegions)) + { + mergedRegions.Add(newMergedRegion); + } } } - } } } diff --git a/main/NPOI.Core.csproj b/main/NPOI.Core.csproj index e3542e626..e8dff7bfa 100644 --- a/main/NPOI.Core.csproj +++ b/main/NPOI.Core.csproj @@ -17,7 +17,7 @@ - + diff --git a/ooxml/NPOI.OOXML.Core.csproj b/ooxml/NPOI.OOXML.Core.csproj index 11bb7f393..39e6f0ec4 100644 --- a/ooxml/NPOI.OOXML.Core.csproj +++ b/ooxml/NPOI.OOXML.Core.csproj @@ -27,6 +27,7 @@ + diff --git a/ooxml/XSSF/UserModel/XSSFSheet.cs b/ooxml/XSSF/UserModel/XSSFSheet.cs index b8650a1dc..1b072bc76 100644 --- a/ooxml/XSSF/UserModel/XSSFSheet.cs +++ b/ooxml/XSSF/UserModel/XSSFSheet.cs @@ -3670,10 +3670,19 @@ public void CopyTo(IWorkbook dest, string name, bool copyStyle, bool keepFormula XSSFRow destRow = (XSSFRow)newSheet.CreateRow(i); if (srcRow != null) { - CopyRow(this, newSheet, srcRow, destRow, styleMap, keepFormulas); + // avoid O(N^2) performance scanning through all regions for each row + // merged regions will be copied after all the rows have been copied + CopyRow(this, newSheet, srcRow, destRow, styleMap, keepFormulas, keepMergedRegion: false); } } + // Copying merged regions + foreach (var srcRegion in this.MergedRegions) + { + var destRegion = srcRegion.Copy(); + newSheet.AddMergedRegion(destRegion); + } + List srcCols = worksheet.GetColsList(); List dstCols = newSheet.worksheet.GetColsList(); dstCols.Clear(); //Should already be empty since this is a new sheet. @@ -5628,7 +5637,7 @@ private XSSFPictureData FindPicture(IList sheetPictures, str return null; } - private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow srcRow, XSSFRow destRow, IDictionary styleMap, bool keepFormulas) + private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow srcRow, XSSFRow destRow, IDictionary styleMap, bool keepFormulas, bool keepMergedRegion) { destRow.Height = srcRow.Height; if (!srcRow.GetCTRow().IsSetCustomHeight()) @@ -5664,22 +5673,26 @@ private static void CopyRow(XSSFSheet srcSheet, XSSFSheet destSheet, XSSFRow src } CopyCell(oldCell, newCell, styleMap, keepFormulas); - CellRangeAddress mergedRegion = srcSheet.GetMergedRegion( - new CellRangeAddress(srcRow.RowNum, srcRow.RowNum, - (short)oldCell.ColumnIndex, - (short)oldCell.ColumnIndex)); - - if (mergedRegion != null) + + if (keepMergedRegion) { - CellRangeAddress newMergedRegion = new CellRangeAddress( - mergedRegion.FirstRow, - mergedRegion.LastRow, - mergedRegion.FirstColumn, - mergedRegion.LastColumn); + CellRangeAddress mergedRegion = srcSheet.GetMergedRegion( + new CellRangeAddress(srcRow.RowNum, srcRow.RowNum, + (short)oldCell.ColumnIndex, + (short)oldCell.ColumnIndex)); - if (!destSheet.IsMergedRegion(newMergedRegion)) + if (mergedRegion != null) { - destSheet.AddMergedRegion(newMergedRegion); + CellRangeAddress newMergedRegion = new CellRangeAddress( + mergedRegion.FirstRow, + mergedRegion.LastRow, + mergedRegion.FirstColumn, + mergedRegion.LastColumn); + + if (!destSheet.IsMergedRegion(newMergedRegion)) + { + destSheet.AddMergedRegion(newMergedRegion); + } } } } diff --git a/testcases/main/HSSF/UserModel/TestCopySheet.cs b/testcases/main/HSSF/UserModel/TestHSSFSheetCopy.cs similarity index 73% rename from testcases/main/HSSF/UserModel/TestCopySheet.cs rename to testcases/main/HSSF/UserModel/TestHSSFSheetCopy.cs index d8a6cb1d9..1b50ef396 100644 --- a/testcases/main/HSSF/UserModel/TestCopySheet.cs +++ b/testcases/main/HSSF/UserModel/TestHSSFSheetCopy.cs @@ -1,13 +1,14 @@ using System.IO; - +using System.Linq; using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; +using NPOI.SS.Util; using NUnit.Framework; namespace TestCases.HSSF.UserModel { [TestFixture] - public class TestCopySheet + public class TestHSSFSheetCopy { [Test] public void TestBasicCopySheet() @@ -124,5 +125,66 @@ public void TestImageCopy() Assert.IsTrue(sanityCheck.GetAllPictures().Count == 2); } } + + [Test] + public void CopySheetToWorkbookShouldCopyFormulasOver() + { + HSSFWorkbook srcWorkbook = new HSSFWorkbook(); + HSSFSheet srcSheet = srcWorkbook.CreateSheet("Sheet1") as HSSFSheet; + + // Set some values + IRow row1 = srcSheet.CreateRow((short)0); + ICell cell = row1.CreateCell((short)0); + cell.SetCellValue(1); + ICell cell2 = row1.CreateCell((short)1); + cell2.SetCellFormula("A1+1"); + HSSFWorkbook destWorkbook = new HSSFWorkbook(); + srcSheet.CopyTo(destWorkbook, srcSheet.SheetName, true, true); + + var destSheet = destWorkbook.GetSheet("Sheet1"); + Assert.NotNull(destSheet); + + Assert.AreEqual(1, destSheet.GetRow(0)?.GetCell(0).NumericCellValue); + Assert.AreEqual("A1+1", destSheet.GetRow(0)?.GetCell(1).CellFormula); + + destSheet.GetRow(0)?.GetCell(0).SetCellValue(10); + var evaluator = destWorkbook.GetCreationHelper() + .CreateFormulaEvaluator(); + + var destCell = destSheet.GetRow(0)?.GetCell(1); + evaluator.EvaluateFormulaCell(destCell); + var destCellValue = evaluator.Evaluate(destCell); + + Assert.AreEqual(11, destCellValue.NumberValue); + } + + [Test] + public void CopySheetToWorkbookShouldCopyMergedRegionsOver() + { + HSSFWorkbook srcWorkbook = new HSSFWorkbook(); + HSSFSheet srcSheet = srcWorkbook.CreateSheet("Sheet1") as HSSFSheet; + + // Set some merged regions + srcSheet.AddMergedRegion(CellRangeAddress.ValueOf("A1:B4")); + srcSheet.AddMergedRegion(CellRangeAddress.ValueOf("C1:F40")); + + + HSSFWorkbook destWorkbook = new HSSFWorkbook(); + srcSheet.CopyTo(destWorkbook, srcSheet.SheetName, true, true); + + var destSheet = destWorkbook.GetSheet("Sheet1"); + Assert.NotNull(destSheet); + Assert.AreEqual(2, destSheet.MergedRegions.Count); + + Assert.IsTrue( + new string[] + { + "A1:B4", + "C1:F40" + } + .SequenceEqual( + destSheet.MergedRegions + .Select(r => r.FormatAsString()))); + } } } diff --git a/testcases/ooxml/XSSF/UserModel/TestXSSFSheetCopyTo.cs b/testcases/ooxml/XSSF/UserModel/TestXSSFSheetCopyTo.cs new file mode 100644 index 000000000..377bcd2fc --- /dev/null +++ b/testcases/ooxml/XSSF/UserModel/TestXSSFSheetCopyTo.cs @@ -0,0 +1,92 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for Additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +using NPOI.SS.UserModel; +using NPOI.SS.Util; +using NPOI.XSSF.UserModel; +using NUnit.Framework; +using System; +using System.Linq; +using TestCases.SS.UserModel; + +namespace TestCases.XSSF.UserModel +{ + [TestFixture] + public class TestXSSFSheetCopyTo + { + [Test] + public void CopySheetToWorkbookShouldCopyFormulasOver() + { + XSSFWorkbook srcWorkbook = new XSSFWorkbook(); + XSSFSheet srcSheet = srcWorkbook.CreateSheet("Sheet1") as XSSFSheet; + + // Set some values + IRow row1 = srcSheet.CreateRow((short)0); + ICell cell = row1.CreateCell((short)0); + cell.SetCellValue(1); + ICell cell2 = row1.CreateCell((short)1); + cell2.SetCellFormula("A1+1"); + XSSFWorkbook destWorkbook = new XSSFWorkbook(); + srcSheet.CopyTo(destWorkbook, srcSheet.SheetName, true, true); + + var destSheet = destWorkbook.GetSheet("Sheet1"); + Assert.NotNull(destSheet); + + Assert.AreEqual(1, destSheet.GetRow(0)?.GetCell(0).NumericCellValue); + Assert.AreEqual("A1+1", destSheet.GetRow(0)?.GetCell(1).CellFormula); + + destSheet.GetRow(0)?.GetCell(0).SetCellValue(10); + var evaluator = destWorkbook.GetCreationHelper() + .CreateFormulaEvaluator(); + + var destCell = destSheet.GetRow(0)?.GetCell(1); + evaluator.EvaluateFormulaCell(destCell); + var destCellValue = evaluator.Evaluate(destCell); + + Assert.AreEqual(11, destCellValue.NumberValue); + } + + [Test] + public void CopySheetToWorkbookShouldCopyMergedRegionsOver() + { + XSSFWorkbook srcWorkbook = new XSSFWorkbook(); + XSSFSheet srcSheet = srcWorkbook.CreateSheet("Sheet1") as XSSFSheet; + + // Set some merged regions + srcSheet.AddMergedRegion(CellRangeAddress.ValueOf("A1:B4")); + srcSheet.AddMergedRegion(CellRangeAddress.ValueOf("C1:F40")); + + + XSSFWorkbook destWorkbook = new XSSFWorkbook(); + srcSheet.CopyTo(destWorkbook, srcSheet.SheetName, true, true); + + var destSheet = destWorkbook.GetSheet("Sheet1"); + Assert.NotNull(destSheet); + Assert.AreEqual(2, destSheet.MergedRegions.Count); + + Assert.IsTrue( + new string[] + { + "A1:B4", + "C1:F40" + } + .SequenceEqual( + destSheet.MergedRegions + .Select(r => r.FormatAsString()))); + } + } +} \ No newline at end of file