mirror of https://github.com/apache/poi.git
Fix formula parser to properly support the range operator. Small fixes to parsing of sheet names and full column references.
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@699487 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
3e317e0747
commit
b22f939c79
|
@ -30,248 +30,264 @@ import org.apache.poi.util.LittleEndian;
|
||||||
* @author Jason Height (jheight at chariot dot net dot au)
|
* @author Jason Height (jheight at chariot dot net dot au)
|
||||||
*/
|
*/
|
||||||
public abstract class AreaPtgBase extends OperandPtg implements AreaI {
|
public abstract class AreaPtgBase extends OperandPtg implements AreaI {
|
||||||
/**
|
/**
|
||||||
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
|
* TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
|
||||||
* see similar comment in ReferencePtg
|
* see similar comment in ReferencePtg
|
||||||
*/
|
*/
|
||||||
protected final RuntimeException notImplemented() {
|
protected final RuntimeException notImplemented() {
|
||||||
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
|
return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** zero based, unsigned 16 bit */
|
/** zero based, unsigned 16 bit */
|
||||||
private int field_1_first_row;
|
private int field_1_first_row;
|
||||||
/** zero based, unsigned 16 bit */
|
/** zero based, unsigned 16 bit */
|
||||||
private int field_2_last_row;
|
private int field_2_last_row;
|
||||||
/** zero based, unsigned 8 bit */
|
/** zero based, unsigned 8 bit */
|
||||||
private int field_3_first_column;
|
private int field_3_first_column;
|
||||||
/** zero based, unsigned 8 bit */
|
/** zero based, unsigned 8 bit */
|
||||||
private int field_4_last_column;
|
private int field_4_last_column;
|
||||||
|
|
||||||
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
|
private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
|
||||||
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
|
private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
|
||||||
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
|
private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
|
||||||
|
|
||||||
protected AreaPtgBase() {
|
protected AreaPtgBase() {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AreaPtgBase(String arearef) {
|
protected AreaPtgBase(String arearef) {
|
||||||
AreaReference ar = new AreaReference(arearef);
|
AreaReference ar = new AreaReference(arearef);
|
||||||
CellReference firstCell = ar.getFirstCell();
|
CellReference firstCell = ar.getFirstCell();
|
||||||
CellReference lastCell = ar.getLastCell();
|
CellReference lastCell = ar.getLastCell();
|
||||||
setFirstRow(firstCell.getRow());
|
setFirstRow(firstCell.getRow());
|
||||||
setFirstColumn(firstCell.getCol());
|
setFirstColumn(firstCell.getCol());
|
||||||
setLastRow(lastCell.getRow());
|
setLastRow(lastCell.getRow());
|
||||||
setLastColumn(lastCell.getCol());
|
setLastColumn(lastCell.getCol());
|
||||||
setFirstColRelative(!firstCell.isColAbsolute());
|
setFirstColRelative(!firstCell.isColAbsolute());
|
||||||
setLastColRelative(!lastCell.isColAbsolute());
|
setLastColRelative(!lastCell.isColAbsolute());
|
||||||
setFirstRowRelative(!firstCell.isRowAbsolute());
|
setFirstRowRelative(!firstCell.isRowAbsolute());
|
||||||
setLastRowRelative(!lastCell.isRowAbsolute());
|
setLastRowRelative(!lastCell.isRowAbsolute());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
|
protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
|
||||||
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
|
boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
|
||||||
|
|
||||||
checkColumnBounds(firstColumn);
|
checkColumnBounds(firstColumn);
|
||||||
checkColumnBounds(lastColumn);
|
checkColumnBounds(lastColumn);
|
||||||
checkRowBounds(firstRow);
|
checkRowBounds(firstRow);
|
||||||
checkRowBounds(lastRow);
|
checkRowBounds(lastRow);
|
||||||
setFirstRow(firstRow);
|
|
||||||
setLastRow(lastRow);
|
|
||||||
setFirstColumn(firstColumn);
|
|
||||||
setLastColumn(lastColumn);
|
|
||||||
setFirstRowRelative(firstRowRelative);
|
|
||||||
setLastRowRelative(lastRowRelative);
|
|
||||||
setFirstColRelative(firstColRelative);
|
|
||||||
setLastColRelative(lastColRelative);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkColumnBounds(int colIx) {
|
if (lastRow > firstRow) {
|
||||||
if((colIx & 0x0FF) != colIx) {
|
setFirstRow(firstRow);
|
||||||
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
|
setLastRow(lastRow);
|
||||||
}
|
setFirstRowRelative(firstRowRelative);
|
||||||
}
|
setLastRowRelative(lastRowRelative);
|
||||||
private static void checkRowBounds(int rowIx) {
|
} else {
|
||||||
if((rowIx & 0x0FFFF) != rowIx) {
|
setFirstRow(lastRow);
|
||||||
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
|
setLastRow(firstRow);
|
||||||
}
|
setFirstRowRelative(lastRowRelative);
|
||||||
}
|
setLastRowRelative(firstRowRelative);
|
||||||
|
}
|
||||||
|
|
||||||
protected final void readCoordinates(RecordInputStream in) {
|
if (lastColumn > firstColumn) {
|
||||||
field_1_first_row = in.readUShort();
|
setFirstColumn(firstColumn);
|
||||||
field_2_last_row = in.readUShort();
|
setLastColumn(lastColumn);
|
||||||
field_3_first_column = in.readUShort();
|
setFirstColRelative(firstColRelative);
|
||||||
field_4_last_column = in.readUShort();
|
setLastColRelative(lastColRelative);
|
||||||
}
|
} else {
|
||||||
protected final void writeCoordinates(byte[] array, int offset) {
|
setFirstColumn(lastColumn);
|
||||||
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
|
setLastColumn(firstColumn);
|
||||||
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
|
setFirstColRelative(lastColRelative);
|
||||||
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
|
setLastColRelative(firstColRelative);
|
||||||
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static void checkColumnBounds(int colIx) {
|
||||||
* @return the first row in the area
|
if((colIx & 0x0FF) != colIx) {
|
||||||
*/
|
throw new IllegalArgumentException("colIx (" + colIx + ") is out of range");
|
||||||
public final int getFirstRow() {
|
}
|
||||||
return field_1_first_row;
|
}
|
||||||
}
|
private static void checkRowBounds(int rowIx) {
|
||||||
|
if((rowIx & 0x0FFFF) != rowIx) {
|
||||||
|
throw new IllegalArgumentException("rowIx (" + rowIx + ") is out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
protected final void readCoordinates(RecordInputStream in) {
|
||||||
* sets the first row
|
field_1_first_row = in.readUShort();
|
||||||
* @param rowIx number (0-based)
|
field_2_last_row = in.readUShort();
|
||||||
*/
|
field_3_first_column = in.readUShort();
|
||||||
public final void setFirstRow(int rowIx) {
|
field_4_last_column = in.readUShort();
|
||||||
checkRowBounds(rowIx);
|
}
|
||||||
field_1_first_row = rowIx;
|
protected final void writeCoordinates(byte[] array, int offset) {
|
||||||
}
|
LittleEndian.putUShort(array, offset + 0, field_1_first_row);
|
||||||
|
LittleEndian.putUShort(array, offset + 2, field_2_last_row);
|
||||||
|
LittleEndian.putUShort(array, offset + 4, field_3_first_column);
|
||||||
|
LittleEndian.putUShort(array, offset + 6, field_4_last_column);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return last row in the range (x2 in x1,y1-x2,y2)
|
* @return the first row in the area
|
||||||
*/
|
*/
|
||||||
public final int getLastRow() {
|
public final int getFirstRow() {
|
||||||
return field_2_last_row;
|
return field_1_first_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param rowIx last row number in the area
|
* sets the first row
|
||||||
*/
|
* @param rowIx number (0-based)
|
||||||
public final void setLastRow(int rowIx) {
|
*/
|
||||||
checkRowBounds(rowIx);
|
public final void setFirstRow(int rowIx) {
|
||||||
field_2_last_row = rowIx;
|
checkRowBounds(rowIx);
|
||||||
}
|
field_1_first_row = rowIx;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the first column number in the area.
|
* @return last row in the range (x2 in x1,y1-x2,y2)
|
||||||
*/
|
*/
|
||||||
public final int getFirstColumn() {
|
public final int getLastRow() {
|
||||||
return columnMask.getValue(field_3_first_column);
|
return field_2_last_row;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the first column number + the options bit settings unstripped
|
* @param rowIx last row number in the area
|
||||||
*/
|
*/
|
||||||
public final short getFirstColumnRaw() {
|
public final void setLastRow(int rowIx) {
|
||||||
return (short) field_3_first_column; // TODO
|
checkRowBounds(rowIx);
|
||||||
}
|
field_2_last_row = rowIx;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether or not the first row is a relative reference or not.
|
* @return the first column number in the area.
|
||||||
*/
|
*/
|
||||||
public final boolean isFirstRowRelative() {
|
public final int getFirstColumn() {
|
||||||
return rowRelative.isSet(field_3_first_column);
|
return columnMask.getValue(field_3_first_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sets the first row to relative or not
|
* @return the first column number + the options bit settings unstripped
|
||||||
* @param rel is relative or not.
|
*/
|
||||||
*/
|
public final short getFirstColumnRaw() {
|
||||||
public final void setFirstRowRelative(boolean rel) {
|
return (short) field_3_first_column; // TODO
|
||||||
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return isrelative first column to relative or not
|
* @return whether or not the first row is a relative reference or not.
|
||||||
*/
|
*/
|
||||||
public final boolean isFirstColRelative() {
|
public final boolean isFirstRowRelative() {
|
||||||
return colRelative.isSet(field_3_first_column);
|
return rowRelative.isSet(field_3_first_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set whether the first column is relative
|
* sets the first row to relative or not
|
||||||
*/
|
* @param rel is relative or not.
|
||||||
public final void setFirstColRelative(boolean rel) {
|
*/
|
||||||
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
|
public final void setFirstRowRelative(boolean rel) {
|
||||||
}
|
field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the first column in the area
|
* @return isrelative first column to relative or not
|
||||||
*/
|
*/
|
||||||
public final void setFirstColumn(int colIx) {
|
public final boolean isFirstColRelative() {
|
||||||
checkColumnBounds(colIx);
|
return colRelative.isSet(field_3_first_column);
|
||||||
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the first column irrespective of the bitmasks
|
* set whether the first column is relative
|
||||||
*/
|
*/
|
||||||
public final void setFirstColumnRaw(int column) {
|
public final void setFirstColRelative(boolean rel) {
|
||||||
field_3_first_column = column;
|
field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return lastcolumn in the area
|
* set the first column in the area
|
||||||
*/
|
*/
|
||||||
public final int getLastColumn() {
|
public final void setFirstColumn(int colIx) {
|
||||||
return columnMask.getValue(field_4_last_column);
|
checkColumnBounds(colIx);
|
||||||
}
|
field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return last column and bitmask (the raw field)
|
* set the first column irrespective of the bitmasks
|
||||||
*/
|
*/
|
||||||
public final short getLastColumnRaw() {
|
public final void setFirstColumnRaw(int column) {
|
||||||
return (short) field_4_last_column;
|
field_3_first_column = column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return last row relative or not
|
* @return lastcolumn in the area
|
||||||
*/
|
*/
|
||||||
public final boolean isLastRowRelative() {
|
public final int getLastColumn() {
|
||||||
return rowRelative.isSet(field_4_last_column);
|
return columnMask.getValue(field_4_last_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set whether the last row is relative or not
|
* @return last column and bitmask (the raw field)
|
||||||
* @param rel <code>true</code> if the last row relative, else
|
*/
|
||||||
* <code>false</code>
|
public final short getLastColumnRaw() {
|
||||||
*/
|
return (short) field_4_last_column;
|
||||||
public final void setLastRowRelative(boolean rel) {
|
}
|
||||||
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return lastcol relative or not
|
* @return last row relative or not
|
||||||
*/
|
*/
|
||||||
public final boolean isLastColRelative() {
|
public final boolean isLastRowRelative() {
|
||||||
return colRelative.isSet(field_4_last_column);
|
return rowRelative.isSet(field_4_last_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set whether the last column should be relative or not
|
* set whether the last row is relative or not
|
||||||
*/
|
* @param rel <code>true</code> if the last row relative, else
|
||||||
public final void setLastColRelative(boolean rel) {
|
* <code>false</code>
|
||||||
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
|
*/
|
||||||
}
|
public final void setLastRowRelative(boolean rel) {
|
||||||
|
field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the last column in the area
|
* @return lastcol relative or not
|
||||||
*/
|
*/
|
||||||
public final void setLastColumn(int colIx) {
|
public final boolean isLastColRelative() {
|
||||||
checkColumnBounds(colIx);
|
return colRelative.isSet(field_4_last_column);
|
||||||
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* set the last column irrespective of the bitmasks
|
* set whether the last column should be relative or not
|
||||||
*/
|
*/
|
||||||
public final void setLastColumnRaw(short column) {
|
public final void setLastColRelative(boolean rel) {
|
||||||
field_4_last_column = column;
|
field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
|
||||||
}
|
}
|
||||||
protected final String formatReferenceAsString() {
|
|
||||||
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
|
|
||||||
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
|
|
||||||
|
|
||||||
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
|
/**
|
||||||
return (new AreaReference(topLeft, botRight)).formatAsString();
|
* set the last column in the area
|
||||||
}
|
*/
|
||||||
return topLeft.formatAsString() + ":" + botRight.formatAsString();
|
public final void setLastColumn(int colIx) {
|
||||||
}
|
checkColumnBounds(colIx);
|
||||||
|
field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
|
||||||
|
}
|
||||||
|
|
||||||
public String toFormulaString() {
|
/**
|
||||||
return formatReferenceAsString();
|
* set the last column irrespective of the bitmasks
|
||||||
}
|
*/
|
||||||
|
public final void setLastColumnRaw(short column) {
|
||||||
|
field_4_last_column = column;
|
||||||
|
}
|
||||||
|
protected final String formatReferenceAsString() {
|
||||||
|
CellReference topLeft = new CellReference(getFirstRow(),getFirstColumn(),!isFirstRowRelative(),!isFirstColRelative());
|
||||||
|
CellReference botRight = new CellReference(getLastRow(),getLastColumn(),!isLastRowRelative(),!isLastColRelative());
|
||||||
|
|
||||||
public byte getDefaultOperandClass() {
|
if(AreaReference.isWholeColumnReference(topLeft, botRight)) {
|
||||||
return Ptg.CLASS_REF;
|
return (new AreaReference(topLeft, botRight)).formatAsString();
|
||||||
}
|
}
|
||||||
|
return topLeft.formatAsString() + ":" + botRight.formatAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toFormulaString() {
|
||||||
|
return formatReferenceAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getDefaultOperandClass() {
|
||||||
|
return Ptg.CLASS_REF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,13 +101,27 @@ public final class SheetNameFormatter {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (nameLooksLikeBooleanLiteral(rawSheetName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Error constant literals all contain '#' and other special characters
|
||||||
|
// so they don't get this far
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean nameLooksLikeBooleanLiteral(String rawSheetName) {
|
||||||
|
switch(rawSheetName.charAt(0)) {
|
||||||
|
case 'T': case 't':
|
||||||
|
return "TRUE".equalsIgnoreCase(rawSheetName);
|
||||||
|
case 'F': case 'f':
|
||||||
|
return "FALSE".equalsIgnoreCase(rawSheetName);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* @return <code>true</code> if the presence of the specified character in a sheet name would
|
* @return <code>true</code> if the presence of the specified character in a sheet name would
|
||||||
* require the sheet name to be delimited in formulas. This includes every non-alphanumeric
|
* require the sheet name to be delimited in formulas. This includes every non-alphanumeric
|
||||||
* character besides underscore '_'.
|
* character besides underscore '_' and dot '.'.
|
||||||
*/
|
*/
|
||||||
/* package */ static boolean isSpecialChar(char ch) {
|
/* package */ static boolean isSpecialChar(char ch) {
|
||||||
// note - Character.isJavaIdentifierPart() would allow dollars '$'
|
// note - Character.isJavaIdentifierPart() would allow dollars '$'
|
||||||
|
@ -115,7 +129,8 @@ public final class SheetNameFormatter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
switch(ch) {
|
switch(ch) {
|
||||||
case '_': // underscore is ok
|
case '.': // dot is OK
|
||||||
|
case '_': // underscore is OK
|
||||||
return false;
|
return false;
|
||||||
case '\n':
|
case '\n':
|
||||||
case '\r':
|
case '\r':
|
||||||
|
|
|
@ -90,7 +90,7 @@ public final class AreaReference {
|
||||||
for(int i=refPart.length()-1; i>=0; i--) {
|
for(int i=refPart.length()-1; i>=0; i--) {
|
||||||
int ch = refPart.charAt(i);
|
int ch = refPart.charAt(i);
|
||||||
if (ch == '$' && i==0) {
|
if (ch == '$' && i==0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ch < 'A' || ch > 'Z') {
|
if (ch < 'A' || ch > 'Z') {
|
||||||
return false;
|
return false;
|
||||||
|
@ -101,10 +101,48 @@ public final class AreaReference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an area ref from a pair of Cell References.
|
* Creates an area ref from a pair of Cell References.
|
||||||
|
* Also normalises such that the top-left
|
||||||
*/
|
*/
|
||||||
public AreaReference(CellReference topLeft, CellReference botRight) {
|
public AreaReference(CellReference topLeft, CellReference botRight) {
|
||||||
_firstCell = topLeft;
|
boolean swapRows = topLeft.getRow() > botRight.getRow();
|
||||||
_lastCell = botRight;
|
boolean swapCols = topLeft.getCol() > botRight.getCol();
|
||||||
|
if (swapRows || swapCols) {
|
||||||
|
int firstRow;
|
||||||
|
int lastRow;
|
||||||
|
int firstColumn;
|
||||||
|
int lastColumn;
|
||||||
|
boolean firstRowAbs;
|
||||||
|
boolean lastRowAbs;
|
||||||
|
boolean firstColAbs;
|
||||||
|
boolean lastColAbs;
|
||||||
|
if (swapRows) {
|
||||||
|
firstRow = botRight.getRow();
|
||||||
|
firstRowAbs = botRight.isRowAbsolute();
|
||||||
|
lastRow = topLeft.getRow();
|
||||||
|
lastRowAbs = topLeft.isRowAbsolute();
|
||||||
|
} else {
|
||||||
|
firstRow = topLeft.getRow();
|
||||||
|
firstRowAbs = topLeft.isRowAbsolute();
|
||||||
|
lastRow = botRight.getRow();
|
||||||
|
lastRowAbs = botRight.isRowAbsolute();
|
||||||
|
}
|
||||||
|
if (swapCols) {
|
||||||
|
firstColumn = botRight.getCol();
|
||||||
|
firstColAbs = botRight.isColAbsolute();
|
||||||
|
lastColumn = topLeft.getCol();
|
||||||
|
lastColAbs = topLeft.isColAbsolute();
|
||||||
|
} else {
|
||||||
|
firstColumn = topLeft.getCol();
|
||||||
|
firstColAbs = topLeft.isColAbsolute();
|
||||||
|
lastColumn = botRight.getCol();
|
||||||
|
lastColAbs = botRight.isColAbsolute();
|
||||||
|
}
|
||||||
|
_firstCell = new CellReference(firstRow, firstColumn, firstRowAbs, firstColAbs);
|
||||||
|
_lastCell = new CellReference(lastRow, lastColumn, lastRowAbs, lastColAbs);
|
||||||
|
} else {
|
||||||
|
_firstCell = topLeft;
|
||||||
|
_lastCell = botRight;
|
||||||
|
}
|
||||||
_isSingleCell = false;
|
_isSingleCell = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ public final class CellReference {
|
||||||
public static final class NameType {
|
public static final class NameType {
|
||||||
public static final int CELL = 1;
|
public static final int CELL = 1;
|
||||||
public static final int NAMED_RANGE = 2;
|
public static final int NAMED_RANGE = 2;
|
||||||
|
public static final int COLUMN = 3;
|
||||||
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
|
public static final int BAD_CELL_OR_NAMED_RANGE = -1;
|
||||||
}
|
}
|
||||||
/** The character ($) that signifies a row or column value is absolute instead of relative */
|
/** The character ($) that signifies a row or column value is absolute instead of relative */
|
||||||
|
@ -44,10 +45,16 @@ public final class CellReference {
|
||||||
private static final char SPECIAL_NAME_DELIMITER = '\'';
|
private static final char SPECIAL_NAME_DELIMITER = '\'';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches a run of letters followed by a run of digits. The run of letters is group 1 and the
|
* Matches a run of one or more letters followed by a run of one or more digits.
|
||||||
* run of digits is group 2. Each group may optionally be prefixed with a single '$'.
|
* The run of letters is group 1 and the run of digits is group 2.
|
||||||
|
* Each group may optionally be prefixed with a single '$'.
|
||||||
*/
|
*/
|
||||||
private static final Pattern CELL_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)\\$?([0-9]+)");
|
private static final Pattern CELL_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)\\$?([0-9]+)");
|
||||||
|
/**
|
||||||
|
* Matches a run of one or more letters. The run of letters is group 1.
|
||||||
|
* The text may optionally be prefixed with a single '$'.
|
||||||
|
*/
|
||||||
|
private static final Pattern COLUMN_REF_PATTERN = Pattern.compile("\\$?([A-Za-z]+)");
|
||||||
/**
|
/**
|
||||||
* Named range names must start with a letter or underscore. Subsequent characters may include
|
* Named range names must start with a letter or underscore. Subsequent characters may include
|
||||||
* digits or dot. (They can even end in dot).
|
* digits or dot. (They can even end in dot).
|
||||||
|
@ -203,7 +210,7 @@ public final class CellReference {
|
||||||
// named range name
|
// named range name
|
||||||
// This behaviour is a little weird. For example, "IW123" is a valid named range name
|
// This behaviour is a little weird. For example, "IW123" is a valid named range name
|
||||||
// because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
|
// because the column "IW" is beyond the maximum "IV". Note - this behaviour is version
|
||||||
// dependent. In Excel 2007, "IW123" is not a valid named range name.
|
// dependent. In BIFF12, "IW123" is not a valid named range name, but in BIFF8 it is.
|
||||||
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
|
if (str.indexOf(ABSOLUTE_REFERENCE_MARKER) >= 0) {
|
||||||
// Of course, named range names cannot have '$'
|
// Of course, named range names cannot have '$'
|
||||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||||
|
@ -212,11 +219,17 @@ public final class CellReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int validateNamedRangeName(String str) {
|
private static int validateNamedRangeName(String str) {
|
||||||
|
Matcher colMatcher = COLUMN_REF_PATTERN.matcher(str);
|
||||||
|
if (colMatcher.matches()) {
|
||||||
|
String colStr = colMatcher.group(1);
|
||||||
|
if (isColumnWithnRange(colStr)) {
|
||||||
|
return NameType.COLUMN;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
|
if (!NAMED_RANGE_NAME_PATTERN.matcher(str).matches()) {
|
||||||
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
return NameType.BAD_CELL_OR_NAMED_RANGE;
|
||||||
}
|
}
|
||||||
return NameType.NAMED_RANGE;
|
return NameType.NAMED_RANGE;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -257,23 +270,13 @@ public final class CellReference {
|
||||||
* @return <code>true</code> if the row and col parameters are within range of a BIFF8 spreadsheet.
|
* @return <code>true</code> if the row and col parameters are within range of a BIFF8 spreadsheet.
|
||||||
*/
|
*/
|
||||||
public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
|
public static boolean cellReferenceIsWithinRange(String colStr, String rowStr) {
|
||||||
int numberOfLetters = colStr.length();
|
if (!isColumnWithnRange(colStr)) {
|
||||||
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
|
return false;
|
||||||
// "Sheet1" case etc
|
|
||||||
return false; // that was easy
|
|
||||||
}
|
}
|
||||||
int nDigits = rowStr.length();
|
int nDigits = rowStr.length();
|
||||||
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
|
if(nDigits > BIFF8_LAST_ROW_TEXT_LEN) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
|
|
||||||
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// apparent column name has less chars than max
|
|
||||||
// no need to check range
|
|
||||||
}
|
|
||||||
|
|
||||||
if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
|
if(nDigits == BIFF8_LAST_ROW_TEXT_LEN) {
|
||||||
// ASCII comparison is valid if digit count is same
|
// ASCII comparison is valid if digit count is same
|
||||||
|
@ -288,6 +291,23 @@ public final class CellReference {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isColumnWithnRange(String colStr) {
|
||||||
|
int numberOfLetters = colStr.length();
|
||||||
|
if(numberOfLetters > BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||||
|
// "Sheet1" case etc
|
||||||
|
return false; // that was easy
|
||||||
|
}
|
||||||
|
if(numberOfLetters == BIFF8_LAST_COLUMN_TEXT_LEN) {
|
||||||
|
if(colStr.toUpperCase().compareTo(BIFF8_LAST_COLUMN) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// apparent column name has less chars than max
|
||||||
|
// no need to check range
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Separates the row from the columns and returns an array of three Strings. The first element
|
* 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
|
* is the sheet name. Only the first element may be null. The second element in is the column
|
||||||
|
|
|
@ -49,6 +49,7 @@ import org.apache.poi.hssf.record.formula.ParenthesisPtg;
|
||||||
import org.apache.poi.hssf.record.formula.PercentPtg;
|
import org.apache.poi.hssf.record.formula.PercentPtg;
|
||||||
import org.apache.poi.hssf.record.formula.PowerPtg;
|
import org.apache.poi.hssf.record.formula.PowerPtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ptg;
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ref3DPtg;
|
import org.apache.poi.hssf.record.formula.Ref3DPtg;
|
||||||
import org.apache.poi.hssf.record.formula.RefPtg;
|
import org.apache.poi.hssf.record.formula.RefPtg;
|
||||||
import org.apache.poi.hssf.record.formula.StringPtg;
|
import org.apache.poi.hssf.record.formula.StringPtg;
|
||||||
|
@ -81,6 +82,33 @@ import org.apache.poi.hssf.util.CellReference.NameType;
|
||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
public final class FormulaParser {
|
public final class FormulaParser {
|
||||||
|
private static final class Identifier {
|
||||||
|
private final String _name;
|
||||||
|
private final boolean _isQuoted;
|
||||||
|
|
||||||
|
public Identifier(String name, boolean isQuoted) {
|
||||||
|
_name = name;
|
||||||
|
_isQuoted = isQuoted;
|
||||||
|
}
|
||||||
|
public String getName() {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
public boolean isQuoted() {
|
||||||
|
return _isQuoted;
|
||||||
|
}
|
||||||
|
public String toString() {
|
||||||
|
StringBuffer sb = new StringBuffer(64);
|
||||||
|
sb.append(getClass().getName());
|
||||||
|
sb.append(" [");
|
||||||
|
if (_isQuoted) {
|
||||||
|
sb.append("'").append(_name).append("'");
|
||||||
|
} else {
|
||||||
|
sb.append(_name);
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific exception thrown when a supplied formula does not parse properly.<br/>
|
* Specific exception thrown when a supplied formula does not parse properly.<br/>
|
||||||
|
@ -176,23 +204,23 @@ public final class FormulaParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recognize an Alpha Character */
|
/** Recognize an Alpha Character */
|
||||||
private boolean IsAlpha(char c) {
|
private static boolean IsAlpha(char c) {
|
||||||
return Character.isLetter(c) || c == '$' || c=='_';
|
return Character.isLetter(c) || c == '$' || c=='_';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recognize a Decimal Digit */
|
/** Recognize a Decimal Digit */
|
||||||
private boolean IsDigit(char c) {
|
private static boolean IsDigit(char c) {
|
||||||
return Character.isDigit(c);
|
return Character.isDigit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recognize an Alphanumeric */
|
/** Recognize an Alphanumeric */
|
||||||
private boolean IsAlNum(char c) {
|
private static boolean IsAlNum(char c) {
|
||||||
return (IsAlpha(c) || IsDigit(c));
|
return IsAlpha(c) || IsDigit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recognize White Space */
|
/** Recognize White Space */
|
||||||
private boolean IsWhite( char c) {
|
private static boolean IsWhite( char c) {
|
||||||
return (c ==' ' || c== TAB);
|
return c ==' ' || c== TAB;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Skip Over Leading White Space */
|
/** Skip Over Leading White Space */
|
||||||
|
@ -213,7 +241,13 @@ public final class FormulaParser {
|
||||||
}
|
}
|
||||||
GetChar();
|
GetChar();
|
||||||
}
|
}
|
||||||
|
private String parseUnquotedIdentifier() {
|
||||||
|
Identifier iden = parseIdentifier();
|
||||||
|
if (iden.isQuoted()) {
|
||||||
|
throw expected("unquoted identifier");
|
||||||
|
}
|
||||||
|
return iden.getName();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Parses a sheet name, named range name, or simple cell reference.<br/>
|
* Parses a sheet name, named range name, or simple cell reference.<br/>
|
||||||
* Note - identifiers in Excel can contain dots, so this method may return a String
|
* Note - identifiers in Excel can contain dots, so this method may return a String
|
||||||
|
@ -221,18 +255,17 @@ public final class FormulaParser {
|
||||||
* may return a value like "A1..B2", in which case the caller must convert it to
|
* may return a value like "A1..B2", in which case the caller must convert it to
|
||||||
* an area reference like "A1:B2"
|
* an area reference like "A1:B2"
|
||||||
*/
|
*/
|
||||||
private String parseIdentifier() {
|
private Identifier parseIdentifier() {
|
||||||
StringBuffer Token = new StringBuffer();
|
StringBuffer sb = new StringBuffer();
|
||||||
if (!IsAlpha(look) && look != '\'') {
|
if (!IsAlpha(look) && look != '\'') {
|
||||||
throw expected("Name");
|
throw expected("Name");
|
||||||
}
|
}
|
||||||
if(look == '\'')
|
boolean isQuoted = look == '\'';
|
||||||
{
|
if(isQuoted) {
|
||||||
Match('\'');
|
Match('\'');
|
||||||
boolean done = look == '\'';
|
boolean done = look == '\'';
|
||||||
while(!done)
|
while(!done) {
|
||||||
{
|
sb.append(look);
|
||||||
Token.append(look);
|
|
||||||
GetChar();
|
GetChar();
|
||||||
if(look == '\'')
|
if(look == '\'')
|
||||||
{
|
{
|
||||||
|
@ -240,17 +273,15 @@ public final class FormulaParser {
|
||||||
done = look != '\'';
|
done = look != '\'';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// allow for any sequence of dots and identifier chars
|
// allow for any sequence of dots and identifier chars
|
||||||
// special case of two consecutive dots is best treated in the calling code
|
// special case of two consecutive dots is best treated in the calling code
|
||||||
while (IsAlNum(look) || look == '.') {
|
while (IsAlNum(look) || look == '.') {
|
||||||
Token.append(look);
|
sb.append(look);
|
||||||
GetChar();
|
GetChar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Token.toString();
|
return new Identifier(sb.toString(), isQuoted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get a Number */
|
/** Get a Number */
|
||||||
|
@ -265,72 +296,112 @@ public final class FormulaParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParseNode parseFunctionReferenceOrName() {
|
private ParseNode parseFunctionReferenceOrName() {
|
||||||
String name = parseIdentifier();
|
Identifier iden = parseIdentifier();
|
||||||
if (look == '('){
|
if (look == '('){
|
||||||
//This is a function
|
//This is a function
|
||||||
return function(name);
|
return function(iden.getName());
|
||||||
}
|
}
|
||||||
return new ParseNode(parseNameOrReference(name));
|
if (!iden.isQuoted()) {
|
||||||
|
String name = iden.getName();
|
||||||
|
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
|
||||||
|
return new ParseNode(new BoolPtg(name.toUpperCase()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parseRangeExpression(iden);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Ptg parseNameOrReference(String name) {
|
private ParseNode parseRangeExpression(Identifier iden) {
|
||||||
|
Ptg ptgA = parseNameOrCellRef(iden);
|
||||||
|
if (look == ':') {
|
||||||
|
GetChar();
|
||||||
|
Identifier iden2 = parseIdentifier();
|
||||||
|
Ptg ptgB = parseNameOrCellRef(iden2);
|
||||||
|
Ptg simplified = reduceRangeExpression(ptgA, ptgB);
|
||||||
|
|
||||||
|
if (simplified == null) {
|
||||||
|
ParseNode[] children = {
|
||||||
|
new ParseNode(ptgA),
|
||||||
|
new ParseNode(ptgB),
|
||||||
|
};
|
||||||
|
return new ParseNode(RangePtg.instance, children);
|
||||||
|
}
|
||||||
|
return new ParseNode(simplified);
|
||||||
|
}
|
||||||
|
return new ParseNode(ptgA);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* "A1", "B3" -> "A1:B3"
|
||||||
|
* "sheet1!A1", "B3" -> "sheet1!A1:B3"
|
||||||
|
*
|
||||||
|
* @return <code>null</code> if the range expression cannot / shouldn't be reduced.
|
||||||
|
*/
|
||||||
|
private static Ptg reduceRangeExpression(Ptg ptgA, Ptg ptgB) {
|
||||||
|
if (!(ptgB instanceof RefPtg)) {
|
||||||
|
// only when second ref is simple 2-D ref can the range
|
||||||
|
// expression be converted to an area ref
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
RefPtg refB = (RefPtg) ptgB;
|
||||||
|
|
||||||
|
if (ptgA instanceof RefPtg) {
|
||||||
|
RefPtg refA = (RefPtg) ptgA;
|
||||||
|
return new AreaPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
|
||||||
|
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative());
|
||||||
|
}
|
||||||
|
if (ptgA instanceof Ref3DPtg) {
|
||||||
|
Ref3DPtg refA = (Ref3DPtg) ptgA;
|
||||||
|
return new Area3DPtg(refA.getRow(), refB.getRow(), refA.getColumn(), refB.getColumn(),
|
||||||
|
refA.isRowRelative(), refB.isRowRelative(), refA.isColRelative(), refB.isColRelative(),
|
||||||
|
refA.getExternSheetIndex());
|
||||||
|
}
|
||||||
|
// Note - other operand types (like AreaPtg) which probably can't evaluate
|
||||||
|
// do not cause validation errors at parse time
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ptg parseNameOrCellRef(Identifier iden) {
|
||||||
|
|
||||||
|
if (look == '!') {
|
||||||
|
GetChar();
|
||||||
|
// 3-D ref
|
||||||
|
// this code assumes iden is a sheetName
|
||||||
|
// TODO - handle <book name> ! <named range name>
|
||||||
|
int externIdx = book.getExternalSheetIndex(iden.getName());
|
||||||
|
String secondIden = parseUnquotedIdentifier();
|
||||||
|
AreaReference areaRef = parseArea(secondIden);
|
||||||
|
if (areaRef == null) {
|
||||||
|
return new Ref3DPtg(secondIden, externIdx);
|
||||||
|
}
|
||||||
|
// will happen if dots are used instead of colon
|
||||||
|
return new Area3DPtg(areaRef.formatAsString(), externIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = iden.getName();
|
||||||
AreaReference areaRef = parseArea(name);
|
AreaReference areaRef = parseArea(name);
|
||||||
if (areaRef != null) {
|
if (areaRef != null) {
|
||||||
// will happen if dots are used instead of colon
|
// will happen if dots are used instead of colon
|
||||||
return new AreaPtg(areaRef.formatAsString());
|
return new AreaPtg(areaRef.formatAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (look == ':' || look == '.') { // this is a AreaReference
|
|
||||||
GetChar();
|
|
||||||
|
|
||||||
while (look == '.') { // formulas can have . or .. or ... instead of :
|
|
||||||
GetChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
String first = name;
|
|
||||||
String second = parseIdentifier();
|
|
||||||
return new AreaPtg(first+":"+second);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (look == '!') {
|
|
||||||
Match('!');
|
|
||||||
String sheetName = name;
|
|
||||||
String first = parseIdentifier();
|
|
||||||
int externIdx = book.getExternalSheetIndex(sheetName);
|
|
||||||
areaRef = parseArea(name);
|
|
||||||
if (areaRef != null) {
|
|
||||||
// will happen if dots are used instead of colon
|
|
||||||
return new Area3DPtg(areaRef.formatAsString(), externIdx);
|
|
||||||
}
|
|
||||||
if (look == ':') {
|
|
||||||
Match(':');
|
|
||||||
String second=parseIdentifier();
|
|
||||||
if (look == '!') {
|
|
||||||
//The sheet name was included in both of the areas. Only really
|
|
||||||
//need it once
|
|
||||||
Match('!');
|
|
||||||
String third=parseIdentifier();
|
|
||||||
|
|
||||||
if (!sheetName.equals(second))
|
|
||||||
throw new RuntimeException("Unhandled double sheet reference.");
|
|
||||||
|
|
||||||
return new Area3DPtg(first+":"+third,externIdx);
|
|
||||||
}
|
|
||||||
return new Area3DPtg(first+":"+second,externIdx);
|
|
||||||
}
|
|
||||||
return new Ref3DPtg(first, externIdx);
|
|
||||||
}
|
|
||||||
if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
|
|
||||||
return new BoolPtg(name.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
// This can be either a cell ref or a named range
|
// This can be either a cell ref or a named range
|
||||||
// Try to spot which it is
|
|
||||||
|
|
||||||
int nameType = CellReference.classifyCellReference(name);
|
int nameType = CellReference.classifyCellReference(name);
|
||||||
if (nameType == NameType.CELL) {
|
if (nameType == NameType.CELL) {
|
||||||
return new RefPtg(name);
|
return new RefPtg(name);
|
||||||
}
|
}
|
||||||
|
if (look == ':') {
|
||||||
|
if (nameType == NameType.COLUMN) {
|
||||||
|
GetChar();
|
||||||
|
String secondIden = parseUnquotedIdentifier();
|
||||||
|
if (CellReference.classifyCellReference(secondIden) != NameType.COLUMN) {
|
||||||
|
throw new FormulaParseException("Expected full column after '" + name
|
||||||
|
+ ":' but got '" + secondIden + "'");
|
||||||
|
}
|
||||||
|
return new AreaPtg(name + ":" + secondIden);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (nameType != NameType.NAMED_RANGE) {
|
if (nameType != NameType.NAMED_RANGE) {
|
||||||
new FormulaParseException("Name '" + name
|
new FormulaParseException("Name '" + name
|
||||||
+ "' does not look like a cell reference or named range");
|
+ "' does not look like a cell reference or named range");
|
||||||
|
@ -662,7 +733,7 @@ public final class FormulaParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Boolean parseBooleanLiteral() {
|
private Boolean parseBooleanLiteral() {
|
||||||
String iden = parseIdentifier();
|
String iden = parseUnquotedIdentifier();
|
||||||
if ("TRUE".equalsIgnoreCase(iden)) {
|
if ("TRUE".equalsIgnoreCase(iden)) {
|
||||||
return Boolean.TRUE;
|
return Boolean.TRUE;
|
||||||
}
|
}
|
||||||
|
@ -720,7 +791,7 @@ public final class FormulaParser {
|
||||||
|
|
||||||
private int parseErrorLiteral() {
|
private int parseErrorLiteral() {
|
||||||
Match('#');
|
Match('#');
|
||||||
String part1 = parseIdentifier().toUpperCase();
|
String part1 = parseUnquotedIdentifier().toUpperCase();
|
||||||
|
|
||||||
switch(part1.charAt(0)) {
|
switch(part1.charAt(0)) {
|
||||||
case 'V':
|
case 'V':
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.apache.poi.ss.formula;
|
||||||
|
|
||||||
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
|
import org.apache.poi.hssf.record.formula.AbstractFunctionPtg;
|
||||||
import org.apache.poi.hssf.record.formula.ControlPtg;
|
import org.apache.poi.hssf.record.formula.ControlPtg;
|
||||||
|
import org.apache.poi.hssf.record.formula.RangePtg;
|
||||||
import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
|
import org.apache.poi.hssf.record.formula.ValueOperatorPtg;
|
||||||
import org.apache.poi.hssf.record.formula.Ptg;
|
import org.apache.poi.hssf.record.formula.Ptg;
|
||||||
|
|
||||||
|
@ -115,6 +116,10 @@ final class OperandClassTransformer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
|
if (token == RangePtg.instance) {
|
||||||
|
// TODO is any token transformation required under the various ref operators?
|
||||||
|
return;
|
||||||
|
}
|
||||||
throw new IllegalStateException("Node should not have any children");
|
throw new IllegalStateException("Node should not have any children");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -880,6 +880,37 @@ public final class TestFormulaParser extends TestCase {
|
||||||
Object[][] values = aptg.getTokenArrayValues();
|
Object[][] values = aptg.getTokenArrayValues();
|
||||||
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
|
assertEquals(ErrorConstant.valueOf(HSSFErrorConstants.ERROR_REF), values[0][3]);
|
||||||
assertEquals(Boolean.FALSE, values[1][0]);
|
assertEquals(Boolean.FALSE, values[1][0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRangeOperator() {
|
||||||
|
|
||||||
|
HSSFWorkbook wb = new HSSFWorkbook();
|
||||||
|
HSSFSheet sheet = wb.createSheet();
|
||||||
|
HSSFCell cell = sheet.createRow(0).createCell(0);
|
||||||
|
|
||||||
|
wb.setSheetName(0, "Sheet1");
|
||||||
|
cell.setCellFormula("Sheet1!B$4:Sheet1!$C1"); // explicit range ':' operator
|
||||||
|
assertEquals("Sheet1!B$4:Sheet1!$C1", cell.getCellFormula());
|
||||||
|
|
||||||
|
cell.setCellFormula("Sheet1!B$4:$C1"); // plain area ref
|
||||||
|
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula()); // note - area ref is normalised
|
||||||
|
|
||||||
|
cell.setCellFormula("Sheet1!$C1...B$4"); // different syntax for plain area ref
|
||||||
|
assertEquals("Sheet1!B1:$C$4", cell.getCellFormula());
|
||||||
|
|
||||||
|
// with funny sheet name
|
||||||
|
wb.setSheetName(0, "A1...A2");
|
||||||
|
cell.setCellFormula("A1...A2!B1");
|
||||||
|
assertEquals("A1...A2!B1", cell.getCellFormula());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBooleanNamedSheet() {
|
||||||
|
|
||||||
|
HSSFWorkbook wb = new HSSFWorkbook();
|
||||||
|
HSSFSheet sheet = wb.createSheet("true");
|
||||||
|
HSSFCell cell = sheet.createRow(0).createCell(0);
|
||||||
|
cell.setCellFormula("'true'!B2");
|
||||||
|
|
||||||
|
assertEquals("'true'!B2", cell.getCellFormula());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -20,16 +20,12 @@ package org.apache.poi.hssf.record.formula;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for SheetNameFormatter
|
* Tests for {@link SheetNameFormatter}
|
||||||
*
|
*
|
||||||
* @author Josh Micich
|
* @author Josh Micich
|
||||||
*/
|
*/
|
||||||
public final class TestSheetNameFormatter extends TestCase {
|
public final class TestSheetNameFormatter extends TestCase {
|
||||||
|
|
||||||
public TestSheetNameFormatter(String testName) {
|
|
||||||
super(testName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
|
private static void confirmFormat(String rawSheetName, String expectedSheetNameEncoding) {
|
||||||
assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
|
assertEquals(expectedSheetNameEncoding, SheetNameFormatter.format(rawSheetName));
|
||||||
}
|
}
|
||||||
|
@ -55,6 +51,16 @@ public final class TestSheetNameFormatter extends TestCase {
|
||||||
confirmFormat("TAXRETURN19980415", "TAXRETURN19980415");
|
confirmFormat("TAXRETURN19980415", "TAXRETURN19980415");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testBooleanLiterals() {
|
||||||
|
confirmFormat("TRUE", "'TRUE'");
|
||||||
|
confirmFormat("FALSE", "'FALSE'");
|
||||||
|
confirmFormat("True", "'True'");
|
||||||
|
confirmFormat("fAlse", "'fAlse'");
|
||||||
|
|
||||||
|
confirmFormat("Yes", "Yes");
|
||||||
|
confirmFormat("No", "No");
|
||||||
|
}
|
||||||
|
|
||||||
private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
|
private static void confirmCellNameMatch(String rawSheetName, boolean expected) {
|
||||||
assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
|
assertEquals(expected, SheetNameFormatter.nameLooksLikePlainCellReference(rawSheetName));
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ public final class TestFormulaBugs extends TestCase {
|
||||||
throw new AssertionFailedError("Identified bug 42448");
|
throw new AssertionFailedError("Identified bug 42448");
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals("SUMPRODUCT(A!C7:C67,B8:B68)/B69", cell.getCellFormula());
|
assertEquals("SUMPRODUCT(A!C7:A!C67,B8:B68)/B69", cell.getCellFormula());
|
||||||
|
|
||||||
// might as well evaluate the sucker...
|
// might as well evaluate the sucker...
|
||||||
|
|
||||||
|
|
|
@ -1064,7 +1064,7 @@ public final class TestFormulas extends TestCase {
|
||||||
/** Unknown Ptg 3D*/
|
/** Unknown Ptg 3D*/
|
||||||
public void test27272_2() throws Exception {
|
public void test27272_2() throws Exception {
|
||||||
HSSFWorkbook wb = openSample("27272_2.xls");
|
HSSFWorkbook wb = openSample("27272_2.xls");
|
||||||
assertEquals("Reference for named range ", "'LOAD.POD_HISTORIES'!#REF!",wb.getNameAt(0).getReference());
|
assertEquals("LOAD.POD_HISTORIES!#REF!", wb.getNameAt(0).getReference());
|
||||||
File outF = File.createTempFile("bug27272_2",".xls");
|
File outF = File.createTempFile("bug27272_2",".xls");
|
||||||
wb.write(new FileOutputStream(outF));
|
wb.write(new FileOutputStream(outF));
|
||||||
System.out.println("Open "+outF.getAbsolutePath()+" in Excel");
|
System.out.println("Open "+outF.getAbsolutePath()+" in Excel");
|
||||||
|
|
Loading…
Reference in New Issue