From 38d4834ba72b7bfd402c48149a1050b4a23329c2 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Wed, 6 Oct 2021 19:06:12 +0000 Subject: [PATCH] support copying to hssf row git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1893945 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/xssf/usermodel/XSSFCell.java | 29 +++-- .../apache/poi/xssf/usermodel/XSSFRow.java | 30 ++--- .../apache/poi/hssf/usermodel/HSSFRow.java | 97 +++++++++++++++ .../usermodel/helpers/HSSFRowColShifter.java | 115 ++++++++++++++++++ .../usermodel/helpers/HSSFRowShifter.java | 13 ++ .../poi/hssf/usermodel/TestHSSFRow.java | 100 ++++++++++++++- 6 files changed, 353 insertions(+), 31 deletions(-) create mode 100644 poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 99d33ffafb..a1d33c9600 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -240,17 +240,17 @@ public final class XSSFCell extends CellBase { return 0.0; case NUMERIC: if(_cell.isSetV()) { - String v = _cell.getV(); - if (v.isEmpty()) { - return 0.0; - } - try { - return Double.parseDouble(v); - } catch(NumberFormatException e) { - throw typeMismatch(CellType.NUMERIC, CellType.STRING, false); - } + String v = _cell.getV(); + if (v.isEmpty()) { + return 0.0; + } + try { + return Double.parseDouble(v); + } catch(NumberFormatException e) { + throw typeMismatch(CellType.NUMERIC, CellType.STRING, false); + } } else { - return 0.0; + return 0.0; } case FORMULA: throw new AssertionError(); @@ -586,7 +586,7 @@ public final class XSSFCell extends CellBase { */ private boolean isFormulaCell() { return (_cell.isSetF() && _cell.getF().getT() != STCellFormulaType.DATA_TABLE) - || getSheet().isCellInArrayFormulaContext(this); + || getSheet().isCellInArrayFormulaContext(this); } /** @@ -645,7 +645,7 @@ public final class XSSFCell extends CellBase { case STCellType.INT_S: // String is in shared strings case STCellType.INT_INLINE_STR: // String is inline in cell case STCellType.INT_STR: - return CellType.STRING; + return CellType.STRING; default: throw new IllegalStateException("Illegal cell type: " + this._cell.getT()); } @@ -972,7 +972,7 @@ public final class XSSFCell extends CellBase { /** * Removes the comment for this cell, if there is one. - */ + */ @Override public void removeCellComment() { XSSFComment comment = getCellComment(); @@ -1125,7 +1125,7 @@ public final class XSSFCell extends CellBase { return FALSE; } throw new IllegalStateException("Unexpected boolean cached formula value '" - + textValue + "'."); + + textValue + "'."); case STRING: // fall-through @@ -1174,4 +1174,3 @@ public final class XSSFCell extends CellBase { } } - diff --git a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java index 7cf05c2ccf..b0f02eaa54 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java +++ b/poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java @@ -176,7 +176,7 @@ public class XSSFRow implements Row, Comparable { XSSFRow other = (XSSFRow) obj; return (this.getRowNum() == other.getRowNum()) && - (this.getSheet() == other.getSheet()); + (this.getSheet() == other.getSheet()); } @Override @@ -463,16 +463,16 @@ public class XSSFRow implements Row, Comparable { */ @Override public XSSFCellStyle getRowStyle() { - if(!isFormatted()) { - return null; - } + if(!isFormatted()) { + return null; + } - StylesTable stylesSource = getSheet().getWorkbook().getStylesSource(); - if(stylesSource.getNumCellStyles() > 0) { - return stylesSource.getStyleAt(Math.toIntExact(_row.getS())); - } else { - return null; - } + StylesTable stylesSource = getSheet().getWorkbook().getStylesSource(); + if(stylesSource.getNumCellStyles() > 0) { + return stylesSource.getStyleAt(Math.toIntExact(_row.getS())); + } else { + return null; + } } /** @@ -483,10 +483,10 @@ public class XSSFRow implements Row, Comparable { @Override public void setRowStyle(CellStyle style) { if(style == null) { - if(_row.isSetS()) { - _row.unsetS(); - _row.unsetCustomFormat(); - } + if(_row.isSetS()) { + _row.unsetS(); + _row.unsetCustomFormat(); + } } else { StylesTable styleSource = getSheet().getWorkbook().getStylesSource(); @@ -519,7 +519,7 @@ public class XSSFRow implements Row, Comparable { xcell.setCellFormula(null); // to remove the array formula } if(cell.getCellType() == CellType.FORMULA) { - _sheet.getWorkbook().onDeleteFormula(xcell); + _sheet.getWorkbook().onDeleteFormula(xcell); } // Performance optimization for bug 57840: explicit boxing is slightly faster than auto-unboxing, though may use more memory final Integer colI = Integer.valueOf(cell.getColumnIndex()); // NOSONAR diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFRow.java index eb1be9c208..e73e0a5365 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -17,18 +17,28 @@ package org.apache.poi.hssf.usermodel; +import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Set; import org.apache.poi.hssf.record.CellValueRecordInterface; import org.apache.poi.hssf.record.ExtendedFormatRecord; import org.apache.poi.hssf.record.RowRecord; +import org.apache.poi.hssf.usermodel.helpers.HSSFRowShifter; import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellCopyContext; +import org.apache.poi.ss.usermodel.CellCopyPolicy; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.FormulaError; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.helpers.RowShifter; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellUtil; +import org.apache.poi.util.Beta; import org.apache.poi.util.Configurator; /** @@ -775,4 +785,91 @@ public final class HSSFRow implements Row, Comparable { cells[columnIndex] = null; } } + + /** + * Copy the cells from srcRow to this row + * If this row is not a blank row, this will merge the two rows, overwriting + * the cells in this row with the cells in srcRow + * If srcRow is null, overwrite cells in destination row with blank values, styles, etc per cell copy policy + * srcRow may be from a different sheet in the same workbook + * @param srcRow the rows to copy from + * @param policy the policy to determine what gets copied + */ + @Beta + public void copyRowFrom(Row srcRow, CellCopyPolicy policy) { + copyRowFrom(srcRow, policy, null); + } + + /** + * Copy the cells from srcRow to this row + * If this row is not a blank row, this will merge the two rows, overwriting + * the cells in this row with the cells in srcRow + * If srcRow is null, overwrite cells in destination row with blank values, styles, etc per cell copy policy + * srcRow may be from a different sheet in the same workbook + * @param srcRow the rows to copy from + * @param policy the policy to determine what gets copied + * @param context the context - see {@link CellCopyContext} + * @since v5.1.0 + */ + @Beta + public void copyRowFrom(Row srcRow, CellCopyPolicy policy, CellCopyContext context) { + if (srcRow == null) { + // srcRow is blank. Overwrite cells with blank values, blank styles, etc per cell copy policy + for (Cell destCell : this) { + CellUtil.copyCell(null, destCell, policy, context); + } + + if (policy.isCopyMergedRegions()) { + // Remove MergedRegions in dest row + final int destRowNum = getRowNum(); + int index = 0; + final Set indices = new HashSet<>(); + for (CellRangeAddress destRegion : getSheet().getMergedRegions()) { + if (destRowNum == destRegion.getFirstRow() && destRowNum == destRegion.getLastRow()) { + indices.add(index); + } + index++; + } + getSheet().removeMergedRegions(indices); + } + + if (policy.isCopyRowHeight()) { + // clear row height + setHeight((short)-1); + } + + } else { + for (final Cell c : srcRow) { + final HSSFCell destCell = createCell(c.getColumnIndex()); + CellUtil.copyCell(c, destCell, policy, context); + } + + final int sheetIndex = sheet.getWorkbook().getSheetIndex(sheet); + final String sheetName = sheet.getWorkbook().getSheetName(sheetIndex); + final int srcRowNum = srcRow.getRowNum(); + final int destRowNum = getRowNum(); + final int rowDifference = destRowNum - srcRowNum; + + final FormulaShifter formulaShifter = FormulaShifter.createForRowCopy(sheetIndex, sheetName, srcRowNum, srcRowNum, rowDifference, SpreadsheetVersion.EXCEL2007); + final HSSFRowShifter rowShifter = new HSSFRowShifter(sheet); + rowShifter.updateRowFormulas(this, formulaShifter); + + // Copy merged regions that are fully contained on the row + // FIXME: is this something that rowShifter could be doing? + if (policy.isCopyMergedRegions()) { + for (CellRangeAddress srcRegion : srcRow.getSheet().getMergedRegions()) { + if (srcRowNum == srcRegion.getFirstRow() && srcRowNum == srcRegion.getLastRow()) { + CellRangeAddress destRegion = srcRegion.copy(); + destRegion.setFirstRow(destRowNum); + destRegion.setLastRow(destRowNum); + getSheet().addMergedRegion(destRegion); + } + } + } + + if (policy.isCopyRowHeight()) { + setHeight(srcRow.getHeight()); + } + } + } } diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java new file mode 100644 index 0000000000..df2609315f --- /dev/null +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java @@ -0,0 +1,115 @@ +/* ==================================================================== + 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. +==================================================================== */ + +package org.apache.poi.hssf.usermodel.helpers; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.formula.*; +import org.apache.poi.ss.formula.ptg.Ptg; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.util.Internal; + +import static org.apache.logging.log4j.util.Unbox.box; + +/** + * Class for code common to {@link HSSFRowShifter} and {@link HSSFColumnShifter} + * + * @since POI 5.1.0 + */ +@Internal +/*private*/ final class HSSFRowColShifter { + private static final Logger LOG = LogManager.getLogger(HSSFRowColShifter.class); + + private HSSFRowColShifter() { /*no instances for static classes*/} + + /** + * Update formulas. + */ + /*package*/ static void updateFormulas(Sheet sheet, FormulaShifter formulaShifter) { + //update formulas on the parent sheet + updateSheetFormulas(sheet,formulaShifter); + + //update formulas on other sheets + Workbook wb = sheet.getWorkbook(); + for(Sheet sh : wb) + { + if (sheet == sh) continue; + updateSheetFormulas(sh, formulaShifter); + } + } + + /*package*/ static void updateSheetFormulas(Sheet sh, FormulaShifter formulashifter) { + for (Row r : sh) { + HSSFRow row = (HSSFRow) r; + updateRowFormulas(row, formulashifter); + } + } + + /** + * Update the formulas in specified row using the formula shifting policy specified by shifter + * + * @param row the row to update the formulas on + * @param formulaShifter the formula shifting policy + */ + /*package*/ static void updateRowFormulas(HSSFRow row, FormulaShifter formulaShifter) { + HSSFSheet sheet = row.getSheet(); + for (Cell c : row) { + HSSFCell cell = (HSSFCell) c; + String formula = cell.getCellFormula(); + if (formula.length() > 0) { + String shiftedFormula = shiftFormula(row, formula, formulaShifter); + cell.setCellFormula(shiftedFormula); + } + } + } + + /** + * Shift a formula using the supplied FormulaShifter + * + * @param row the row of the cell this formula belongs to. Used to get a reference to the parent workbook. + * @param formula the formula to shift + * @param formulaShifter the FormulaShifter object that operates on the parsed formula tokens + * @return the shifted formula if the formula was changed, + * null if the formula wasn't modified + */ + /*package*/ + static String shiftFormula(Row row, String formula, FormulaShifter formulaShifter) { + Sheet sheet = row.getSheet(); + Workbook wb = sheet.getWorkbook(); + int sheetIndex = wb.getSheetIndex(sheet); + final int rowIndex = row.getRowNum(); + HSSFEvaluationWorkbook fpb = HSSFEvaluationWorkbook.create((HSSFWorkbook) wb); + + try { + Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex); + String shiftedFmla; + if (formulaShifter.adjustFormula(ptgs, sheetIndex)) { + shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs); + } else { + shiftedFmla = formula; + } + return shiftedFmla; + } catch (FormulaParseException fpe) { + // Log, but don't change, rather than breaking + LOG.atWarn().withThrowable(fpe).log("Error shifting formula on row {}", box(row.getRowNum())); + return formula; + } + } + +} diff --git a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java index 4a743556a4..cfd4934595 100644 --- a/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java +++ b/poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java @@ -19,10 +19,12 @@ package org.apache.poi.hssf.usermodel.helpers; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.usermodel.helpers.RowShifter; +import org.apache.poi.util.Internal; import org.apache.poi.util.NotImplemented; /** @@ -61,4 +63,15 @@ public final class HSSFRowShifter extends RowShifter { throw new NotImplementedException("updateHyperlinks"); } + /** + * Update the formulas in specified row using the formula shifting policy specified by shifter + * + * @param row the row to update the formulas on + * @param formulaShifter the formula shifting policy + */ + @Internal(since="5.1.0") + public void updateRowFormulas(HSSFRow row, FormulaShifter formulaShifter) { + HSSFRowColShifter.updateRowFormulas(row, formulaShifter); + } + } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRow.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRow.java index d58a00f838..46a1c2e089 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRow.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRow.java @@ -29,7 +29,7 @@ import java.io.IOException; import org.apache.poi.hssf.HSSFITestDataProvider; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.RowRecord; -import org.apache.poi.ss.usermodel.BaseTestRow; +import org.apache.poi.ss.usermodel.*; import org.junit.jupiter.api.Test; /** @@ -141,4 +141,102 @@ final class TestHSSFRow extends BaseTestRow { workbook.close(); } + + @Test + void testCopyRowFromExternalSheet() throws IOException { + final HSSFWorkbook workbook = new HSSFWorkbook(); + final HSSFSheet srcSheet = workbook.createSheet("src"); + final HSSFSheet destSheet = workbook.createSheet("dest"); + workbook.createSheet("other"); + + final Row srcRow = srcSheet.createRow(0); + int col = 0; + //Test 2D and 3D Ref Ptgs (Pxg for OOXML Workbooks) + srcRow.createCell(col++).setCellFormula("B5"); + srcRow.createCell(col++).setCellFormula("src!B5"); + srcRow.createCell(col++).setCellFormula("dest!B5"); + srcRow.createCell(col++).setCellFormula("other!B5"); + + //Test 2D and 3D Ref Ptgs with absolute row + srcRow.createCell(col++).setCellFormula("B$5"); + srcRow.createCell(col++).setCellFormula("src!B$5"); + srcRow.createCell(col++).setCellFormula("dest!B$5"); + srcRow.createCell(col++).setCellFormula("other!B$5"); + + //Test 2D and 3D Area Ptgs (Pxg for OOXML Workbooks) + srcRow.createCell(col++).setCellFormula("SUM(B5:D$5)"); + srcRow.createCell(col++).setCellFormula("SUM(src!B5:D$5)"); + srcRow.createCell(col++).setCellFormula("SUM(dest!B5:D$5)"); + srcRow.createCell(col++).setCellFormula("SUM(other!B5:D$5)"); + + ////////////////// + + final int styleCount = workbook.getNumCellStyles(); + + final HSSFRow destRow = destSheet.createRow(1); + destRow.copyRowFrom(srcRow, new CellCopyPolicy()); + + ////////////////// + + //Test 2D and 3D Ref Ptgs (Pxg for OOXML Workbooks) + col = 0; + Cell cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("B6", cell.getCellFormula(), "RefPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("src!B6", cell.getCellFormula(), "Ref3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("dest!B6", cell.getCellFormula(), "Ref3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("other!B6", cell.getCellFormula(), "Ref3DPtg"); + + ///////////////////////////////////////////// + + //Test 2D and 3D Ref Ptgs with absolute row (Ptg row number shouldn't change) + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("B$5", cell.getCellFormula(), "RefPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("src!B$5", cell.getCellFormula(), "Ref3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("dest!B$5", cell.getCellFormula(), "Ref3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("other!B$5", cell.getCellFormula(), "Ref3DPtg"); + + ////////////////////////////////////////// + + //Test 2D and 3D Area Ptgs (Pxg for OOXML Workbooks) + // Note: absolute row changes from last cell to first cell in order + // to maintain topLeft:bottomRight order + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("SUM(B$5:D6)", cell.getCellFormula(), "Area2DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("SUM(src!B$5:D6)", cell.getCellFormula(), "Area3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("SUM(dest!B$5:D6)", cell.getCellFormula(), "Area3DPtg"); + + cell = destRow.getCell(col++); + assertNotNull(cell); + assertEquals("SUM(other!B$5:D6)", cell.getCellFormula(), "Area3DPtg"); + + assertEquals(styleCount, workbook.getNumCellStyles(), "no new styles should be added by copyRow"); + workbook.close(); + } }