From 522face545361739cc5d5104be9f6a8ac72405bf Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Fri, 15 Feb 2008 12:04:42 +0000 Subject: [PATCH] Fix from Josh from bug #44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@628033 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../apache/poi/hssf/record/NameRecord.java | 2 +- .../poi/hssf/record/formula/Area3DPtg.java | 22 +- .../poi/hssf/record/formula/AreaPtg.java | 22 +- .../poi/hssf/record/formula/Ref3DPtg.java | 2 +- .../poi/hssf/record/formula/ReferencePtg.java | 2 +- .../record/formula/SheetNameFormatter.java | 2 +- .../poi/hssf/usermodel/HSSFWorkbook.java | 6 +- .../apache/poi/hssf/util/AreaReference.java | 212 +++++++++++----- .../apache/poi/hssf/util/CellReference.java | 228 ++++++++++++------ .../poi/hssf/usermodel/TestBug42464.java | 45 ++-- .../org/apache/poi/hssf/HSSFTests.java | 12 +- .../poi/hssf/usermodel/TestFormulas.java | 16 +- .../poi/hssf/usermodel/TestNamedRange.java | 22 +- .../poi/hssf/util/AllHSSFUtilTests.java | 39 +++ .../poi/hssf/util/TestAreaReference.java | 189 +++++++-------- .../poi/hssf/util/TestCellReference.java | 78 +++--- 18 files changed, 550 insertions(+), 351 deletions(-) create mode 100755 src/testcases/org/apache/poi/hssf/util/AllHSSFUtilTests.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 1c91e62f85..f7e9714e5b 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,7 @@ + 44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name 44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself 44403 - Fix for Mid function handling its arguments wrong 44364 - Support for Match, NA and SumProduct functions, as well as initial function error support diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 67d1ddd4ce..22eca6da81 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,7 @@ + 44417 - Improved handling of references for the need to quote the sheet name for some formulas, but not when fetching a sheet by name 44413 - Fix for circular references in INDEX, OFFSET, VLOOKUP formulas, where a cell is actually allowed to reference itself 44403 - Fix for Mid function handling its arguments wrong 44364 - Support for Match, NA and SumProduct functions, as well as initial function error support diff --git a/src/java/org/apache/poi/hssf/record/NameRecord.java b/src/java/org/apache/poi/hssf/record/NameRecord.java index 3b1c413d58..a06bc8aedd 100644 --- a/src/java/org/apache/poi/hssf/record/NameRecord.java +++ b/src/java/org/apache/poi/hssf/record/NameRecord.java @@ -726,7 +726,7 @@ public class NameRecord extends Record { for(int i=0; i 1) { - lastCell = crs[1]; - } + CellReference frstCell = ar.getFirstCell(); + CellReference lastCell = ar.getLastCell(); - setFirstRow( (short) firstCell.getRow() ); - setFirstColumn( (short) firstCell.getCol() ); + setFirstRow( (short) frstCell.getRow() ); + setFirstColumn( frstCell.getCol() ); setLastRow( (short) lastCell.getRow() ); - setLastColumn( (short) lastCell.getCol() ); - setFirstColRelative( !firstCell.isColAbsolute() ); + setLastColumn( lastCell.getCol() ); + setFirstColRelative( !frstCell.isColAbsolute() ); setLastColRelative( !lastCell.isColAbsolute() ); - setFirstRowRelative( !firstCell.isRowAbsolute() ); + setFirstRowRelative( !frstCell.isRowAbsolute() ); setLastRowRelative( !lastCell.isRowAbsolute() ); } @@ -273,9 +269,9 @@ public class Area3DPtg extends Ptg SheetNameFormatter.appendFormat(retval, sheetName); retval.append( '!' ); } - retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).toString() ); + retval.append( ( new CellReference( getFirstRow(), getFirstColumn(), !isFirstRowRelative(), !isFirstColRelative() ) ).formatAsString() ); retval.append( ':' ); - retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).toString() ); + retval.append( ( new CellReference( getLastRow(), getLastColumn(), !isLastRowRelative(), !isLastColRelative() ) ).formatAsString() ); return retval.toString(); } diff --git a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java index 908c8d5e3c..61ce2a0d16 100644 --- a/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/AreaPtg.java @@ -53,14 +53,16 @@ public class AreaPtg public AreaPtg(String arearef) { AreaReference ar = new AreaReference(arearef); - setFirstRow((short)ar.getCells()[0].getRow()); - setFirstColumn((short)ar.getCells()[0].getCol()); - setLastRow((short)ar.getCells()[1].getRow()); - setLastColumn((short)ar.getCells()[1].getCol()); - setFirstColRelative(!ar.getCells()[0].isColAbsolute()); - setLastColRelative(!ar.getCells()[1].isColAbsolute()); - setFirstRowRelative(!ar.getCells()[0].isRowAbsolute()); - setLastRowRelative(!ar.getCells()[1].isRowAbsolute()); + CellReference firstCell = ar.getFirstCell(); + CellReference lastCell = ar.getLastCell(); + setFirstRow((short)firstCell.getRow()); + setFirstColumn(firstCell.getCol()); + setLastRow((short)lastCell.getRow()); + setLastColumn(lastCell.getCol()); + setFirstColRelative(!firstCell.isColAbsolute()); + setLastColRelative(!lastCell.isColAbsolute()); + setFirstRowRelative(!firstCell.isRowAbsolute()); + setLastRowRelative(!lastCell.isRowAbsolute()); } public AreaPtg(short firstRow, short lastRow, short firstColumn, short lastColumn, boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) { @@ -282,8 +284,8 @@ public class AreaPtg public String toFormulaString(Workbook book) { - return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).toString() + ":" + - (new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).toString(); + return (new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative())).formatAsString() + ":" + + (new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative())).formatAsString(); } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java index 510eebb037..ea4c742e2c 100644 --- a/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/Ref3DPtg.java @@ -183,7 +183,7 @@ public class Ref3DPtg extends Ptg { SheetNameFormatter.appendFormat(retval, sheetName); retval.append( '!' ); } - retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).toString()); + retval.append((new CellReference(getRow(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString()); return retval.toString(); } diff --git a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java index df3e5a70bc..3afd641926 100644 --- a/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java +++ b/src/java/org/apache/poi/hssf/record/formula/ReferencePtg.java @@ -191,7 +191,7 @@ public class ReferencePtg extends Ptg public String toFormulaString(Workbook book) { //TODO -- should we store a cellreference instance in this ptg?? but .. memory is an issue, i believe! - return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).toString(); + return (new CellReference(getRowAsInt(),getColumn(),!isRowRelative(),!isColRelative())).formatAsString(); } public byte getDefaultOperandClass() { diff --git a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java index ba796db3b2..8e47cbe7a0 100755 --- a/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java +++ b/src/java/org/apache/poi/hssf/record/formula/SheetNameFormatter.java @@ -26,7 +26,7 @@ import java.util.regex.Pattern; * * @author Josh Micich */ -final class SheetNameFormatter { +public final class SheetNameFormatter { private static final String BIFF8_LAST_COLUMN = "IV"; private static final int BIFF8_LAST_COLUMN_TEXT_LEN = BIFF8_LAST_COLUMN.length(); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index e237ed75db..0cbafa32f1 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -1119,12 +1119,12 @@ public class HSSFWorkbook extends POIDocument public void setPrintArea(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { - //using absolute references because they dont get copied and pasted anyway + //using absolute references because they don't get copied and pasted anyway CellReference cell = new CellReference(startRow, startColumn, true, true); - String reference = cell.toString(); + String reference = cell.formatAsString(); cell = new CellReference(endRow, endColumn, true, true); - reference = reference+":"+cell.toString(); + reference = reference+":"+cell.formatAsString(); setPrintArea(sheetIndex, reference); } diff --git a/src/java/org/apache/poi/hssf/util/AreaReference.java b/src/java/org/apache/poi/hssf/util/AreaReference.java index 8169378e76..8a0c9b0bd4 100644 --- a/src/java/org/apache/poi/hssf/util/AreaReference.java +++ b/src/java/org/apache/poi/hssf/util/AreaReference.java @@ -21,26 +21,40 @@ package org.apache.poi.hssf.util; import java.util.ArrayList; import java.util.StringTokenizer; -public class AreaReference { - - -private CellReference [] cells; -private int dim; +public final class AreaReference { + + /** The character (!) that separates sheet names from cell references */ + private static final char SHEET_NAME_DELIMITER = '!'; + /** The character (:) that separates the two cell references in a multi-cell area reference */ + private static final char CELL_DELIMITER = ':'; + /** The character (') used to quote sheet names when they contain special characters */ + private static final char SPECIAL_NAME_DELIMITER = '\''; + + private final CellReference _firstCell; + private final CellReference _lastCell; + private final boolean _isSingleCell; /** - * Create an area ref from a string representation. - * The area reference must be contiguous + * Create an area ref from a string representation. Sheet names containing special characters should be + * delimited and escaped as per normal syntax rules for formulas.
+ * The area reference must be contiguous (i.e. represent a single rectangle, not a union of rectangles) */ public AreaReference(String reference) { if(! isContiguous(reference)) { - throw new IllegalArgumentException("References passed to the AreaReference must be contiguous, use generateContiguous(ref) if you have non-contiguous references"); + throw new IllegalArgumentException( + "References passed to the AreaReference must be contiguous, " + + "use generateContiguous(ref) if you have non-contiguous references"); } - String[] refs = seperateAreaRefs(reference); - dim = refs.length; - cells = new CellReference[dim]; - for (int i=0;ifalse if this area reference involves more than one cell + */ + public boolean isSingleCell() { + return _isSingleCell; } - /** - * Return the cell references that define this area - * (i.e. the two corners) + + /** + * @return the first cell reference which defines this area. Usually this cell is in the upper + * left corner of the area (but this is not a requirement). */ - public CellReference[] getCells() { - return cells; + public CellReference getFirstCell() { + return _firstCell; + } + + /** + * Note - if this area reference refers to a single cell, the return value of this method will + * be identical to that of getFirstCell() + * @return the second cell reference which defines this area. For multi-cell areas, this is + * cell diagonally opposite the 'first cell'. Usually this cell is in the lower right corner + * of the area (but this is not a requirement). + */ + public CellReference getLastCell() { + return _lastCell; } /** * Returns a reference to every cell covered by this area */ public CellReference[] getAllReferencedCells() { // Special case for single cell reference - if(cells.length == 1) { - return cells; + if(_isSingleCell) { + return new CellReference[] { _firstCell, }; } + // Interpolate between the two - int minRow = Math.min(cells[0].getRow(), cells[1].getRow()); - int maxRow = Math.max(cells[0].getRow(), cells[1].getRow()); - int minCol = Math.min(cells[0].getCol(), cells[1].getCol()); - int maxCol = Math.max(cells[0].getCol(), cells[1].getCol()); + int minRow = Math.min(_firstCell.getRow(), _lastCell.getRow()); + int maxRow = Math.max(_firstCell.getRow(), _lastCell.getRow()); + int minCol = Math.min(_firstCell.getCol(), _lastCell.getCol()); + int maxCol = Math.max(_firstCell.getCol(), _lastCell.getCol()); + String sheetName = _firstCell.getSheetName(); ArrayList refs = new ArrayList(); for(int row=minRow; row<=maxRow; row++) { for(int col=minCol; col<=maxCol; col++) { - CellReference ref = new CellReference(row, col, cells[0].isRowAbsolute(), cells[0].isColAbsolute()); - ref.setSheetName(cells[0].getSheetName()); + CellReference ref = new CellReference(sheetName, row, col, _firstCell.isRowAbsolute(), _firstCell.isColAbsolute()); refs.add(ref); } } return (CellReference[])refs.toArray(new CellReference[refs.size()]); } - public String toString() { - StringBuffer retval = new StringBuffer(); - for (int i=0;i + * ResultComment + * A1:A1Single cell area reference without sheet + * A1:$C$1Multi-cell area reference without sheet + * Sheet1!A$1:B4Standard sheet name + * 'O''Brien''s Sales'!B5:C6' Sheet name with special characters + * + * @return the text representation of this area reference as it would appear in a formula. + */ + public String formatAsString() { + StringBuffer sb = new StringBuffer(32); + sb.append(_firstCell.formatAsString()); + if(!_isSingleCell) { + sb.append(CELL_DELIMITER); + if(_lastCell.getSheetName() == null) { + sb.append(_lastCell.formatAsString()); + } else { + // don't want to include the sheet name twice + _lastCell.appendCellReference(sb); + } } - retval.deleteCharAt(0); - return retval.toString(); + return sb.toString(); + } + public String toString() { + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); } /** - * seperates Area refs in two parts and returns them as seperate elements in a - * String array + * Separates Area refs in two parts and returns them as separate elements in a String array, + * each qualified with the sheet name (if present) + * + * @return array with one or two elements. never null */ - private String[] seperateAreaRefs(String reference) { - String[] retval = null; - - int length = reference.length(); - - int loc = reference.indexOf(':',0); - if(loc == -1){ - retval = new String[1]; - retval[0] = reference; + private static String[] separateAreaRefs(String reference) { + // TODO - refactor cell reference parsing logic to one place. + // Current known incarnations: + // FormulaParser.GetName() + // CellReference.separateRefParts() + // AreaReference.separateAreaRefs() (here) + // SheetNameFormatter.format() (inverse) + + + int len = reference.length(); + int delimiterPos = -1; + boolean insideDelimitedName = false; + for(int i=0; i=0) { + throw new IllegalArgumentException("More than one cell delimiter '" + + CELL_DELIMITER + "' appears in area reference '" + reference + "'"); + } + delimiterPos = i; + } + default: + continue; + case SPECIAL_NAME_DELIMITER: + // fall through + } + if(!insideDelimitedName) { + insideDelimitedName = true; + continue; + } + + if(i >= len-1) { + // reference ends with the delimited name. + // Assume names like: "Sheet1!'A1'" are never legal. + throw new IllegalArgumentException("Area reference '" + reference + + "' ends with special name delimiter '" + SPECIAL_NAME_DELIMITER + "'"); + } + if(reference.charAt(i+1) == SPECIAL_NAME_DELIMITER) { + // two consecutive quotes is the escape sequence for a single one + i++; // skip this and keep parsing the special name + } else { + // this is the end of the delimited name + insideDelimitedName = false; + } + } + if(delimiterPos < 0) { + return new String[] { reference, }; } - else{ - retval = new String[2]; - int sheetStart = reference.indexOf("!"); - retval[0] = reference.substring(0, sheetStart+1) + reference.substring(sheetStart + 1,loc); - retval[1] = reference.substring(0, sheetStart+1) + reference.substring(loc+1); + String partA = reference.substring(0, delimiterPos); + String partB = reference.substring(delimiterPos+1); + if(partB.indexOf(SHEET_NAME_DELIMITER) >=0) { + // TODO - are references like "Sheet1!A1:Sheet1:B2" ever valid? + // FormulaParser has code to handle that. + + throw new RuntimeException("Unexpected " + SHEET_NAME_DELIMITER + + " in second cell reference of '" + reference + "'"); + } + + int plingPos = partA.lastIndexOf(SHEET_NAME_DELIMITER); + if(plingPos < 0) { + return new String [] { partA, partB, }; } - return retval; + + String sheetName = partA.substring(0, plingPos + 1); // +1 to include delimiter + + return new String [] { partA, sheetName + partB, }; } } \ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/util/CellReference.java b/src/java/org/apache/poi/hssf/util/CellReference.java index def58472a8..33e33ba958 100644 --- a/src/java/org/apache/poi/hssf/util/CellReference.java +++ b/src/java/org/apache/poi/hssf/util/CellReference.java @@ -15,75 +15,91 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.util; +import org.apache.poi.hssf.record.formula.SheetNameFormatter; + /** * * @author Avik Sengupta * @author Dennis Doubleday (patch to seperateRowColumns()) */ -public class CellReference { +public final class CellReference { + /** The character ($) that signifies a row or column value is absolute instead of relative */ + private static final char ABSOLUTE_REFERENCE_MARKER = '$'; + /** The character (!) that separates sheet names from cell references */ + private static final char SHEET_NAME_DELIMITER = '!'; + /** The character (') used to quote sheet names when they contain special characters */ + private static final char SPECIAL_NAME_DELIMITER = '\''; + - /** Creates new CellReference */ - private int row; - private int col; - private String sheetName; - private boolean rowAbs; - private boolean colAbs; + private final int _rowIndex; + private final int _colIndex; + private final String _sheetName; + private final boolean _isRowAbs; + private final boolean _isColAbs; + /** + * Create an cell ref from a string representation. Sheet names containing special characters should be + * delimited and escaped as per normal syntax rules for formulas. + */ public CellReference(String cellRef) { String[] parts = separateRefParts(cellRef); - sheetName = parts[0]; - String ref = parts[1]; - if ((ref == null)||("".equals(ref))) - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - if (ref.charAt(0) == '$') { - colAbs=true; - ref=ref.substring(1); + _sheetName = parts[0]; + String colRef = parts[1]; + if (colRef.length() < 1) { + throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); } - col = convertColStringToNum(ref); - ref=parts[2]; - if ((ref == null)||("".equals(ref))) - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - if (ref.charAt(0) == '$') { - rowAbs=true; - ref=ref.substring(1); + _isColAbs = colRef.charAt(0) == '$'; + if (_isColAbs) { + colRef=colRef.substring(1); } - row = Integer.parseInt(ref)-1; - } - - public CellReference(int pRow, int pCol) { - this(pRow,pCol,false,false); + _colIndex = convertColStringToNum(colRef); + + String rowRef=parts[2]; + if (rowRef.length() < 1) { + throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); + } + _isRowAbs = rowRef.charAt(0) == '$'; + if (_isRowAbs) { + rowRef=rowRef.substring(1); + } + _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } public CellReference(int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { - row=pRow;col=pCol; - rowAbs = pAbsRow; - colAbs=pAbsCol; - + this(null, pRow, pCol, pAbsRow, pAbsCol); } - - public int getRow(){return row;} - public short getCol(){return (short) col;} - public boolean isRowAbsolute(){return rowAbs;} - public boolean isColAbsolute(){return colAbs;} - public String getSheetName(){return sheetName;} - - protected void setSheetName(String sheetName) { - this.sheetName = sheetName; + public CellReference(String pSheetName, int pRow, int pCol, boolean pAbsRow, boolean pAbsCol) { + _sheetName = pSheetName; + _rowIndex=pRow; + _colIndex=pCol; + _isRowAbs = pAbsRow; + _isColAbs=pAbsCol; } + public int getRow(){return _rowIndex;} + public short getCol(){return (short) _colIndex;} + public boolean isRowAbsolute(){return _isRowAbs;} + public boolean isColAbsolute(){return _isColAbs;} + /** + * @return possibly null if this is a 2D reference. Special characters are not + * escaped or delimited + */ + public String getSheetName(){ + return _sheetName; + } + /** * takes in a column reference portion of a CellRef and converts it from * ALPHA-26 number format to 0-based base 10. */ private int convertColStringToNum(String ref) { - int len = ref.length(); + int lastIx = ref.length()-1; int retval=0; int pos = 0; - for (int k = ref.length()-1; k > -1; k--) { + for (int k = lastIx; k > -1; k--) { char thechar = ref.charAt(k); if ( pos == 0) { retval += (Character.getNumericValue(thechar)-9); @@ -97,36 +113,78 @@ public class CellReference { /** - * Seperates the row from the columns and returns an array. Element in - * position one is the substring containing the columns still in ALPHA-26 - * number format. + * Separates the row from the columns and returns an array of three Strings. The first element + * is the sheet name. Only the first element may be null. The second element in is the column + * name still in ALPHA-26 number format. The third element is the row. */ - private String[] separateRefParts(String reference) { - - // Look for end of sheet name. This will either set - // start to 0 (if no sheet name present) or the - // index after the sheet reference ends. - String retval[] = new String[3]; - - int start = reference.indexOf("!"); - if (start != -1) retval[0] = reference.substring(0, start); - start += 1; + private static String[] separateRefParts(String reference) { + + int plingPos = reference.lastIndexOf(SHEET_NAME_DELIMITER); + String sheetName = parseSheetName(reference, plingPos); + int start = plingPos+1; int length = reference.length(); - char[] chars = reference.toCharArray(); int loc = start; - if (chars[loc]=='$') loc++; - for (; loc < chars.length; loc++) { - if (Character.isDigit(chars[loc]) || chars[loc] == '$') { + // skip initial dollars + if (reference.charAt(loc)==ABSOLUTE_REFERENCE_MARKER) { + loc++; + } + // step over column name chars until first digit (or dollars) for row number. + for (; loc < length; loc++) { + char ch = reference.charAt(loc); + if (Character.isDigit(ch) || ch == ABSOLUTE_REFERENCE_MARKER) { break; } } + return new String[] { + sheetName, + reference.substring(start,loc), + reference.substring(loc), + }; + } + + private static String parseSheetName(String reference, int indexOfSheetNameDelimiter) { + if(indexOfSheetNameDelimiter < 0) { + return null; + } + + boolean isQuoted = reference.charAt(0) == SPECIAL_NAME_DELIMITER; + if(!isQuoted) { + return reference.substring(0, indexOfSheetNameDelimiter); + } + int lastQuotePos = indexOfSheetNameDelimiter-1; + if(reference.charAt(lastQuotePos) != SPECIAL_NAME_DELIMITER) { + throw new RuntimeException("Mismatched quotes: (" + reference + ")"); + } - retval[1] = reference.substring(start,loc); - retval[2] = reference.substring(loc); - return retval; + // TODO - refactor cell reference parsing logic to one place. + // Current known incarnations: + // FormulaParser.GetName() + // CellReference.parseSheetName() (here) + // AreaReference.separateAreaRefs() + // SheetNameFormatter.format() (inverse) + + StringBuffer sb = new StringBuffer(indexOfSheetNameDelimiter); + + for(int i=1; i + * ResultComment + * A1Cell reference without sheet + * Sheet1!A1Standard sheet name + * 'O''Brien''s Sales'!A1' Sheet name with special characters + * + * @return the text representation of this cell reference as it would appear in a formula. + */ + public String formatAsString() { + StringBuffer sb = new StringBuffer(32); + if(_sheetName != null) { + SheetNameFormatter.appendFormat(sb, _sheetName); + sb.append(SHEET_NAME_DELIMITER); + } + appendCellReference(sb); + return sb.toString(); + } + public String toString() { - StringBuffer retval = new StringBuffer(); - retval.append( (colAbs)?"$":""); - retval.append( convertNumToColString(col)); - retval.append((rowAbs)?"$":""); - retval.append(row+1); + StringBuffer sb = new StringBuffer(64); + sb.append(getClass().getName()).append(" ["); + sb.append(formatAsString()); + sb.append("]"); + return sb.toString(); + } - return retval.toString(); + /** + * Appends cell reference with '$' markers for absolute values as required. + * Sheet name is not included. + */ + /* package */ void appendCellReference(StringBuffer sb) { + if(_isColAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append( convertNumToColString(_colIndex)); + if(_isRowAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append(_rowIndex+1); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java index 3b31cc03a3..c849fd4369 100644 --- a/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java +++ b/src/scratchpad/testcases/org/apache/poi/hssf/usermodel/TestBug42464.java @@ -21,14 +21,14 @@ import java.io.FileInputStream; import java.util.Iterator; import java.util.List; +import junit.framework.TestCase; + import org.apache.poi.hssf.record.FormulaRecord; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; -import org.apache.poi.hssf.record.formula.ExpPtg; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator.CellValue; import org.apache.poi.hssf.util.CellReference; -import junit.framework.TestCase; - -public class TestBug42464 extends TestCase { +public final class TestBug42464 extends TestCase { String dirname; protected void setUp() throws Exception { @@ -68,26 +68,27 @@ public class TestBug42464 extends TestCase { Iterator it = row.cellIterator(); while(it.hasNext()) { HSSFCell cell = (HSSFCell)it.next(); - if(cell.getCellType() == HSSFCell.CELL_TYPE_FORMULA) { - FormulaRecordAggregate record = (FormulaRecordAggregate) - cell.getCellValueRecord(); - FormulaRecord r = record.getFormulaRecord(); - List ptgs = r.getParsedExpression(); - - String cellRef = (new CellReference(row.getRowNum(), cell.getCellNum())).toString(); - if(cellRef.equals("BP24")) { - System.out.print(cellRef); - System.out.println(" - has " + r.getNumberOfExpressionTokens() + " ptgs over " + r.getExpressionLength() + " tokens:"); - for(int i=0; i " + cell.getCellFormula()); + if(cell.getCellType() != HSSFCell.CELL_TYPE_FORMULA) { + continue; + } + FormulaRecordAggregate record = (FormulaRecordAggregate) cell.getCellValueRecord(); + FormulaRecord r = record.getFormulaRecord(); + List ptgs = r.getParsedExpression(); + + String cellRef = new CellReference(row.getRowNum(), cell.getCellNum(), false, false).formatAsString(); + if(false && cellRef.equals("BP24")) { // TODO - replace System.out.println()s with asserts + System.out.print(cellRef); + System.out.println(" - has " + r.getNumberOfExpressionTokens() + + " ptgs over " + r.getExpressionLength() + " tokens:"); + for(int i=0; i " + cell.getCellFormula()); } + + CellValue evalResult = eval.evaluate(cell); + assertNotNull(evalResult); } } } diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index 1e0edd6825..da37b68adf 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -100,11 +100,7 @@ import org.apache.poi.hssf.usermodel.TestReadWriteChart; import org.apache.poi.hssf.usermodel.TestSanityChecker; import org.apache.poi.hssf.usermodel.TestSheetShiftRows; import org.apache.poi.hssf.usermodel.TestWorkbook; -import org.apache.poi.hssf.util.TestAreaReference; -import org.apache.poi.hssf.util.TestCellReference; -import org.apache.poi.hssf.util.TestRKUtil; -import org.apache.poi.hssf.util.TestRangeAddress; -import org.apache.poi.hssf.util.TestSheetReferences; +import org.apache.poi.hssf.util.AllHSSFUtilTests; /** * Test Suite for running just HSSF tests. Mostly @@ -202,11 +198,7 @@ public class HSSFTests suite.addTest(new TestSuite(TestUnitsRecord.class)); suite.addTest(new TestSuite(TestValueRangeRecord.class)); suite.addTest(new TestSuite(TestRowRecordsAggregate.class)); - suite.addTest(new TestSuite(TestAreaReference.class)); - suite.addTest(new TestSuite(TestCellReference.class)); - suite.addTest(new TestSuite(TestRangeAddress.class)); - suite.addTest(new TestSuite(TestRKUtil.class)); - suite.addTest(new TestSuite(TestSheetReferences.class)); + suite.addTest(AllHSSFUtilTests.suite()); suite.addTest(AllFormulaTests.suite()); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java index f1e8f49777..9eb12bd4e1 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestFormulas.java @@ -302,10 +302,10 @@ extends TestCase { } c = r.getCell((short) y); - CellReference cr= new CellReference(refx1,refy1); - ref=cr.toString(); - cr=new CellReference(refx2,refy2); - ref2=cr.toString(); + CellReference cr= new CellReference(refx1,refy1, false, false); + ref=cr.formatAsString(); + cr=new CellReference(refx2,refy2, false, false); + ref2=cr.formatAsString(); c = r.createCell((short) y); c.setCellFormula("" + ref + operator + ref2); @@ -379,10 +379,10 @@ extends TestCase { } c = r.getCell((short) y); - CellReference cr= new CellReference(refx1,refy1); - ref=cr.toString(); - cr=new CellReference(refx2,refy2); - ref2=cr.toString(); + CellReference cr= new CellReference(refx1, refy1, false, false); + ref=cr.formatAsString(); + cr=new CellReference(refx2,refy2, false, false); + ref2=cr.formatAsString(); assertTrue("loop Formula is as expected "+ref+operator+ref2+"!="+c.getCellFormula(),( diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java index f22de1758c..afbbde026e 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestNamedRange.java @@ -578,18 +578,16 @@ public class TestNamedRange // retrieve the cell at the named range and test its contents AreaReference aref = new AreaReference(aNamedCell.getReference()); - CellReference[] crefs = aref.getCells(); - assertNotNull(crefs); - assertEquals("Should be exactly 1 cell in the named cell :'" +cellName+"'", 1, crefs.length); - for (int i=0, iSize=crefs.length; i