]> source.dussan.org Git - poi.git/commitdiff
support copying to hssf row
authorPJ Fanning <fanningpj@apache.org>
Wed, 6 Oct 2021 19:06:12 +0000 (19:06 +0000)
committerPJ Fanning <fanningpj@apache.org>
Wed, 6 Oct 2021 19:06:12 +0000 (19:06 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1893945 13f79535-47bb-0310-9956-ffa450edef68

poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFCell.java
poi-ooxml/src/main/java/org/apache/poi/xssf/usermodel/XSSFRow.java
poi/src/main/java/org/apache/poi/hssf/usermodel/HSSFRow.java
poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowColShifter.java [new file with mode: 0644]
poi/src/main/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java
poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFRow.java

index 99d33ffafba6c20ca9522a411c00bf449a1fe2b0..a1d33c9600a31462112d085f2ac3aa85eef17b26 100644 (file)
@@ -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 {
     }
 
 }
-
index 7cf05c2ccfa95fa5860d9f7701abfc77f164e0b9..b0f02eaa546715114084ed3c11a4d3ef9816fee7 100644 (file)
@@ -176,7 +176,7 @@ public class XSSFRow implements Row, Comparable<XSSFRow> {
         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<XSSFRow> {
      */
     @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<XSSFRow> {
     @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<XSSFRow> {
             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
index eb1be9c208ef303ff798f9670134100438bcccd7..e73e0a5365ade7fad4e9b107c37ada3e31ee9edb 100644 (file)
 
 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<HSSFRow> {
             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<Integer> 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 (file)
index 0000000..df26093
--- /dev/null
@@ -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,
+     * <code>null</code> 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;
+        }
+    }
+
+}
index 4a743556a45a306bcac76955b5886cac7fea9ed2..cfd4934595ce9720c2e8e3b98835aa868c331f6c 100644 (file)
@@ -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);
+    }
+
 }
index d58a00f83833c52133d976de545b90647e8ff38f..46a1c2e089f573a3501fb1b5378dc3f8d9b15dc4 100644 (file)
@@ -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();
+    }
 }