diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index c0eb9bd20a..3a33a75cdd 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -24,11 +24,13 @@ 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.ss.SpreadsheetVersion; +import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.util.Configurator; +import org.apache.poi.util.NotImplemented; /** * High level representation of a row of a spreadsheet. @@ -715,4 +717,58 @@ public final class HSSFRow implements Row, Comparable { public int hashCode() { return row.hashCode(); } + + /** + * Shifts column range [firstShiftColumnIndex-lastShiftColumnIndex] step places to the right. + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param step length of the shifting step + */ + @Override + public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + if(step < 0) + throw new IllegalArgumentException("Shifting step may not be negative "); + if(firstShiftColumnIndex > lastShiftColumnIndex) + throw new IllegalArgumentException(String.format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); + if(lastShiftColumnIndex + step + 1> cells.length) + extend(lastShiftColumnIndex + step + 1); + for (int columnIndex = lastShiftColumnIndex; columnIndex >= firstShiftColumnIndex; columnIndex--){ // process cells backwards, because of shifting + HSSFCell cell = getCell(columnIndex); + cells[columnIndex+step] = null; + if(cell != null) + moveCell(cell, (short)(columnIndex+step)); + } + for (int columnIndex = firstShiftColumnIndex; columnIndex <= firstShiftColumnIndex+step-1; columnIndex++) + cells[columnIndex] = null; + } + private void extend(int newLenght){ + HSSFCell[] temp = cells.clone(); + cells = new HSSFCell[newLenght]; + System.arraycopy(temp, 0, cells, 0, temp.length); + } + /** + * Shifts column range [firstShiftColumnIndex-lastShiftColumnIndex] step places to the left. + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param step length of the shifting step + */ + @Override + public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + if(step < 0) + throw new IllegalArgumentException("Shifting step may not be negative "); + if(firstShiftColumnIndex > lastShiftColumnIndex) + throw new IllegalArgumentException(String.format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); + if(firstShiftColumnIndex - step < 0) + throw new IllegalStateException("Column index less than zero : " + (Integer.valueOf(firstShiftColumnIndex + step)).toString()); + for (int columnIndex = firstShiftColumnIndex; columnIndex <= lastShiftColumnIndex; columnIndex++){ + HSSFCell cell = getCell(columnIndex); + if(cell != null){ + cells[columnIndex-step] = null; + moveCell(cell, (short)(columnIndex-step)); + } + else cells[columnIndex-step] = null; + } + for (int columnIndex = lastShiftColumnIndex-step+1; columnIndex <= lastShiftColumnIndex; columnIndex++) + cells[columnIndex] = null; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index 162c42b0a5..de3b05c1e5 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -50,6 +50,8 @@ import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor; import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; +import org.apache.poi.hssf.usermodel.helpers.HSSFColumnShifter; + import org.apache.poi.hssf.usermodel.helpers.HSSFRowShifter; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaShifter; @@ -73,6 +75,7 @@ import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.PaneInformation; import org.apache.poi.ss.util.SSCellRange; import org.apache.poi.ss.util.SheetUtil; +import org.apache.poi.util.Beta; import org.apache.poi.util.Configurator; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -1647,16 +1650,18 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { // Re-compute the first and last rows of the sheet as needed recomputeFirstAndLastRowsForRowShift(startRow, endRow, n); + int sheetIndex = _workbook.getSheetIndex(this); + short externSheetIndex = _book.checkExternSheet(sheetIndex); + String sheetName = _workbook.getSheetName(sheetIndex); + FormulaShifter formulaShifter = FormulaShifter.createForRowShift( + externSheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL97); // Update formulas that refer to rows that have been moved - updateFormulasForRowShift(startRow, endRow, n); + updateFormulasForShift(formulaShifter); } - private void updateFormulasForRowShift(int startRow, int endRow, int n) { + private void updateFormulasForShift(FormulaShifter formulaShifter) { int sheetIndex = _workbook.getSheetIndex(this); - String sheetName = _workbook.getSheetName(sheetIndex); short externSheetIndex = _book.checkExternSheet(sheetIndex); - FormulaShifter formulaShifter = FormulaShifter.createForRowShift( - externSheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL97); // update formulas on this sheet that point to rows which have been moved _sheet.updateFormulasAfterCellShift(formulaShifter, externSheetIndex); @@ -1737,6 +1742,31 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } } } + + /** + * Shifts columns in range [startColumn, endColumn] for n places to the right. + * For n < 0, it will shift columns left. + * Additionally adjusts formulas. + * Probably should also process other features (hyperlinks, comments...) in the way analog to shiftRows method + * @param startRow the row to start shifting + * @param endRow the row to end shifting + * @param n the number of rows to shift + */ + + @Beta + @Override + public void shiftColumns(int startColumn, int endColumn, int n){ + HSSFColumnShifter columnShifter = new HSSFColumnShifter(this); + columnShifter.shiftColumns(startColumn, endColumn, n); + + int sheetIndex = _workbook.getSheetIndex(this); + short externSheetIndex = _book.checkExternSheet(sheetIndex); + String sheetName = _workbook.getSheetName(sheetIndex); + FormulaShifter formulaShifter = FormulaShifter.createForColumnShift( + externSheetIndex, sheetName, startColumn, endColumn, n, SpreadsheetVersion.EXCEL97); + updateFormulasForShift(formulaShifter); + // add logic for hyperlinks etc, like in shiftRows() + } protected void insertChartRecords(List records) { int window2Loc = _sheet.findFirstRecordLocBySid(WindowTwoRecord.sid); diff --git a/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFColumnShifter.java b/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFColumnShifter.java index 2e9af38f2d..d6b84d6588 100644 --- a/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFColumnShifter.java +++ b/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFColumnShifter.java @@ -65,4 +65,4 @@ public final class HSSFColumnShifter extends ColumnShifter { throw new NotImplementedException("updateHyperlinks"); } -} +} \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java b/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java index 31f8834b62..f63f22ee08 100644 --- a/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java +++ b/src/java/org/apache/poi/hssf/usermodel/helpers/HSSFRowShifter.java @@ -20,6 +20,8 @@ package org.apache.poi.hssf.usermodel.helpers; 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.Row; +import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.helpers.RowShifter; import org.apache.poi.util.NotImplemented; import org.apache.poi.util.POILogFactory; diff --git a/src/java/org/apache/poi/ss/formula/FormulaShifter.java b/src/java/org/apache/poi/ss/formula/FormulaShifter.java index 345f2dff54..ed2589d754 100644 --- a/src/java/org/apache/poi/ss/formula/FormulaShifter.java +++ b/src/java/org/apache/poi/ss/formula/FormulaShifter.java @@ -5,9 +5,7 @@ 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. @@ -838,4 +836,4 @@ public final class FormulaShifter { throw new IllegalArgumentException("Unexpected ref ptg class (" + ptg.getClass().getName() + ")"); } -} +} \ No newline at end of file diff --git a/src/java/org/apache/poi/ss/usermodel/Row.java b/src/java/org/apache/poi/ss/usermodel/Row.java index 3595cd65ef..ff27f4e826 100644 --- a/src/java/org/apache/poi/ss/usermodel/Row.java +++ b/src/java/org/apache/poi/ss/usermodel/Row.java @@ -234,4 +234,7 @@ public interface Row extends Iterable { * you take it out of them. */ public int getOutlineLevel(); + + public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step); + public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step); } diff --git a/src/java/org/apache/poi/ss/usermodel/Sheet.java b/src/java/org/apache/poi/ss/usermodel/Sheet.java index 586207ebb2..2d77479677 100644 --- a/src/java/org/apache/poi/ss/usermodel/Sheet.java +++ b/src/java/org/apache/poi/ss/usermodel/Sheet.java @@ -703,6 +703,17 @@ public interface Sheet extends Iterable { */ void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight); + /** + * Shifts columns between startColumn and endColumn, n number of columns. + * If you use a negative number, it will shift columns left. + * Code ensures that columns don't wrap around + * + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param n the number of columns to shift + */ + void shiftColumns(int startColumn, int endColumn, int n); + /** * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. *

diff --git a/src/java/org/apache/poi/ss/usermodel/helpers/ColumnShifter.java b/src/java/org/apache/poi/ss/usermodel/helpers/ColumnShifter.java index a4d2508b66..c6d97f3ff6 100644 --- a/src/java/org/apache/poi/ss/usermodel/helpers/ColumnShifter.java +++ b/src/java/org/apache/poi/ss/usermodel/helpers/ColumnShifter.java @@ -22,6 +22,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.util.Beta; @@ -119,4 +120,18 @@ public abstract class ColumnShifter extends BaseRowColShifter { // if the merged-region and the overwritten area intersect, we need to remove it return merged.intersects(overwrite); } + + public void shiftColumns(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + if(step > 0){ + for (Row row : sheet) + if(row != null) + row.shiftCellsRight(firstShiftColumnIndex, lastShiftColumnIndex, step); + } + else if(step < 0){ + for (Row row : sheet) + if(row != null) + row.shiftCellsLeft(firstShiftColumnIndex, lastShiftColumnIndex, -step); + } + //else step == 0 => nothing to shift + } } diff --git a/src/java/org/apache/poi/ss/usermodel/helpers/RowShifter.java b/src/java/org/apache/poi/ss/usermodel/helpers/RowShifter.java index 4597bd1976..a27a474196 100644 --- a/src/java/org/apache/poi/ss/usermodel/helpers/RowShifter.java +++ b/src/java/org/apache/poi/ss/usermodel/helpers/RowShifter.java @@ -35,11 +35,11 @@ import org.apache.poi.ss.util.CellRangeAddress; public abstract class RowShifter extends BaseRowColShifter { protected final Sheet sheet; - public RowShifter(Sheet sh) { - sheet = sh; - } - - /** + public RowShifter(Sheet sh) { + sheet = sh; + } + + /** * Shifts, grows, or shrinks the merged regions due to a row shift. * Merged regions that are completely overlaid by shifting will be deleted. * diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java index 868885c9e9..9884ce0935 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java @@ -24,12 +24,14 @@ import java.util.SortedMap; import java.util.TreeMap; import org.apache.poi.ss.SpreadsheetVersion; +import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.util.Internal; +import org.apache.poi.util.NotImplemented; /** * Streaming version of XSSFRow implementing the "BigGridDemo" strategy. @@ -545,6 +547,16 @@ public class SXSSFRow implements Row, Comparable return _cells.hashCode(); } + @Override + @NotImplemented + public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + throw new NotImplementedException("shiftCellsRight"); + } + @Override + @NotImplemented + public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + throw new NotImplementedException("shiftCellsLeft"); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java index 4442c87515..6530fafec9 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -2119,4 +2119,10 @@ public class SXSSFSheet implements Sheet color.setIndexed(colorIndex); pr.setTabColor(color); } + + @NotImplemented + @Override + public void shiftColumns(int startColumn, int endColumn, int n){ + throw new UnsupportedOperationException("NotImplemented"); + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java index 27c8dd5bb2..cc732cd2ba 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java @@ -49,6 +49,7 @@ import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.Removal; import org.apache.poi.xssf.model.SharedStringsTable; import org.apache.poi.xssf.model.StylesTable; +import org.apache.poi.xssf.model.CalculationChain; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCellFormula; import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCellFormulaType; @@ -1324,4 +1325,22 @@ public final class XSSFCell implements Cell { "You cannot change part of an array."; notifyArrayFormulaChanging(msg); } + + + //Moved from XSSFRow.shift(). Not sure what is purpose. + public void updateCellReferencesForShifting(String msg){ + if(isPartOfArrayFormulaGroup()) + notifyArrayFormulaChanging(msg); + CalculationChain calcChain = getSheet().getWorkbook().getCalculationChain(); + int sheetId = (int)getSheet().sheet.getSheetId(); + + //remove the reference in the calculation chain + if(calcChain != null) calcChain.removeItem(sheetId, getReference()); + + CTCell ctCell = getCTCell(); + String r = new CellReference(getRowIndex(), getColumnIndex()).formatAsString(); + ctCell.setR(r); + } + } + \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java index 6c4956dba0..311faacde0 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRow.java @@ -30,10 +30,8 @@ import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.util.CellRangeAddress; -import org.apache.poi.ss.util.CellReference; import org.apache.poi.util.Beta; import org.apache.poi.util.Internal; -import org.apache.poi.xssf.model.CalculationChain; import org.apache.poi.xssf.model.StylesTable; import org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCell; @@ -227,7 +225,6 @@ public class XSSFRow implements Row, Comparable { _cells.put(colI, xcell); return xcell; } - /** * Returns the cell at the given (0 based) index, * with the {@link org.apache.poi.ss.usermodel.Row.MissingCellPolicy} from the parent Workbook. @@ -554,23 +551,11 @@ public class XSSFRow implements Row, Comparable { */ protected void shift(int n) { int rownum = getRowNum() + n; - CalculationChain calcChain = _sheet.getWorkbook().getCalculationChain(); - int sheetId = (int)_sheet.sheet.getSheetId(); String msg = "Row[rownum="+getRowNum()+"] contains cell(s) included in a multi-cell array formula. " + "You cannot change part of an array."; for(Cell c : this){ - XSSFCell cell = (XSSFCell)c; - if(cell.isPartOfArrayFormulaGroup()){ - cell.notifyArrayFormulaChanging(msg); - } - - //remove the reference in the calculation chain - if(calcChain != null) calcChain.removeItem(sheetId, cell.getReference()); - - CTCell ctCell = cell.getCTCell(); - String r = new CellReference(rownum, cell.getColumnIndex()).formatAsString(); - ctCell.setR(r); - } + ((XSSFCell)c).updateCellReferencesForShifting(msg); + } setRowNum(rownum); } @@ -620,13 +605,14 @@ public class XSSFRow implements Row, Comparable { destCell.copyCellFrom(srcCell, policy); } - final XSSFRowShifter rowShifter = new XSSFRowShifter(_sheet); 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 XSSFRowShifter rowShifter = new XSSFRowShifter(_sheet); rowShifter.updateRowFormulas(this, formulaShifter); // Copy merged regions that are fully contained on the row @@ -652,4 +638,68 @@ public class XSSFRow implements Row, Comparable { public int getOutlineLevel() { return _row.getOutlineLevel(); } + + /** + * Shifts column range [firstShiftColumnIndex-lastShiftColumnIndex] step places to the right. + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param step length of the shifting step + */ + @Override + public void shiftCellsRight(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + if(step < 0) + throw new IllegalArgumentException("Shifting step may not be negative "); + if(firstShiftColumnIndex > lastShiftColumnIndex) + throw new IllegalArgumentException(String.format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); + for (int columnIndex = lastShiftColumnIndex; columnIndex >= firstShiftColumnIndex; columnIndex--){ // process cells backwards, because of shifting + shiftCell(columnIndex, step); + } + for (int columnIndex = firstShiftColumnIndex; columnIndex <= firstShiftColumnIndex+step-1; columnIndex++) + { + _cells.remove(columnIndex); + XSSFCell targetCell = getCell(columnIndex); + if(targetCell != null) + targetCell.getCTCell().set(CTCell.Factory.newInstance()); + } + } + /** + * Shifts column range [firstShiftColumnIndex-lastShiftColumnIndex] step places to the left. + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param step length of the shifting step + */ + @Override + public void shiftCellsLeft(int firstShiftColumnIndex, int lastShiftColumnIndex, int step){ + if(step < 0) + throw new IllegalArgumentException("Shifting step may not be negative "); + if(firstShiftColumnIndex > lastShiftColumnIndex) + throw new IllegalArgumentException(String.format("Incorrect shifting range : %d-%d", firstShiftColumnIndex, lastShiftColumnIndex)); + if(firstShiftColumnIndex - step < 0) + throw new IllegalStateException("Column index less than zero : " + (Integer.valueOf(firstShiftColumnIndex + step)).toString()); + for (int columnIndex = firstShiftColumnIndex; columnIndex <= lastShiftColumnIndex; columnIndex++){ + shiftCell(columnIndex, -step); + } + for (int columnIndex = lastShiftColumnIndex-step+1; columnIndex <= lastShiftColumnIndex; columnIndex++){ + _cells.remove(columnIndex); + XSSFCell targetCell = getCell(columnIndex); + if(targetCell != null) + targetCell.getCTCell().set(CTCell.Factory.newInstance()); + } + } + private void shiftCell(int columnIndex, int step/*pass negative value for left shift*/){ + if(columnIndex + step < 0) // only for shifting left + throw new IllegalStateException("Column index less than zero : " + (Integer.valueOf(columnIndex + step)).toString()); + + XSSFCell currentCell = getCell(columnIndex); + if(currentCell != null){ + currentCell.setCellNum(columnIndex+step); + _cells.put(columnIndex+step, currentCell); + } + else { + _cells.remove(columnIndex+step); + XSSFCell targetCell = getCell(columnIndex+step); + if(targetCell != null) + targetCell.getCTCell().set(CTCell.Factory.newInstance()); + } + } } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index 259c31445e..f4329abdc4 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -84,6 +84,7 @@ import org.apache.poi.util.Units; import org.apache.poi.xssf.model.CommentsTable; import org.apache.poi.xssf.usermodel.XSSFPivotTable.PivotTableReferenceConfigurator; import org.apache.poi.xssf.usermodel.helpers.ColumnHelper; +import org.apache.poi.xssf.usermodel.helpers.XSSFColumnShifter; import org.apache.poi.xssf.usermodel.helpers.XSSFIgnoredErrorHelper; import org.apache.poi.xssf.usermodel.helpers.XSSFRowShifter; import org.apache.xmlbeans.XmlCursor; @@ -2985,7 +2986,65 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { public void shiftRows(int startRow, int endRow, final int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { XSSFVMLDrawing vml = getVMLDrawing(false); - // first remove all rows which will be overwritten + int sheetIndex = getWorkbook().getSheetIndex(this); + String sheetName = getWorkbook().getSheetName(sheetIndex); + FormulaShifter formulaShifter = FormulaShifter.createForRowShift( + sheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL2007); + removeOverwritten(vml, startRow, endRow, n); + shiftCommentsAndRows(vml, startRow, endRow, n); + + XSSFRowShifter rowShifter = new XSSFRowShifter(this); + rowShifter.shiftMergedRegions(startRow, endRow, n); + rowShifter.updateNamedRanges(formulaShifter); + rowShifter.updateFormulas(formulaShifter); + rowShifter.updateConditionalFormatting(formulaShifter); + rowShifter.updateHyperlinks(formulaShifter); + + //rebuild the _rows map + Map map = new HashMap<>(); + for(XSSFRow r : _rows.values()) { + // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory + final Integer rownumI = new Integer(r.getRowNum()); // NOSONAR + map.put(rownumI, r); + } + _rows.clear(); + _rows.putAll(map); + } + + /** + * Shifts columns between startColumn and endColumn n number of columns. + * If you use a negative number, it will shift columns left. + * Code ensures that columns don't wrap around + * + * @param startColumn the column to start shifting + * @param endColumn the column to end shifting + * @param n length of the shifting step + */ + @Override + public void shiftColumns(int startColumn, int endColumn, final int n) { + XSSFVMLDrawing vml = getVMLDrawing(false); + shiftCommentsForColumns(vml, startColumn, endColumn, n); + FormulaShifter formulaShifter = FormulaShifter.createForColumnShift(this.getWorkbook().getSheetIndex(this), this.getSheetName(), startColumn, endColumn, n, SpreadsheetVersion.EXCEL2007); + XSSFColumnShifter columnShifter = new XSSFColumnShifter(this); + columnShifter.shiftColumns(startColumn, endColumn, n); + columnShifter.shiftMergedRegions(startColumn, startColumn, n); + columnShifter.updateFormulas(formulaShifter); + columnShifter.updateConditionalFormatting(formulaShifter); + columnShifter.updateHyperlinks(formulaShifter); + columnShifter.updateNamedRanges(formulaShifter); + + //rebuild the _rows map + Map map = new HashMap<>(); + for(XSSFRow r : _rows.values()) { + final Integer rownumI = new Integer(r.getRowNum()); // NOSONAR + map.put(rownumI, r); + } + _rows.clear(); + _rows.putAll(map); + } + + // remove all rows which will be overwritten + private void removeOverwritten(XSSFVMLDrawing vml, int startRow, int endRow, final int n){ for (Iterator it = rowIterator() ; it.hasNext() ; ) { XSSFRow row = (XSSFRow)it.next(); int rownum = row.getRowNum(); @@ -3029,102 +3088,86 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { } } - // then do the actual moving and also adjust comments/rowHeight - // we need to sort it in a way so the shifting does not mess up the structures, - // i.e. when shifting down, start from down and go up, when shifting up, vice-versa - SortedMap commentsToShift = new TreeMap<>(new Comparator() { - @Override - public int compare(XSSFComment o1, XSSFComment o2) { - int row1 = o1.getRow(); - int row2 = o2.getRow(); - - if (row1 == row2) { - // ordering is not important when row is equal, but don't return zero to still - // get multiple comments per row into the map - return o1.hashCode() - o2.hashCode(); - } - - // when shifting down, sort higher row-values first - if (n > 0) { - return row1 < row2 ? 1 : -1; - } else { - // sort lower-row values first when shifting up - return row1 > row2 ? 1 : -1; - } - } - }); - - - for (Iterator it = rowIterator() ; it.hasNext() ; ) { - XSSFRow row = (XSSFRow)it.next(); - int rownum = row.getRowNum(); - - if(sheetComments != null){ - // calculate the new rownum - int newrownum = shiftedRowNum(startRow, endRow, n, rownum); - - // is there a change necessary for the current row? - if(newrownum != rownum) { - CTCommentList lst = sheetComments.getCTComments().getCommentList(); - for (CTComment comment : lst.getCommentArray()) { - String oldRef = comment.getRef(); - CellReference ref = new CellReference(oldRef); - - // is this comment part of the current row? - if(ref.getRow() == rownum) { - XSSFComment xssfComment = new XSSFComment(sheetComments, comment, - vml == null ? null : vml.findCommentShape(rownum, ref.getCol())); - - // we should not perform the shifting right here as we would then find - // already shifted comments and would shift them again... - commentsToShift.put(xssfComment, newrownum); - } - } - } - } - - if(rownum < startRow || rownum > endRow) { - continue; - } - - if (!copyRowHeight) { - row.setHeight((short)-1); - } - - row.shift(n); - } - - // adjust all the affected comment-structures now - // the Map is sorted and thus provides them in the order that we need here, - // i.e. from down to up if shifting down, vice-versa otherwise - for(Map.Entry entry : commentsToShift.entrySet()) { - entry.getKey().setRow(entry.getValue()); - } - - XSSFRowShifter rowShifter = new XSSFRowShifter(this); - - int sheetIndex = getWorkbook().getSheetIndex(this); - String sheetName = getWorkbook().getSheetName(sheetIndex); - FormulaShifter shifter = FormulaShifter.createForRowShift( - sheetIndex, sheetName, startRow, endRow, n, SpreadsheetVersion.EXCEL2007); - - rowShifter.updateNamedRanges(shifter); - rowShifter.updateFormulas(shifter); - rowShifter.shiftMergedRegions(startRow, endRow, n); - rowShifter.updateConditionalFormatting(shifter); - rowShifter.updateHyperlinks(shifter); - - //rebuild the _rows map - Map map = new HashMap<>(); - for(XSSFRow r : _rows.values()) { - // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory - final Integer rownumI = Integer.valueOf(r.getRowNum()); // NOSONAR - map.put(rownumI, r); - } - _rows.clear(); - _rows.putAll(map); } + private void shiftCommentsAndRows(XSSFVMLDrawing vml, int startRow, int endRow, final int n){ + // then do the actual moving and also adjust comments/rowHeight + // we need to sort it in a way so the shifting does not mess up the structures, + // i.e. when shifting down, start from down and go up, when shifting up, vice-versa + SortedMap commentsToShift = new TreeMap<>(new Comparator() { + @Override + public int compare(XSSFComment o1, XSSFComment o2) { + int row1 = o1.getRow(); + int row2 = o2.getRow(); + + if (row1 == row2) { + // ordering is not important when row is equal, but don't return zero to still + // get multiple comments per row into the map + return o1.hashCode() - o2.hashCode(); + } + + // when shifting down, sort higher row-values first + if (n > 0) { + return row1 < row2 ? 1 : -1; + } else { + // sort lower-row values first when shifting up + return row1 > row2 ? 1 : -1; + } + } + }); + + + for (Iterator it = rowIterator() ; it.hasNext() ; ) { + XSSFRow row = (XSSFRow)it.next(); + int rownum = row.getRowNum(); + + if(sheetComments != null){ + // calculate the new rownum + int newrownum = shiftedRowNum(startRow, endRow, n, rownum); + + // is there a change necessary for the current row? + if(newrownum != rownum) { + CTCommentList lst = sheetComments.getCTComments().getCommentList(); + for (CTComment comment : lst.getCommentArray()) { + String oldRef = comment.getRef(); + CellReference ref = new CellReference(oldRef); + + // is this comment part of the current row? + if(ref.getRow() == rownum) { + XSSFComment xssfComment = new XSSFComment(sheetComments, comment, + vml == null ? null : vml.findCommentShape(rownum, ref.getCol())); + + // we should not perform the shifting right here as we would then find + // already shifted comments and would shift them again... + commentsToShift.put(xssfComment, newrownum); + } + } + } + } + + if(rownum < startRow || rownum > endRow) { + continue; + } + row.shift(n); + } + // adjust all the affected comment-structures now + // the Map is sorted and thus provides them in the order that we need here, + // i.e. from down to up if shifting down, vice-versa otherwise + for(Map.Entry entry : commentsToShift.entrySet()) { + entry.getKey().setRow(entry.getValue()); + } + + //rebuild the _rows map + Map map = new HashMap<>(); + for(XSSFRow r : _rows.values()) { + // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory + final Integer rownumI = Integer.valueOf(r.getRowNum()); // NOSONAR + map.put(rownumI, r); + } + _rows.clear(); + _rows.putAll(map); + + } private int shiftedRowNum(int startRow, int endRow, int n, int rownum) { // no change if before any affected row if(rownum < startRow && (n > 0 || (startRow - rownum) > n)) { @@ -3151,6 +3194,66 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { // row is part of the shifted block return rownum + n; } + private void shiftCommentsForColumns(XSSFVMLDrawing vml, int startColumnIndex, int endColumnIndex, final int n){ + // then do the actual moving and also adjust comments/rowHeight + // we need to sort it in a way so the shifting does not mess up the structures, + // i.e. when shifting down, start from down and go up, when shifting up, vice-versa + SortedMap commentsToShift = new TreeMap<>(new Comparator() { + @Override + public int compare(XSSFComment o1, XSSFComment o2) { + int column1 = o1.getColumn(); + int column2 = o2.getColumn(); + + if (column1 == column2) { + // ordering is not important when row is equal, but don't return zero to still + // get multiple comments per row into the map + return o1.hashCode() - o2.hashCode(); + } + + // when shifting down, sort higher row-values first + if (n > 0) { + return column1 < column2 ? 1 : -1; + } else { + // sort lower-row values first when shifting up + return column1 > column2 ? 1 : -1; + } + } + }); + + + if(sheetComments != null){ + CTCommentList lst = sheetComments.getCTComments().getCommentList(); + for (CTComment comment : lst.getCommentArray()) { + String oldRef = comment.getRef(); + CellReference ref = new CellReference(oldRef); + + int columnIndex =ref.getCol(); + int newColumnIndex = shiftedRowNum(startColumnIndex, endColumnIndex, n, columnIndex); + if(newColumnIndex != columnIndex){ + XSSFComment xssfComment = new XSSFComment(sheetComments, comment, + vml == null ? null : vml.findCommentShape(ref.getRow(), columnIndex)); + commentsToShift.put(xssfComment, newColumnIndex); + } + } + } + // adjust all the affected comment-structures now + // the Map is sorted and thus provides them in the order that we need here, + // i.e. from down to up if shifting down, vice-versa otherwise + for(Map.Entry entry : commentsToShift.entrySet()) { + entry.getKey().setColumn(entry.getValue()); + } + + //rebuild the _rows map + Map map = new HashMap<>(); + for(XSSFRow r : _rows.values()) { + // Performance optimization: explicit boxing is slightly faster than auto-unboxing, though may use more memory + final Integer rownumI = Integer.valueOf(r.getRowNum()); // NOSONAR + map.put(rownumI, r); + } + _rows.clear(); + _rows.putAll(map); + + } /** * Location of the top left visible cell Location of the top left visible cell in the bottom right diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java index 5cfd38294a..88a5e4109c 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFVMLDrawing.java @@ -264,7 +264,7 @@ public final class XSSFVMLDrawing extends POIXMLDocumentPart { * * @return the comment shape or null */ - protected CTShape findCommentShape(int row, int col){ + public CTShape findCommentShape(int row, int col){ for(XmlObject itm : _items){ if(itm instanceof CTShape){ CTShape sh = (CTShape)itm; diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java index ebcb095d6f..d7628bcf42 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java @@ -36,7 +36,6 @@ public final class XSSFRowShifter extends RowShifter { public XSSFRowShifter(XSSFSheet sh) { super(sh); } - @Override public void updateNamedRanges(FormulaShifter formulaShifter) { XSSFRowColShifter.updateNamedRanges(sheet, formulaShifter); @@ -46,7 +45,7 @@ public final class XSSFRowShifter extends RowShifter { public void updateFormulas(FormulaShifter formulaShifter) { XSSFRowColShifter.updateFormulas(sheet, formulaShifter); } - + /** * Update the formulas in specified row using the formula shifting policy specified by shifter * diff --git a/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFRow.java b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFRow.java index 90a730d195..ee237cc9b0 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFRow.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFRow.java @@ -22,6 +22,8 @@ package org.apache.poi.xssf.streaming; import org.apache.poi.ss.usermodel.BaseTestXRow; import org.apache.poi.xssf.SXSSFITestDataProvider; import org.junit.After; +import org.junit.Ignore; +import org.junit.Test; /** * Tests for XSSFRow @@ -38,4 +40,16 @@ public final class TestSXSSFRow extends BaseTestXRow { ((SXSSFITestDataProvider) _testDataProvider).cleanup(); } + @Override + @Ignore ("see ") @Test + public void testCellShiftingRight(){ + // Remove when SXSSFRow.shiftCellsRight() is implemented. + } + @Override + @Ignore ("see ") @Test + public void testCellShiftingLeft(){ + // Remove when SXSSFRow.shiftCellsLeft() is implemented. + } + + } diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java new file mode 100644 index 0000000000..698411baa9 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheetShiftColumns.java @@ -0,0 +1,24 @@ +package org.apache.poi.xssf.usermodel; + +import java.io.IOException; + +import org.apache.poi.ss.usermodel.BaseTestSheetShiftColumns; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.XSSFITestDataProvider; +import org.apache.poi.xssf.XSSFTestDataSamples; + +public class TestXSSFSheetShiftColumns extends BaseTestSheetShiftColumns { + public TestXSSFSheetShiftColumns(){ + super(); + workbook = new XSSFWorkbook(); + _testDataProvider = XSSFITestDataProvider.instance; + } + + protected Workbook openWorkbook(String spreadsheetFileName) throws IOException{ + return XSSFTestDataSamples.openSampleWorkbook(spreadsheetFileName); + } + protected Workbook getReadBackWorkbook(Workbook wb){ + return XSSFTestDataSamples.writeOutAndReadBack(wb); + } + +} diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java new file mode 100644 index 0000000000..de913f3bb7 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/helpers/TestXSSFColumnShifting.java @@ -0,0 +1,18 @@ +package org.apache.poi.xssf.usermodel.helpers; + +import org.apache.poi.ss.usermodel.BaseTestColumnShifting; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +public class TestXSSFColumnShifting extends BaseTestColumnShifting{ + public TestXSSFColumnShifting(){ + super(); + wb = new XSSFWorkbook(); + } + @Override + protected void initColumnShifter(){ + columnShifter = new XSSFColumnShifter((XSSFSheet)sheet1); + } + + +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFColumnShifting.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFColumnShifting.java new file mode 100644 index 0000000000..c99b6c159b --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFColumnShifting.java @@ -0,0 +1,16 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.hssf.usermodel.helpers.HSSFColumnShifter; +import org.apache.poi.ss.usermodel.BaseTestColumnShifting; + +public class TestHSSFColumnShifting extends BaseTestColumnShifting { + public TestHSSFColumnShifting(){ + super(); + wb = new HSSFWorkbook(); + } + @Override + protected void initColumnShifter(){ + columnShifter = new HSSFColumnShifter((HSSFSheet)sheet1); + } + +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java new file mode 100644 index 0000000000..7b45872118 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheetShiftColumns.java @@ -0,0 +1,69 @@ +package org.apache.poi.hssf.usermodel; + +import java.io.IOException; + +import org.apache.poi.hssf.HSSFITestDataProvider; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.ss.usermodel.BaseTestSheetShiftColumns; +import org.apache.poi.ss.usermodel.Workbook; +import org.junit.Ignore; +import org.junit.Test; + +public class TestHSSFSheetShiftColumns extends BaseTestSheetShiftColumns { + public TestHSSFSheetShiftColumns(){ + super(); + workbook = new HSSFWorkbook(); + _testDataProvider = HSSFITestDataProvider.instance; + } + + protected Workbook openWorkbook(String spreadsheetFileName) + throws IOException { + return HSSFTestDataSamples.openSampleWorkbook(spreadsheetFileName); + } + + protected Workbook getReadBackWorkbook(Workbook wb) throws IOException { + return HSSFTestDataSamples.writeOutAndReadBack((HSSFWorkbook)wb); + } + + @Override + @Ignore("see ") + @Test + public void shiftMergedColumnsToMergedColumnsLeft() throws IOException { + // This override is used only in order to test failing for HSSF. Please remove method after code is fixed on hssf, + // so that original method from BaseTestSheetShiftColumns can be executed. + } + @Override + @Ignore("see ") + @Test + public void shiftMergedColumnsToMergedColumnsRight() throws IOException { + // This override is used only in order to test failing for HSSF. Please remove method after code is fixed on hssf, + // so that original method from BaseTestSheetShiftColumns can be executed. + } + @Override + @Ignore("see ") + @Test + public void testBug54524() throws IOException { + // This override is used only in order to test failing for HSSF. Please remove method after code is fixed on hssf, + // so that original method from BaseTestSheetShiftColumns can be executed. + } + @Override + @Ignore("see ") + @Test + public void testCommentsShifting() throws IOException { + // This override is used only in order to test failing for HSSF. Please remove method after code is fixed on hssf, + // so that original method from BaseTestSheetShiftColumns can be executed. + } + @Override + @Ignore("see ") + @Test + public void testShiftWithMergedRegions() throws IOException { + // This override is used only in order to test failing for HSSF. Please remove method after code is fixed on hssf, + // so that original method from BaseTestSheetShiftColumns can be executed. + // After removing, you can re-add 'final' keyword to specification of original method. + } + + @Override + @Ignore("see ") + @Test + public void testShiftHyperlinks() throws IOException {} +} diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestColumnShifting.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestColumnShifting.java new file mode 100644 index 0000000000..6ae6067a58 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestColumnShifting.java @@ -0,0 +1,69 @@ +package org.apache.poi.ss.usermodel; + +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.apache.poi.ss.usermodel.helpers.ColumnShifter; + +public class BaseTestColumnShifting { + protected Workbook wb; + protected Sheet sheet1; + protected ColumnShifter columnShifter; + + @Before + public void init() { + int rowIndex = 0; + sheet1 = wb.createSheet("sheet1"); + Row row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.NUMERIC).setCellValue(0); + row.createCell(3, CellType.NUMERIC).setCellValue(3); + row.createCell(4, CellType.NUMERIC).setCellValue(4); + + row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.NUMERIC).setCellValue(0.1); + row.createCell(1, CellType.NUMERIC).setCellValue(1.1); + row.createCell(2, CellType.NUMERIC).setCellValue(2.1); + row.createCell(3, CellType.NUMERIC).setCellValue(3.1); + row.createCell(4, CellType.NUMERIC).setCellValue(4.1); + row.createCell(5, CellType.NUMERIC).setCellValue(5.1); + row.createCell(6, CellType.NUMERIC).setCellValue(6.1); + row.createCell(7, CellType.NUMERIC).setCellValue(7.1); + row = sheet1.createRow(rowIndex++); + row.createCell(3, CellType.NUMERIC).setCellValue(3.2); + row.createCell(5, CellType.NUMERIC).setCellValue(5.2); + row.createCell(7, CellType.NUMERIC).setCellValue(7.2); + + initColumnShifter(); + } + protected void initColumnShifter(){ + + } + + @Test + public void testShift3ColumnsRight() { + columnShifter.shiftColumns(1, 2, 3); + + Cell cell = sheet1.getRow(0).getCell(4); + assertNull(cell); + cell = sheet1.getRow(1).getCell(4); + assertEquals(1.1, cell.getNumericCellValue(), 0.01); + cell = sheet1.getRow(1).getCell(5); + assertEquals(2.1, cell.getNumericCellValue(), 0.01); + cell = sheet1.getRow(2).getCell(4); + assertNull(cell); + } + + @Test + public void testShiftLeft() { + try { + columnShifter.shiftColumns(1, 2, -3); + assertTrue("Shift to negative indices should throw exception", false); + } + catch(IllegalStateException e){ + assertTrue(true); + } + } +} diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestRow.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestRow.java index 27ac4c93a8..ae88bf63a9 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestRow.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestRow.java @@ -466,4 +466,69 @@ public abstract class BaseTestRow { wb2.close(); } + + @Test + public void testCellShiftingRight() { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet("sheet1"); + Row row = sheet.createRow(0); + row.createCell(0, CellType.NUMERIC).setCellValue(0); + row.createCell(1, CellType.NUMERIC).setCellValue(1); + row.createCell(2, CellType.NUMERIC).setCellValue(2);//C + row.createCell(3, CellType.NUMERIC).setCellValue(3);//D + row.createCell(4, CellType.NUMERIC).setCellValue(4);//E + row.createCell(5, CellType.NUMERIC).setCellValue(5);//F + row.createCell(6, CellType.NUMERIC).setCellValue(6);//G + try { + row.shiftCellsLeft(6, 4, 2); // range [6-4] is illegal + fail("expected shiftLeft to fail"); + } catch (IllegalArgumentException e){ + row.shiftCellsRight(2, 4, 1); + //should be [0.0, 1.0, null, 2.0, 3.0, 4.0, 6.0, null] + + Cell h1 = row.getCell(7); + assertNull(h1); + Cell g1 = row.getCell(6); + assertEquals(6, g1.getNumericCellValue(), 0.01); + Cell f1 = row.getCell(5); + assertEquals(4, f1.getNumericCellValue(), 0.01); + Cell e1 = row.getCell(4); + assertEquals(3, e1.getNumericCellValue(), 0.01); + Cell d1 = row.getCell(3); + assertEquals(2, d1.getNumericCellValue(), 0.01); + Cell c1 = row.getCell(2); + assertNull(c1); + } + } + @Test + public void testCellShiftingLeft() { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet("sheet1"); + Row row = sheet.createRow(0); + row.createCell(0, CellType.NUMERIC).setCellValue(0); + row.createCell(1, CellType.NUMERIC).setCellValue(1); + row.createCell(2, CellType.NUMERIC).setCellValue(2);//C + row.createCell(3, CellType.NUMERIC).setCellValue(3);//D + row.createCell(4, CellType.NUMERIC).setCellValue(4);//E + row.createCell(5, CellType.NUMERIC).setCellValue(5);//F + row.createCell(6, CellType.NUMERIC).setCellValue(6);//G + try { + row.shiftCellsLeft(4, 6, -2); // step = -1 is illegal + fail("expected shiftLeft to fail"); + } catch (IllegalArgumentException e){ + row.shiftCellsLeft(4, 6, 2); + //should be [0.0, 1.0, 4.0, 5.0, 6.0, null, null, null] + + Cell b1 = row.getCell(1); + assertEquals(1, b1.getNumericCellValue(), 0.01); + Cell c1 = row.getCell(2); + assertEquals(4, c1.getNumericCellValue(), 0.01); + Cell d1 = row.getCell(3); + assertEquals(5, d1.getNumericCellValue(), 0.01); + Cell e1 = row.getCell(4); + assertEquals(6, e1.getNumericCellValue(), 0.01); + Cell f1 = row.getCell(5); + assertNull(f1); + } + } } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java new file mode 100644 index 0000000000..23483d3fc7 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheetShiftColumns.java @@ -0,0 +1,388 @@ +package org.apache.poi.ss.usermodel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.apache.poi.common.usermodel.HyperlinkType; +import org.apache.poi.ss.ITestDataProvider; +import org.apache.poi.ss.util.CellAddress; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellUtil; +import org.junit.Before; +import org.junit.Test; + +public abstract class BaseTestSheetShiftColumns { + protected Sheet sheet1; + protected Sheet sheet2; + protected Workbook workbook; + + protected ITestDataProvider _testDataProvider; + + public BaseTestSheetShiftColumns(){ + } + + @Before + public void init() { + int rowIndex = 0; + sheet1 = workbook.createSheet("sheet1"); + Row row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.NUMERIC).setCellValue(0); + row.createCell(1, CellType.NUMERIC).setCellValue(1); + row.createCell(2, CellType.NUMERIC).setCellValue(2); + + row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.NUMERIC).setCellValue(0.1); + row.createCell(1, CellType.NUMERIC).setCellValue(1.1); + row.createCell(2, CellType.NUMERIC).setCellValue(2.1); + row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.NUMERIC).setCellValue(0.2); + row.createCell(1, CellType.NUMERIC).setCellValue(1.2); + row.createCell(2, CellType.NUMERIC).setCellValue(2.2); + row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.FORMULA).setCellFormula("A2*B3"); + row.createCell(1, CellType.NUMERIC).setCellValue(1.3); + row.createCell(2, CellType.FORMULA).setCellFormula("B1-B3"); + row = sheet1.createRow(rowIndex++); + row.createCell(0, CellType.FORMULA).setCellFormula("SUM(C1:C4)"); + row.createCell(1, CellType.FORMULA).setCellFormula("SUM(A3:C3)"); + row.createCell(2, CellType.FORMULA).setCellFormula("$C1+C$2"); + row = sheet1.createRow(rowIndex++); + row.createCell(1, CellType.NUMERIC).setCellValue(1.5); + row = sheet1.createRow(rowIndex); + row.createCell(1, CellType.BOOLEAN).setCellValue(false); + Cell textCell = row.createCell(2, CellType.STRING); + textCell.setCellValue("TEXT"); + textCell.setCellStyle(newCenterBottomStyle()); + + sheet2 = workbook.createSheet("sheet2"); + row = sheet2.createRow(0); row.createCell(0, CellType.NUMERIC).setCellValue(10); + row.createCell(1, CellType.NUMERIC).setCellValue(11); + row.createCell(2, CellType.FORMULA).setCellFormula("SUM(sheet1!B3:C3)"); + row = sheet2.createRow(1); + row.createCell(0, CellType.NUMERIC).setCellValue(21); + row.createCell(1, CellType.NUMERIC).setCellValue(22); + row.createCell(2, CellType.NUMERIC).setCellValue(23); + row = sheet2.createRow(2); + row.createCell(0, CellType.FORMULA).setCellFormula("sheet1!A4+sheet1!C2+A2"); + row.createCell(1, CellType.FORMULA).setCellFormula("SUM(sheet1!A3:$C3)"); + row = sheet2.createRow(3); + row.createCell(0, CellType.STRING).setCellValue("dummy"); + } + + private CellStyle newCenterBottomStyle(){ + CellStyle style = workbook.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.BOTTOM); + return style; + } + + @Test + public void testShiftOneColumnRight() { + sheet1.shiftColumns(1, 2, 1); + double c1Value = sheet1.getRow(0).getCell(2).getNumericCellValue(); + assertEquals(1d, c1Value, 0.01); + String formulaA4 = sheet1.getRow(3).getCell(0).getCellFormula(); + assertEquals("A2*C3", formulaA4); + String formulaC4 = sheet1.getRow(3).getCell(3).getCellFormula(); + assertEquals("C1-C3", formulaC4); + String formulaB5 = sheet1.getRow(4).getCell(2).getCellFormula(); + assertEquals("SUM(A3:D3)", formulaB5); + String formulaD5 = sheet1.getRow(4).getCell(3).getCellFormula(); // $C1+C$2 + assertEquals("$D1+D$2", formulaD5); + + Cell newb5Null = sheet1.getRow(4).getCell(1); + assertEquals(newb5Null, null); + boolean logicalValue = sheet1.getRow(6).getCell(2).getBooleanCellValue(); + assertEquals(logicalValue, false); + Cell textCell = sheet1.getRow(6).getCell(3); + assertEquals(textCell.getStringCellValue(), "TEXT"); + assertEquals(textCell.getCellStyle().getAlignment(), HorizontalAlignment.CENTER); + + // other sheet + String formulaC1 = sheet2.getRow(0).getCell(2).getCellFormula(); // SUM(sheet1!B3:C3) + assertEquals("SUM(sheet1!C3:D3)", formulaC1); + String formulaA3 = sheet2.getRow(2).getCell(0).getCellFormula(); // sheet1!A4+sheet1!C2+A2 + assertEquals("sheet1!A4+sheet1!D2+A2", formulaA3); + } + + @Test + public void testShiftTwoColumnsRight() { + sheet1.shiftColumns(1, 2, 2); + String formulaA4 = sheet1.getRow(3).getCell(0).getCellFormula(); + assertEquals("A2*D3", formulaA4); + String formulaD4 = sheet1.getRow(3).getCell(4).getCellFormula(); + assertEquals("D1-D3", formulaD4); + String formulaD5 = sheet1.getRow(4).getCell(3).getCellFormula(); + assertEquals("SUM(A3:E3)", formulaD5); + + Cell b5Null = sheet1.getRow(4).getCell(1); + assertEquals(b5Null, null); + Object c6Null = sheet1.getRow(5).getCell(2); // null cell A5 is shifted + // for 2 columns, so now + // c5 should be null + assertEquals(c6Null, null); + } + + @Test + public void testShiftOneColumnLeft() { + sheet1.shiftColumns(1, 2, -1); + + String formulaA5 = sheet1.getRow(4).getCell(0).getCellFormula(); + assertEquals("SUM(A3:B3)", formulaA5); + String formulaB4 = sheet1.getRow(3).getCell(1).getCellFormula(); + assertEquals("A1-A3", formulaB4); + String formulaB5 = sheet1.getRow(4).getCell(1).getCellFormula(); + assertEquals("$B1+B$2", formulaB5); + Cell newb6Null = sheet1.getRow(5).getCell(1); + assertEquals(newb6Null, null); + } + + @Test(expected = IllegalStateException.class) + public void testShiftTwoColumnsLeft() { + sheet1.shiftColumns(1, 2, -2); + } + + @Test + public void testShiftHyperlinks() throws IOException { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet("test"); + Row row = sheet.createRow(0); + + // How to create hyperlinks + // https://poi.apache.org/spreadsheet/quick-guide.html#Hyperlinks + CreationHelper helper = wb.getCreationHelper(); + CellStyle hlinkStyle = wb.createCellStyle(); + Font hlinkFont = wb.createFont(); + hlinkFont.setUnderline(Font.U_SINGLE); + hlinkFont.setColor(IndexedColors.BLUE.getIndex()); + hlinkStyle.setFont(hlinkFont); + + // 3D relative document link + // CellAddress=A1, shifted to A4 + Cell cell = row.createCell(0); + cell.setCellStyle(hlinkStyle); + createHyperlink(helper, cell, HyperlinkType.DOCUMENT, "test!E1"); + + // URL + cell = row.createCell(1); + // CellAddress=B1, shifted to B4 + cell.setCellStyle(hlinkStyle); + createHyperlink(helper, cell, HyperlinkType.URL, "http://poi.apache.org/"); + + // row0 will be shifted on top of row1, so this URL should be removed + // from the workbook + Row overwrittenRow = sheet.createRow(3); + cell = overwrittenRow.createCell(2); + // CellAddress=C4, will be overwritten (deleted) + cell.setCellStyle(hlinkStyle); + createHyperlink(helper, cell, HyperlinkType.EMAIL, "mailto:poi@apache.org"); + + Row unaffectedRow = sheet.createRow(20); + cell = unaffectedRow.createCell(3); + // CellAddress=D21, will be unaffected + cell.setCellStyle(hlinkStyle); + createHyperlink(helper, cell, HyperlinkType.FILE, "54524.xlsx"); + + cell = wb.createSheet("other").createRow(0).createCell(0); + // CellAddress=Other!A1, will be unaffected + cell.setCellStyle(hlinkStyle); + createHyperlink(helper, cell, HyperlinkType.URL, "http://apache.org/"); + + int startRow = 0; + int endRow = 4; + int n = 3; + sheet.shiftColumns(startRow, endRow, n); + + Workbook read = _testDataProvider.writeOutAndReadBack(wb); + wb.close(); + + Sheet sh = read.getSheet("test"); + + Row shiftedRow = sh.getRow(0); + + // document link anchored on a shifted cell should be moved + // Note that hyperlinks do not track what they point to, so this + // hyperlink should still refer to test!E1 + verifyHyperlink(shiftedRow.getCell(3), HyperlinkType.DOCUMENT, "test!E1"); + + // URL, EMAIL, and FILE links anchored on a shifted cell should be moved + verifyHyperlink(shiftedRow.getCell(4), HyperlinkType.URL, "http://poi.apache.org/"); + + // Make sure hyperlinks were moved and not copied + assertNull("Document hyperlink should be moved, not copied", sh.getHyperlink(0, 0)); + assertNull("URL hyperlink should be moved, not copied", sh.getHyperlink(1, 0)); + + assertEquals(4, sh.getHyperlinkList().size()); + read.close(); + } + + private void createHyperlink(CreationHelper helper, Cell cell, HyperlinkType linkType, String ref) { + cell.setCellValue(ref); + Hyperlink link = helper.createHyperlink(linkType); + link.setAddress(ref); + cell.setHyperlink(link); + } + + private void verifyHyperlink(Cell cell, HyperlinkType linkType, String ref) { + assertTrue(cellHasHyperlink(cell)); + if (cell != null) { + Hyperlink link = cell.getHyperlink(); + assertEquals(linkType, link.getType()); + assertEquals(ref, link.getAddress()); + } + } + + private boolean cellHasHyperlink(Cell cell) { + return (cell != null) && (cell.getHyperlink() != null); + } + + @Test + public void shiftMergedColumnsToMergedColumnsRight() throws IOException { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet("test"); + + // populate sheet cells + populateSheetCells(sheet); + CellRangeAddress A1_A5 = new CellRangeAddress(0, 4, 0, 0); // NOSONAR, it's more readable this way + CellRangeAddress B1_B3 = new CellRangeAddress(0, 2, 1, 1); // NOSONAR, it's more readable this way + + sheet.addMergedRegion(B1_B3); + sheet.addMergedRegion(A1_A5); + + // A1:A5 should be moved to B1:B5 + // B1:B3 will be removed + sheet.shiftColumns(0, 0, 1); + + assertEquals(1, sheet.getNumMergedRegions()); + assertEquals(CellRangeAddress.valueOf("B1:B5"), sheet.getMergedRegion(0)); + + wb.close(); + } + + @Test + public void shiftMergedColumnsToMergedColumnsLeft() throws IOException { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet("test"); + populateSheetCells(sheet); + + CellRangeAddress A1_A5 = new CellRangeAddress(0, 4, 0, 0); // NOSONAR, it's more readable this way + CellRangeAddress B1_B3 = new CellRangeAddress(0, 2, 1, 1); // NOSONAR, it's more readable this way + + sheet.addMergedRegion(A1_A5); + sheet.addMergedRegion(B1_B3); + + // A1:E1 should be removed + // B1:B3 will be A1:A3 + sheet.shiftColumns(1, 5, -1); + + assertEquals(1, sheet.getNumMergedRegions()); + assertEquals(CellRangeAddress.valueOf("A1:A3"), sheet.getMergedRegion(0)); + + wb.close(); + } + + private void populateSheetCells(Sheet sheet) { + // populate sheet cells + for (int i = 0; i < 2; i++) { + Row row = sheet.createRow(i); + for (int j = 0; j < 5; j++) { + Cell cell = row.createCell(j); + cell.setCellValue(i + "x" + j); + } + } + } + + @Test + public void testShiftWithMergedRegions() throws IOException { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet(); + Row row = sheet.createRow(0); + row.createCell(0).setCellValue(1.1); + row = sheet.createRow(1); + row.createCell(0).setCellValue(2.2); + CellRangeAddress region = new CellRangeAddress(0, 2, 0, 0); + assertEquals("A1:A3", region.formatAsString()); + + sheet.addMergedRegion(region); + + sheet.shiftColumns(0, 1, 2); + region = sheet.getMergedRegion(0); + assertEquals("C1:C3", region.formatAsString()); + wb.close(); + } + + protected abstract Workbook openWorkbook(String spreadsheetFileName) throws IOException; + protected abstract Workbook getReadBackWorkbook(Workbook wb) throws IOException; + + protected static final String AMDOCS = "Amdocs"; + protected static final String AMDOCS_TEST = "Amdocs:\ntest\n"; + + @Test + public void testCommentsShifting() throws IOException { + Workbook inputWb = openWorkbook("56017.xlsx"); + + Sheet sheet = inputWb.getSheetAt(0); + Comment comment = sheet.getCellComment(new CellAddress(0, 0)); + assertNotNull(comment); + assertEquals(AMDOCS, comment.getAuthor()); + assertEquals(AMDOCS_TEST, comment.getString().getString()); + + sheet.shiftColumns(0, 1, 1); + + // comment in column 0 is gone + comment = sheet.getCellComment(new CellAddress(0, 0)); + assertNull(comment); + + // comment is column in column 1 + comment = sheet.getCellComment(new CellAddress(0, 1)); + assertNotNull(comment); + assertEquals(AMDOCS, comment.getAuthor()); + assertEquals(AMDOCS_TEST, comment.getString().getString()); + + Workbook wbBack = getReadBackWorkbook(inputWb); + inputWb.close(); + assertNotNull(wbBack); + + Sheet sheetBack = wbBack.getSheetAt(0); + + // comment in column 0 is gone + comment = sheetBack.getCellComment(new CellAddress(0, 0)); + assertNull(comment); + + // comment is now in column 1 + comment = sheetBack.getCellComment(new CellAddress(0, 1)); + assertNotNull(comment); + assertEquals(AMDOCS, comment.getAuthor()); + assertEquals(AMDOCS_TEST, comment.getString().getString()); + wbBack.close(); + } + + // transposed version of TestXSSFSheetShiftRows.testBug54524() + @Test + public void testBug54524() throws IOException { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet = wb.createSheet(); + Row firstRow = sheet.createRow(0); + firstRow.createCell(0).setCellValue(""); + firstRow.createCell(1).setCellValue(1); + firstRow.createCell(2).setCellValue(2); + firstRow.createCell(3).setCellFormula("SUM(B1:C1)"); + firstRow.createCell(4).setCellValue("X"); + + sheet.shiftColumns(3, 5, -1); + + Cell cell = CellUtil.getCell(sheet.getRow(0), 1); + assertEquals(1.0, cell.getNumericCellValue(), 0); + cell = CellUtil.getCell(sheet.getRow(0), 2); + assertEquals("SUM(B1:B1)", cell.getCellFormula()); + cell = CellUtil.getCell(sheet.getRow(0), 3); + assertEquals("X", cell.getStringCellValue()); + wb.close(); + } + + +}