diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 092abdbfc..4d84732c4 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 53500 - Getter for repeating rows and columns 53369 - Fixed tests failing on JDK 1.7 53360 - Fixed SXSSF to correctly write text before escaped Unicode control character Change HSMF Types to have full data on ID, Name and Length, rather than just being a simple ID diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index fe332eb83..9ca6a9552 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -2003,4 +2003,71 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { return new HSSFAutoFilter(this); } + + @Override + public CellRangeAddress getRepeatingRows() { + return getRepeatingRowsOrColums(true); + } + + + @Override + public CellRangeAddress getRepeatingColumns() { + return getRepeatingRowsOrColums(false); + } + + + private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { + NameRecord rec = getBuiltinNameRecord(NameRecord.BUILTIN_PRINT_TITLE); + if (rec == null) { + return null; + } + + Ptg[] nameDefinition = rec.getNameDefinition(); + if (rec.getNameDefinition() == null) { + return null; + } + + int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + + for (Ptg ptg :nameDefinition) { + + if (ptg instanceof Area3DPtg) { + Area3DPtg areaPtg = (Area3DPtg) ptg; + + if (areaPtg.getFirstColumn() == 0 + && areaPtg.getLastColumn() == maxColIndex) { + if (rows) { + CellRangeAddress rowRange = new CellRangeAddress( + areaPtg.getFirstRow(), areaPtg.getLastRow(), -1, -1); + return rowRange; + } + } else if (areaPtg.getFirstRow() == 0 + && areaPtg.getLastRow() == maxRowIndex) { + if (!rows) { + CellRangeAddress columnRange = new CellRangeAddress(-1, -1, + areaPtg.getFirstColumn(), areaPtg.getLastColumn()); + return columnRange; + } + } + + } + + } + + return null; + } + + + private NameRecord getBuiltinNameRecord(byte builtinCode) { + int sheetIndex = _workbook.getSheetIndex(this); + int recIndex = + _workbook.findExistingBuiltinNameRecordIdx(sheetIndex, builtinCode); + if (recIndex == -1) { + return null; + } + return _workbook.getNameRecord(recIndex); + } + + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index afd0bce10..a92f4bbff 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -76,7 +76,7 @@ import org.apache.commons.codec.digest.DigestUtils; public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.usermodel.Workbook { private static final Pattern COMMA_PATTERN = Pattern.compile(","); private static final int MAX_ROW = 0xFFFF; - private static final short MAX_COLUMN = (short)0x00FF; + private static final int MAX_COLUMN = (short)0x00FF; /** * The maximum number of cell styles in a .xls workbook. @@ -1034,7 +1034,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss } - private int findExistingBuiltinNameRecordIdx(int sheetIndex, byte builtinCode) { + int findExistingBuiltinNameRecordIdx(int sheetIndex, byte builtinCode) { for(int defNameIndex =0; defNameIndex { */ SheetConditionalFormatting getSheetConditionalFormatting(); + + /** + * Gets the repeating rows used when printing the sheet, as found in + * File->PageSetup->Sheet. + *

+ * Repeating rows cover a range of contiguous rows, e.g.: + *

+     * Sheet1!$1:$1
+     * Sheet2!$5:$8
+     * 
+ * The {@link CellRangeAddress} returned contains a column part which spans + * all columns, and a row part which specifies the contiguous range of + * repeating rows. + *

+ * If the Sheet does not have any repeating rows defined, null is returned. + * + * @return an {@link CellRangeAddress} containing the repeating rows for the + * Sheet, or null. + */ + CellRangeAddress getRepeatingRows(); + + + /** + * Gets the repeating columns used when printing the sheet, as found in + * File->PageSetup->Sheet. + *

+ * Repeating columns cover a range of contiguous columns, e.g.: + *

+     * Sheet1!$A:$A
+     * Sheet2!$C:$F
+     * 
+ * The {@link CellRangeAddress} returned contains a row part which spans all + * rows, and a column part which specifies the contiguous range of + * repeating columns. + *

+ * If the Sheet does not have any repeating columns defined, null is + * returned. + * + * @return an {@link CellRangeAddress} containing the repeating columns for the + * Sheet, or null. + */ + CellRangeAddress getRepeatingColumns(); + + } diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddress.java b/src/java/org/apache/poi/ss/util/CellRangeAddress.java index 4a44a6c8a..418161d75 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddress.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddress.java @@ -100,7 +100,10 @@ public class CellRangeAddress extends CellRangeAddressBase { sb.append(cellRefFrom.formatAsString()); //for a single-cell reference return A1 instead of A1:A1 - if(!cellRefFrom.equals(cellRefTo)){ + //for full-column ranges or full-row ranges return A:A instead of A, + //and 1:1 instead of 1 + if(!cellRefFrom.equals(cellRefTo) + || isFullColumnRange() || isFullRowRange()){ sb.append(':'); sb.append(cellRefTo.formatAsString()); } diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java index e8d9c9100..12aaa6f62 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java @@ -76,11 +76,13 @@ public abstract class CellRangeAddressBase { //TODO use the correct SpreadsheetVersion public final boolean isFullColumnRange() { - return _firstRow == 0 && _lastRow == SpreadsheetVersion.EXCEL97.getLastRowIndex(); + return (_firstRow == 0 && _lastRow == SpreadsheetVersion.EXCEL97.getLastRowIndex()) + || (_firstRow == -1 && _lastRow == -1); } //TODO use the correct SpreadsheetVersion public final boolean isFullRowRange() { - return _firstCol == 0 && _lastCol == SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + return (_firstCol == 0 && _lastCol == SpreadsheetVersion.EXCEL97.getLastColumnIndex()) + || (_firstCol == -1 && _lastCol == -1); } /** diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java index 12120b748..f7c37e300 100644 --- a/src/java/org/apache/poi/ss/util/CellReference.java +++ b/src/java/org/apache/poi/ss/util/CellReference.java @@ -91,25 +91,28 @@ public class CellReference { String[] parts = separateRefParts(cellRef); _sheetName = parts[0]; + String colRef = parts[1]; - if (colRef.length() < 1) { - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - } - _isColAbs = colRef.charAt(0) == '$'; + _isColAbs = (colRef.length() > 0) && colRef.charAt(0) == '$'; if (_isColAbs) { - colRef=colRef.substring(1); + colRef = colRef.substring(1); + } + if (colRef.length() == 0) { + _colIndex = -1; + } else { + _colIndex = convertColStringToIndex(colRef); } - _colIndex = convertColStringToIndex(colRef); String rowRef=parts[2]; - if (rowRef.length() < 1) { - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - } - _isRowAbs = rowRef.charAt(0) == '$'; + _isRowAbs = (rowRef.length() > 0) && rowRef.charAt(0) == '$'; if (_isRowAbs) { - rowRef=rowRef.substring(1); + rowRef = rowRef.substring(1); + } + if (rowRef.length() == 0) { + _rowIndex = -1; + } else { + _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } - _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } public CellReference(int pRow, int pCol) { @@ -482,14 +485,18 @@ public class CellReference { * Sheet name is not included. */ /* package */ void appendCellReference(StringBuffer sb) { - if(_isColAbs) { - sb.append(ABSOLUTE_REFERENCE_MARKER); + if (_colIndex != -1) { + if(_isColAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append( convertNumToColString(_colIndex)); + } + if (_rowIndex != -1) { + if(_isRowAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append(_rowIndex+1); } - sb.append( convertNumToColString(_colIndex)); - if(_isRowAbs) { - sb.append(ABSOLUTE_REFERENCE_MARKER); - } - sb.append(_rowIndex+1); } /** 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 5eb3338af..9c6890f18 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -25,6 +25,7 @@ import java.util.Map; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.AreaReference; import org.apache.poi.ss.util.SheetUtil; import org.apache.poi.xssf.usermodel.XSSFSheet; @@ -1263,7 +1264,20 @@ public class SXSSFSheet implements Sheet, Cloneable public SheetConditionalFormatting getSheetConditionalFormatting(){ return _sh.getSheetConditionalFormatting(); } - + + + @Override + public CellRangeAddress getRepeatingRows() { + return _sh.getRepeatingRows(); + } + + + @Override + public CellRangeAddress getRepeatingColumns() { + return _sh.getRepeatingColumns(); + } + + //end of interface implementation /** * Specifies how many rows can be accessed at most via getRow(). 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 fbed5b06d..1da83b22d 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -3185,4 +3185,54 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { color.setIndexed(colorIndex); pr.setTabColor(color); } + + + @Override + public CellRangeAddress getRepeatingRows() { + return getRepeatingRowsOrColums(true); + } + + + @Override + public CellRangeAddress getRepeatingColumns() { + return getRepeatingRowsOrColums(false); + } + + + private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { + int sheetIndex = getWorkbook().getSheetIndex(this); + XSSFName name = getWorkbook().getBuiltInName( + XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + if (name == null ) { + return null; + } + String refStr = name.getRefersToFormula(); + if (refStr == null) { + return null; + } + String[] parts = refStr.split(","); + int maxRowIndex = SpreadsheetVersion.EXCEL2007.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL2007.getLastColumnIndex(); + for (String part : parts) { + CellRangeAddress range = CellRangeAddress.valueOf(part); + if ((range.getFirstColumn() == 0 + && range.getLastColumn() == maxColIndex) + || (range.getFirstColumn() == -1 + && range.getLastColumn() == -1)) { + if (rows) { + return range; + } + } else if (range.getFirstRow() == 0 + && range.getLastRow() == maxRowIndex + || (range.getFirstRow() == -1 + && range.getLastRow() == -1)) { + if (!rows) { + return range; + } + } + } + return null; + } + + } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java index 347eded97..fca31de5f 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java @@ -359,14 +359,40 @@ public abstract class BaseTestWorkbook extends TestCase { assertSame(row, cell.getRow()); } + + public void testGetRepeatingRowsAnsColumns(){ + Workbook wb = _testDataProvider.openSampleWorkbook( + "RepeatingRowsCols." + + _testDataProvider.getStandardFileNameExtension()); + + Sheet sheet0 = wb.getSheetAt(0); + assertNull(sheet0.getRepeatingRows()); + assertNull(sheet0.getRepeatingColumns()); + + Sheet sheet1 = wb.getSheetAt(1); + assertEquals("1:1", sheet1.getRepeatingRows().formatAsString()); + assertNull(sheet1.getRepeatingColumns()); + + Sheet sheet2 = wb.getSheetAt(2); + assertNull(sheet2.getRepeatingRows()); + assertEquals("A:A", sheet2.getRepeatingColumns().formatAsString()); + + Sheet sheet3 = wb.getSheetAt(3); + assertEquals("2:3", sheet3.getRepeatingRows().formatAsString()); + assertEquals("A:B", sheet3.getRepeatingColumns().formatAsString()); + } + + public void testSetRepeatingRowsAnsColumns(){ Workbook wb = _testDataProvider.createWorkbook(); Sheet sheet1 = wb.createSheet(); wb.setRepeatingRowsAndColumns(wb.getSheetIndex(sheet1), 0, 0, 0, 3); + assertEquals("1:4", sheet1.getRepeatingRows().formatAsString()); //must handle sheets with quotas, see Bugzilla #47294 Sheet sheet2 = wb.createSheet("My' Sheet"); wb.setRepeatingRowsAndColumns(wb.getSheetIndex(sheet2), 0, 0, 0, 3); + assertEquals("1:4", sheet2.getRepeatingRows().formatAsString()); } /** diff --git a/test-data/spreadsheet/RepeatingRowsCols.xls b/test-data/spreadsheet/RepeatingRowsCols.xls new file mode 100644 index 000000000..95f4e5019 Binary files /dev/null and b/test-data/spreadsheet/RepeatingRowsCols.xls differ diff --git a/test-data/spreadsheet/RepeatingRowsCols.xlsx b/test-data/spreadsheet/RepeatingRowsCols.xlsx new file mode 100644 index 000000000..a8a5edf7d Binary files /dev/null and b/test-data/spreadsheet/RepeatingRowsCols.xlsx differ