diff --git a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java index 10ccde01fab..2a61056bee7 100644 --- a/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java +++ b/poi-ooxml/src/test/java/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java @@ -61,6 +61,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import org.apache.poi.xddf.usermodel.chart.XDDFChartData; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.model.StylesTable; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTCalcPr; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTExternalLink; @@ -79,6 +80,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more import java.util.Date; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.zip.CRC32; import static org.apache.poi.hssf.HSSFTestDataSamples.openSampleFileStream; @@ -100,6 +102,11 @@ public TestXSSFWorkbook() { super(XSSFITestDataProvider.instance); } + @BeforeAll + static void setup() { + Locale.setDefault(Locale.US); + } + /** * Tests that we can save, and then re-load a new document */ @@ -1427,6 +1434,44 @@ void testGithub321() throws Exception { } } + @Test + public void changeSheetNameWithRowRanges() throws IOException { + String sampleFile = "multiple_sheets_with_row_and_column_ranges.xlsx"; + try (Workbook wb = _testDataProvider.openSampleWorkbook(sampleFile)) { + String initialFormula = wb.getName("sheet2_rows_2_4").getRefersToFormula(); + + wb.setSheetName(0, "new_first_sheet_name"); + String newFormula = wb.getName("sheet2_rows_2_4").getRefersToFormula(); + assertEquals(initialFormula, newFormula); + + wb.setSheetName(1, "new_second_sheet_name"); + newFormula = wb.getName("sheet2_rows_2_4").getRefersToFormula(); + assertEquals(initialFormula.replace("sheet2", "new_second_sheet_name"), newFormula); + + Workbook reloaded = writeOutAndReadBack(wb); + newFormula = reloaded.getName("sheet2_rows_2_4").getRefersToFormula(); + assertEquals(initialFormula.replace("sheet2", "new_second_sheet_name"), newFormula); + } + } + @Test + public void changeSheetNameWithRowRangesColumn() throws IOException { + String sampleFile = "multiple_sheets_with_row_and_column_ranges.xlsx"; + try (Workbook wb = _testDataProvider.openSampleWorkbook(sampleFile)) { + String initialFormula = wb.getName("sheet2_columns_B_C").getRefersToFormula(); + + wb.setSheetName(0, "new_first_sheet_name"); + String newFormula = wb.getName("sheet2_columns_B_C").getRefersToFormula(); + assertEquals(initialFormula, newFormula); + + wb.setSheetName(1, "new_second_sheet_name"); + newFormula = wb.getName("sheet2_columns_B_C").getRefersToFormula(); + assertEquals(initialFormula.replace("sheet2", "new_second_sheet_name"), newFormula); + + Workbook reloaded = writeOutAndReadBack(wb); + newFormula = reloaded.getName("sheet2_columns_B_C").getRefersToFormula(); + assertEquals(initialFormula.replace("sheet2", "new_second_sheet_name"), newFormula); + } + } private static void expectFormattedContent(Cell cell, String value) { assertEquals(value, new DataFormatter().formatCellValue(cell), "Cell " + ref(cell) + " has wrong formatted content."); diff --git a/poi/src/main/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java b/poi/src/main/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java index 84cc42c81a2..f0d5a1c03d2 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java @@ -37,6 +37,10 @@ public abstract class AreaPtgBase extends OperandPtg implements AreaI { private static final BitField rowRelative = BitFieldFactory.getInstance(0x8000); private static final BitField colRelative = BitFieldFactory.getInstance(0x4000); private static final BitField columnMask = BitFieldFactory.getInstance(0x3FFF); + private boolean firstCellIsRow; + private boolean firstCellIsColumn; + private boolean lastCellIsRow; + private boolean lastCellIsColumn; /** zero based, unsigned 16 bit */ private int field_1_first_row; @@ -55,15 +59,19 @@ protected AreaPtgBase(AreaPtgBase other) { field_2_last_row = other.field_2_last_row; field_3_first_column = other.field_3_first_column; field_4_last_column = other.field_4_last_column; + firstCellIsRow = other.firstCellIsRow; + firstCellIsColumn = other.firstCellIsColumn; + lastCellIsRow = other.lastCellIsRow; + lastCellIsColumn = other.lastCellIsColumn; } protected AreaPtgBase(AreaReference ar) { CellReference firstCell = ar.getFirstCell(); CellReference lastCell = ar.getLastCell(); setFirstRow(firstCell.getRow()); - setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol()); + setFirstColumn(firstCell.getCol()); setLastRow(lastCell.getRow()); - setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol()); + setLastColumn(lastCell.getCol()); setFirstColRelative(!firstCell.isColAbsolute()); setLastColRelative(!lastCell.isColAbsolute()); setFirstRowRelative(!firstCell.isRowAbsolute()); @@ -144,7 +152,7 @@ protected final void writeCoordinates(LittleEndianOutput out) { * @return the first row in the area */ public final int getFirstRow() { - return field_1_first_row; + return firstCellIsRow ? -1 : field_1_first_row; } /** @@ -152,28 +160,30 @@ public final int getFirstRow() { * @param rowIx number (0-based) */ public final void setFirstRow(int rowIx) { - field_1_first_row = rowIx; + firstCellIsRow = rowIx == -1; + field_1_first_row = rowIx== -1 ? 0 : rowIx; } /** * @return last row in the range (x2 in x1,y1-x2,y2) */ public final int getLastRow() { - return field_2_last_row; + return lastCellIsRow ? -1 : field_2_last_row; } /** * @param rowIx last row number in the area */ public final void setLastRow(int rowIx) { - field_2_last_row = rowIx; + lastCellIsRow = rowIx == -1; + field_2_last_row = rowIx== -1 ? 0 : rowIx; } /** * @return the first column number in the area. */ public final int getFirstColumn() { - return columnMask.getValue(field_3_first_column); + return firstCellIsColumn ? -1 : columnMask.getValue(field_3_first_column); } /** @@ -216,7 +226,8 @@ public final void setFirstColRelative(boolean rel) { * set the first column in the area */ public final void setFirstColumn(int colIx) { - field_3_first_column=columnMask.setValue(field_3_first_column, colIx); + firstCellIsColumn = colIx == -1; + field_3_first_column=columnMask.setValue(field_3_first_column, colIx == -1 ? 0 : colIx); } /** @@ -230,7 +241,7 @@ public final void setFirstColumnRaw(int column) { * @return lastcolumn in the area */ public final int getLastColumn() { - return columnMask.getValue(field_4_last_column); + return lastCellIsColumn ? -1 : columnMask.getValue(field_4_last_column); } /** @@ -274,7 +285,8 @@ public final void setLastColRelative(boolean rel) { * set the last column in the area */ public final void setLastColumn(int colIx) { - field_4_last_column=columnMask.setValue(field_4_last_column, colIx); + lastCellIsColumn = colIx == -1; + field_4_last_column=columnMask.setValue(field_4_last_column, colIx == -1 ? 0 : colIx); } /** @@ -287,11 +299,13 @@ protected final String formatReferenceAsString() { CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative()); CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative()); - if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) { - return (new AreaReference(topLeft, botRight, SpreadsheetVersion.EXCEL97)).formatAsString(); - } - if (AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL2007, topLeft, botRight)){ - return new AreaReference(topLeft, botRight,SpreadsheetVersion.EXCEL2007).formatAsString(); + for (SpreadsheetVersion version : SpreadsheetVersion.values()) { + if (AreaReference.isWholeColumnReference(version, topLeft, botRight)) { + return (new AreaReference(topLeft, botRight, version)).formatAsString(); + } + if (AreaReference.isWholeRowReference(version, topLeft, botRight)) { + return (new AreaReference(topLeft, botRight, version)).formatAsString(); + } } return topLeft.formatAsString() + ":" + botRight.formatAsString(); } diff --git a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java index fc45d81b4a5..41ad428d192 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java +++ b/poi/src/main/java/org/apache/poi/ss/util/AreaReference.java @@ -182,7 +182,7 @@ public static AreaReference getWholeRow(SpreadsheetVersion version, String start if (null == version) { version = DEFAULT_SPREADSHEET_VERSION; } - return new AreaReference("$A" + start + ":$" + version.getLastColumnName() + end, version); + return new AreaReference(start + ":" + end, version); } /** @@ -221,6 +221,24 @@ public static boolean isWholeColumnReference(SpreadsheetVersion version, CellRef && botRight.isRowAbsolute()); } + /** + * Is the reference for a whole-row reference, + * such as 2:2 or 3:5 ? + */ + public static boolean isWholeRowReference(SpreadsheetVersion version, CellReference topLeft, CellReference botRight) { + if (null == version) { + version = DEFAULT_SPREADSHEET_VERSION; // how the code used to behave. + } + + // These are represented as something like + // C$1:C$65535 or D$1:F$0 + // i.e. absolute from 1st row to 0th one + return (topLeft.getCol() == 0 + && topLeft.isColAbsolute() + && botRight.getCol() == version.getLastColumnIndex() + && botRight.isColAbsolute()); + } + /** * Takes a non-contiguous area reference, and returns an array of contiguous area references * @return an array of contiguous area references. @@ -346,6 +364,10 @@ public boolean isWholeColumnReference() { return isWholeColumnReference(_version, _firstCell, _lastCell); } + public boolean isWholeRowReference() { + return isWholeRowReference(_version, _firstCell, _lastCell); + } + /** * @return {@code false} if this area reference involves more than one cell */ @@ -358,7 +380,10 @@ public boolean isSingleCell() { * left corner of the area (but this is not a requirement). */ public CellReference getFirstCell() { - return _firstCell; + if (_firstCell.getCol() == -1) { + return new CellReference(_firstCell.getRow(), 0); + } + return _firstCell; } /** @@ -369,6 +394,9 @@ public CellReference getFirstCell() { * of the area (but this is not a requirement). */ public CellReference getLastCell() { + if (_firstCell.getCol() == -1) { + return new CellReference(_lastCell.getRow(), _version.getLastColumnIndex()); + } return _lastCell; } @@ -414,11 +442,18 @@ public CellReference[] getAllReferencedCells() { */ public String formatAsString() { // Special handling for whole-column references - if(isWholeColumnReference()) { - return - CellReference.convertNumToColString(_firstCell.getCol()) - + ":" + - CellReference.convertNumToColString(_lastCell.getCol()); + if (isWholeColumnReference()) { + return _firstCell.formatAsColumnString() + + ":" + + _lastCell.formatAsColumnString(); + } + + if (isWholeRowReference()) { + StringBuilder sb = new StringBuilder(); + _firstCell.appendRowReference(sb); + sb.append(":"); + _lastCell.appendRowReference(sb); + return sb.toString(); } StringBuilder sb = new StringBuilder(32); diff --git a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java index 17498781631..1b145f99290 100644 --- a/poi/src/main/java/org/apache/poi/ss/util/CellReference.java +++ b/poi/src/main/java/org/apache/poi/ss/util/CellReference.java @@ -494,6 +494,13 @@ public String formatAsString() { return formatAsString(true); } + public String formatAsColumnString() { + StringBuilder sb = new StringBuilder(); + appendColumnReference(sb); + return sb.toString(); + } + + /** * Returns a text representation of this cell reference in R1C1 format. *
@@ -599,12 +606,20 @@ public String[] getCellRefParts() { * Sheet name is not included. */ /* package */ void appendCellReference(StringBuilder sb) { + appendColumnReference(sb); + appendRowReference(sb); + } + + public void appendColumnReference(StringBuilder sb) { if (_colIndex != -1) { if(_isColAbs) { sb.append(ABSOLUTE_REFERENCE_MARKER); } sb.append( convertNumToColString(_colIndex)); } + } + + public void appendRowReference(StringBuilder sb) { if (_rowIndex != -1) { if(_isRowAbs) { sb.append(ABSOLUTE_REFERENCE_MARKER); diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java index b9d5fcb3caa..8993246ae3a 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestBugs.java @@ -2191,8 +2191,8 @@ void bug52111() throws Exception { try (Workbook wb = openSampleWorkbook("Intersection-52111.xls")) { Sheet s = wb.getSheetAt(0); assertFormula(wb, s.getRow(2).getCell(0), "(C2:D3 D3:E4)", "4.0"); - assertFormula(wb, s.getRow(6).getCell(0), "Tabelle2!E:E Tabelle2!$A11:$IV11", "5.0"); - assertFormula(wb, s.getRow(8).getCell(0), "Tabelle2!E:F Tabelle2!$A11:$IV12", null); + assertFormula(wb, s.getRow(6).getCell(0), "Tabelle2!E:E Tabelle2!11:11", "5.0"); + assertFormula(wb, s.getRow(8).getCell(0), "Tabelle2!E:F Tabelle2!11:12", null); } } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFName.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFName.java index e3e6cea444b..21ecd4c3035 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFName.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFName.java @@ -72,9 +72,7 @@ void testRepeatingRowsAndColumnsNames() throws Exception { HSSFName nr1 = wb.getNameAt(0); assertEquals("Print_Titles", nr1.getNameName()); - // TODO - full column references not rendering properly, absolute markers not present either - // assertEquals("FirstSheet!$A:$A,FirstSheet!$1:$3", nr1.getRefersToFormula()); - assertEquals("FirstSheet!A:A,FirstSheet!$A$1:$IV$3", nr1.getRefersToFormula()); + assertEquals("FirstSheet!$A:$A,FirstSheet!$1:$3", nr1.getRefersToFormula()); // Save and re-open HSSFWorkbook nwb = HSSFTestDataSamples.writeOutAndReadBack(wb); @@ -84,7 +82,7 @@ void testRepeatingRowsAndColumnsNames() throws Exception { nr1 = nwb.getNameAt(0); assertEquals("Print_Titles", nr1.getNameName()); - assertEquals("FirstSheet!A:A,FirstSheet!$A$1:$IV$3", nr1.getRefersToFormula()); + assertEquals("FirstSheet!$A:$A,FirstSheet!$1:$3", nr1.getRefersToFormula()); // check that setting RR&C on a second sheet causes a new Print_Titles built-in // name to be created @@ -97,7 +95,7 @@ void testRepeatingRowsAndColumnsNames() throws Exception { HSSFName nr2 = nwb.getNameAt(1); assertEquals("Print_Titles", nr2.getNameName()); - assertEquals("SecondSheet!B:C,SecondSheet!$A$1:$IV$1", nr2.getRefersToFormula()); + assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula()); nwb.close(); } diff --git a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java index 94f3c4fa780..a4fd762f6a7 100644 --- a/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java +++ b/poi/src/test/java/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java @@ -531,8 +531,8 @@ void findBuiltInNameRecord() throws IOException { NameRecord nr; assertEquals(3, wb1.getWorkbook().getNumNames()); nr = wb1.getWorkbook().getNameRecord(2); - // TODO - render full row and full column refs properly - assertEquals("Sheet2!$A$1:$IV$1", HSSFFormulaParser.toFormulaString(wb1, nr.getNameDefinition())); // 1:1 + + assertEquals("Sheet2!$1:$1", HSSFFormulaParser.toFormulaString(wb1, nr.getNameDefinition())); // 1:1 try { wb1.getSheetAt(3).setRepeatingRows(CellRangeAddress.valueOf("9:12")); @@ -549,7 +549,7 @@ void findBuiltInNameRecord() throws IOException { wb1.close(); assertEquals(3, wb2.getWorkbook().getNumNames()); nr = wb2.getWorkbook().getNameRecord(2); - assertEquals("Sheet2!E:F,Sheet2!$A$9:$IV$12", HSSFFormulaParser.toFormulaString(wb2, nr.getNameDefinition())); // E:F,9:12 + assertEquals("Sheet2!$E:$F,Sheet2!$9:$12", HSSFFormulaParser.toFormulaString(wb2, nr.getNameDefinition())); // E:F,9:12 wb2.close(); } diff --git a/test-data/spreadsheet/multiple_sheets_with_row_and_column_ranges.xlsx b/test-data/spreadsheet/multiple_sheets_with_row_and_column_ranges.xlsx new file mode 100644 index 00000000000..3cad5d59324 Binary files /dev/null and b/test-data/spreadsheet/multiple_sheets_with_row_and_column_ranges.xlsx differ