]*
@@ -89,339 +91,383 @@ import org.apache.poi.util.POILogger;
*
*/
public final class FormulaParser {
- private final static POILogger log = POILogFactory.getLogger(FormulaParser.class);
- private final String _formulaString;
- private final int _formulaLength;
- /** points at the next character to be read (after the {@link #look} char) */
- private int _pointer;
+ private final static POILogger log = POILogFactory.getLogger(FormulaParser.class);
+ private final String _formulaString;
+ private final int _formulaLength;
+ /** points at the next character to be read (after the {@link #look} char) */
+ private int _pointer;
- private ParseNode _rootNode;
+ private ParseNode _rootNode;
- private final static char TAB = '\t'; // HSSF + XSSF
- private final static char CR = '\r'; // Normally just XSSF
- private final static char LF = '\n'; // Normally just XSSF
+ private final static char TAB = '\t'; // HSSF + XSSF
+ private final static char CR = '\r'; // Normally just XSSF
+ private final static char LF = '\n'; // Normally just XSSF
- /**
- * Lookahead Character.
- * gets value '\0' when the input string is exhausted
- */
- private char look;
+ /**
+ * Lookahead Character.
+ * gets value '\0' when the input string is exhausted
+ */
+ private char look;
/**
* Tracks whether the run of whitespace preceeding "look" could be an
* intersection operator. See GetChar.
*/
- private boolean _inIntersection = false;
+ private boolean _inIntersection = false;
- private final FormulaParsingWorkbook _book;
- private final SpreadsheetVersion _ssVersion;
+ private final FormulaParsingWorkbook _book;
+ private final SpreadsheetVersion _ssVersion;
- private final int _sheetIndex;
+ private final int _sheetIndex;
+ private final int _rowIndex; // 0-based
- /**
- * Create the formula parser, with the string that is to be
- * parsed against the supplied workbook.
- * A later call the parse() method to return ptg list in
- * rpn order, then call the getRPNPtg() to retrieve the
- * parse results.
- * This class is recommended only for single threaded use.
- *
- * If you only have a usermodel.HSSFWorkbook, and not a
- * model.Workbook, then use the convenience method on
- * usermodel.HSSFFormulaEvaluator
- */
- private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex){
- _formulaString = formula;
- _pointer=0;
- _book = book;
- _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
- _formulaLength = _formulaString.length();
- _sheetIndex = sheetIndex;
- }
+ /**
+ * Create the formula parser, with the string that is to be
+ * parsed against the supplied workbook.
+ * A later call the parse() method to return ptg list in
+ * rpn order, then call the getRPNPtg() to retrieve the
+ * parse results.
+ * This class is recommended only for single threaded use.
+ *
+ * If you only have a usermodel.HSSFWorkbook, and not a
+ * model.Workbook, then use the convenience method on
+ * usermodel.HSSFFormulaEvaluator
+ */
+ private FormulaParser(String formula, FormulaParsingWorkbook book, int sheetIndex, int rowIndex){
+ _formulaString = formula;
+ _pointer=0;
+ _book = book;
+ _ssVersion = book == null ? SpreadsheetVersion.EXCEL97 : book.getSpreadsheetVersion();
+ _formulaLength = _formulaString.length();
+ _sheetIndex = sheetIndex;
+ _rowIndex = rowIndex;
+ }
- /**
- * Parse a formula into a array of tokens
- * Side effect: creates name (Workbook.createName) if formula contains unrecognized names (names are likely UDFs)
- *
- * @param formula the formula to parse
- * @param workbook the parent workbook
- * @param formulaType the type of the formula, see {@link FormulaType}
- * @param sheetIndex the 0-based index of the sheet this formula belongs to.
- * The sheet index is required to resolve sheet-level names. -1
means that
- * the scope of the name will be ignored and the parser will match names only by name
- *
- * @return array of parsed tokens
- * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
- */
- public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
- FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex);
- fp.parse();
- return fp.getRPNPtg(formulaType);
- }
-
- /** Read New Character From Input Stream */
- private void GetChar() {
- // The intersection operator is a space. We track whether the run of
- // whitespace preceeding "look" counts as an intersection operator.
- if (IsWhite(look)) {
- if (look == ' ') {
- _inIntersection = true;
- }
- }
- else {
- _inIntersection = false;
- }
-
- // Check to see if we've walked off the end of the string.
- if (_pointer > _formulaLength) {
- throw new RuntimeException("too far");
- }
- if (_pointer < _formulaLength) {
- look=_formulaString.charAt(_pointer);
- } else {
- // Just return if so and reset 'look' to something to keep
- // SkipWhitespace from spinning
- look = (char)0;
- _inIntersection = false;
- }
- _pointer++;
- //System.out.println("Got char: "+ look);
- }
- private void resetPointer(int ptr) {
- _pointer = ptr;
- if (_pointer <= _formulaLength) {
- look=_formulaString.charAt(_pointer-1);
- } else {
- // Just return if so and reset 'look' to something to keep
- // SkipWhitespace from spinning
- look = (char)0;
- }
- }
+ /**
+ * Parse a formula into an array of tokens
+ * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
+ * if formula contains unrecognized names (names are likely UDFs)
+ *
+ * @param formula the formula to parse
+ * @param workbook the parent workbook
+ * @param formulaType the type of the formula, see {@link FormulaType}
+ * @param sheetIndex the 0-based index of the sheet this formula belongs to.
+ * The sheet index is required to resolve sheet-level names. -1
means that
+ * the scope of the name will be ignored and the parser will match names only by name
+ * @param rowIndex - the related cell's row index in 0-based form (-1 if the formula is not cell related)
+ * used to handle structured references that have the "#This Row" quantifier.
+ * Use rowIndex=-1 or {@link #parseStructuredReference(String, FormulaParsingWorkbook, int, int) if formula
+ * does not contain structured references.
+ *
+ * @return array of parsed tokens
+ * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
+ */
+ public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex, int rowIndex) {
+ FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
+ fp.parse();
+ return fp.getRPNPtg(formulaType);
+ }
- /** Report What Was Expected */
- private RuntimeException expected(String s) {
- String msg;
+ /**
+ * Parse a formula into an array of tokens
+ * Side effect: creates name ({@link org.apache.poi.ss.usermodel.Workbook#createName})
+ * if formula contains unrecognized names (names are likely UDFs)
+ *
+ * @param formula the formula to parse
+ * @param workbook the parent workbook
+ * @param formulaType the type of the formula, see {@link FormulaType}
+ * @param sheetIndex the 0-based index of the sheet this formula belongs to.
+ * The sheet index is required to resolve sheet-level names. -1
means that
+ * the scope of the name will be ignored and the parser will match names only by name
+ *
+ * @return array of parsed tokens
+ * @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
+ */
+ public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
+ return parse(formula, workbook, formulaType, sheetIndex, -1);
+ }
- if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) {
- msg = "The specified formula '" + _formulaString
- + "' starts with an equals sign which is not allowed.";
- } else {
- msg = "Parse error near char " + (_pointer-1) + " '" + look + "'"
- + " in specified formula '" + _formulaString + "'. Expected "
- + s;
- }
- return new FormulaParseException(msg);
- }
+ /**
+ * Parse a structured reference. Converts the structured
+ * reference to the area that represent it.
+ *
+ * @param tableText - The structured reference text
+ * @param workbook - the parent workbook
+ * @param rowIndex - the 0-based cell's row index ( used to handle "#This Row" quantifiers )
+ * @return the area that being represented by the structured reference.
+ */
+ public static Area3DPxg parseStructuredReference(String tableText, FormulaParsingWorkbook workbook, int rowIndex) {
+ final int sheetIndex = -1; //don't care?
+ Ptg[] arr = FormulaParser.parse(tableText, workbook, FormulaType.CELL, sheetIndex, rowIndex);
+ if (arr.length != 1 || !(arr[0] instanceof Area3DPxg) ) {
+ throw new IllegalStateException("Illegal structured reference");
+ }
+ return (Area3DPxg) arr[0];
+ }
+
+ /** Read New Character From Input Stream */
+ private void GetChar() {
+ // The intersection operator is a space. We track whether the run of
+ // whitespace preceeding "look" counts as an intersection operator.
+ if (IsWhite(look)) {
+ if (look == ' ') {
+ _inIntersection = true;
+ }
+ }
+ else {
+ _inIntersection = false;
+ }
+
+ // Check to see if we've walked off the end of the string.
+ if (_pointer > _formulaLength) {
+ throw new RuntimeException("too far");
+ }
+ if (_pointer < _formulaLength) {
+ look=_formulaString.charAt(_pointer);
+ } else {
+ // Just return if so and reset 'look' to something to keep
+ // SkipWhitespace from spinning
+ look = (char)0;
+ _inIntersection = false;
+ }
+ _pointer++;
+ //System.out.println("Got char: "+ look);
+ }
+ private void resetPointer(int ptr) {
+ _pointer = ptr;
+ if (_pointer <= _formulaLength) {
+ look=_formulaString.charAt(_pointer-1);
+ } else {
+ // Just return if so and reset 'look' to something to keep
+ // SkipWhitespace from spinning
+ look = (char)0;
+ }
+ }
- /** Recognize an Alpha Character */
- private static boolean IsAlpha(char c) {
- return Character.isLetter(c) || c == '$' || c=='_';
- }
+ /** Report What Was Expected */
+ private RuntimeException expected(String s) {
+ String msg;
- /** Recognize a Decimal Digit */
- private static boolean IsDigit(char c) {
- return Character.isDigit(c);
- }
+ if (look == '=' && _formulaString.substring(0, _pointer-1).trim().length() < 1) {
+ msg = "The specified formula '" + _formulaString
+ + "' starts with an equals sign which is not allowed.";
+ } else {
+ msg = "Parse error near char " + (_pointer-1) + " '" + look + "'"
+ + " in specified formula '" + _formulaString + "'. Expected "
+ + s;
+ }
+ return new FormulaParseException(msg);
+ }
- /** Recognize White Space */
- private static boolean IsWhite( char c) {
- return c ==' ' || c== TAB || c == CR || c == LF;
- }
+ /** Recognize an Alpha Character */
+ private static boolean IsAlpha(char c) {
+ return Character.isLetter(c) || c == '$' || c=='_';
+ }
- /** Skip Over Leading White Space */
- private void SkipWhite() {
- while (IsWhite(look)) {
- GetChar();
- }
- }
+ /** Recognize a Decimal Digit */
+ private static boolean IsDigit(char c) {
+ return Character.isDigit(c);
+ }
- /**
- * Consumes the next input character if it is equal to the one specified otherwise throws an
- * unchecked exception. This method does not consume whitespace (before or after the
- * matched character).
- */
- private void Match(char x) {
- if (look != x) {
- throw expected("'" + x + "'");
- }
- GetChar();
- }
+ /** Recognize White Space */
+ private static boolean IsWhite( char c) {
+ return c ==' ' || c== TAB || c == CR || c == LF;
+ }
- /** Get a Number */
- private String GetNum() {
- StringBuffer value = new StringBuffer();
+ /** Skip Over Leading White Space */
+ private void SkipWhite() {
+ while (IsWhite(look)) {
+ GetChar();
+ }
+ }
- while (IsDigit(this.look)){
- value.append(this.look);
- GetChar();
- }
- return value.length() == 0 ? null : value.toString();
- }
+ /**
+ * Consumes the next input character if it is equal to the one specified otherwise throws an
+ * unchecked exception. This method does not consume whitespace (before or after the
+ * matched character).
+ */
+ private void Match(char x) {
+ if (look != x) {
+ throw expected("'" + x + "'");
+ }
+ GetChar();
+ }
- private ParseNode parseRangeExpression() {
- ParseNode result = parseRangeable();
- boolean hasRange = false;
- while (look == ':') {
- int pos = _pointer;
- GetChar();
- ParseNode nextPart = parseRangeable();
- // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
- // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
- // Furthermore, Excel doesn't seem to simplify
- // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"
+ /** Get a Number */
+ private String GetNum() {
+ StringBuffer value = new StringBuffer();
- checkValidRangeOperand("LHS", pos, result);
- checkValidRangeOperand("RHS", pos, nextPart);
+ while (IsDigit(this.look)){
+ value.append(this.look);
+ GetChar();
+ }
+ return value.length() == 0 ? null : value.toString();
+ }
- ParseNode[] children = { result, nextPart, };
- result = new ParseNode(RangePtg.instance, children);
- hasRange = true;
- }
- if (hasRange) {
- return augmentWithMemPtg(result);
- }
- return result;
- }
+ private ParseNode parseRangeExpression() {
+ ParseNode result = parseRangeable();
+ boolean hasRange = false;
+ while (look == ':') {
+ int pos = _pointer;
+ GetChar();
+ ParseNode nextPart = parseRangeable();
+ // Note - no range simplification here. An expr like "A1:B2:C3:D4:E5" should be
+ // grouped into area ref pairs like: "(A1:B2):(C3:D4):E5"
+ // Furthermore, Excel doesn't seem to simplify
+ // expressions like "Sheet1!A1:Sheet1:B2" into "Sheet1!A1:B2"
- private static ParseNode augmentWithMemPtg(ParseNode root) {
- Ptg memPtg;
- if (needsMemFunc(root)) {
- memPtg = new MemFuncPtg(root.getEncodedSize());
- } else {
- memPtg = new MemAreaPtg(root.getEncodedSize());
- }
- return new ParseNode(memPtg, root);
- }
- /**
- * From OOO doc: "Whenever one operand of the reference subexpression is a function,
- * a defined name, a 3D reference, or an external reference (and no error occurs),
- * a tMemFunc token is used"
- *
- */
- private static boolean needsMemFunc(ParseNode root) {
- Ptg token = root.getToken();
- if (token instanceof AbstractFunctionPtg) {
- return true;
- }
- if (token instanceof ExternSheetReferenceToken) { // 3D refs
- return true;
- }
- if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
- return true;
- }
+ checkValidRangeOperand("LHS", pos, result);
+ checkValidRangeOperand("RHS", pos, nextPart);
- if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
- // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
- for(ParseNode child : root.getChildren()) {
- if (needsMemFunc(child)) {
- return true;
- }
- }
- return false;
- }
- if (token instanceof OperandPtg) {
- return false;
- }
- if (token instanceof OperationPtg) {
- return true;
- }
+ ParseNode[] children = { result, nextPart, };
+ result = new ParseNode(RangePtg.instance, children);
+ hasRange = true;
+ }
+ if (hasRange) {
+ return augmentWithMemPtg(result);
+ }
+ return result;
+ }
- return false;
- }
+ private static ParseNode augmentWithMemPtg(ParseNode root) {
+ Ptg memPtg;
+ if (needsMemFunc(root)) {
+ memPtg = new MemFuncPtg(root.getEncodedSize());
+ } else {
+ memPtg = new MemAreaPtg(root.getEncodedSize());
+ }
+ return new ParseNode(memPtg, root);
+ }
+ /**
+ * From OOO doc: "Whenever one operand of the reference subexpression is a function,
+ * a defined name, a 3D reference, or an external reference (and no error occurs),
+ * a tMemFunc token is used"
+ *
+ */
+ private static boolean needsMemFunc(ParseNode root) {
+ Ptg token = root.getToken();
+ if (token instanceof AbstractFunctionPtg) {
+ return true;
+ }
+ if (token instanceof ExternSheetReferenceToken) { // 3D refs
+ return true;
+ }
+ if (token instanceof NamePtg || token instanceof NameXPtg) { // 3D refs
+ return true;
+ }
- /**
- * @param currentParsePosition used to format a potential error message
- */
- private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) {
- if (!isValidRangeOperand(pn)) {
- throw new FormulaParseException("The " + sideName
- + " of the range operator ':' at position "
- + currentParsePosition + " is not a proper reference.");
- }
- }
+ if (token instanceof OperationPtg || token instanceof ParenthesisPtg) {
+ // expect RangePtg, but perhaps also UnionPtg, IntersectionPtg etc
+ for(ParseNode child : root.getChildren()) {
+ if (needsMemFunc(child)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ if (token instanceof OperandPtg) {
+ return false;
+ }
+ if (token instanceof OperationPtg) {
+ return true;
+ }
- /**
- * @return false
if sub-expression represented the specified ParseNode definitely
- * cannot appear on either side of the range (':') operator
- */
- private static boolean isValidRangeOperand(ParseNode a) {
- Ptg tkn = a.getToken();
- // Note - order is important for these instance-of checks
- if (tkn instanceof OperandPtg) {
- // notably cell refs and area refs
- return true;
- }
+ return false;
+ }
- // next 2 are special cases of OperationPtg
- if (tkn instanceof AbstractFunctionPtg) {
- AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
- byte returnClass = afp.getDefaultOperandClass();
- return Ptg.CLASS_REF == returnClass;
- }
- if (tkn instanceof ValueOperatorPtg) {
- return false;
- }
- if (tkn instanceof OperationPtg) {
- return true;
- }
+ /**
+ * @param currentParsePosition used to format a potential error message
+ */
+ private static void checkValidRangeOperand(String sideName, int currentParsePosition, ParseNode pn) {
+ if (!isValidRangeOperand(pn)) {
+ throw new FormulaParseException("The " + sideName
+ + " of the range operator ':' at position "
+ + currentParsePosition + " is not a proper reference.");
+ }
+ }
- // one special case of ControlPtg
- if (tkn instanceof ParenthesisPtg) {
- // parenthesis Ptg should have only one child
- return isValidRangeOperand(a.getChildren()[0]);
- }
+ /**
+ * @return false
if sub-expression represented the specified ParseNode definitely
+ * cannot appear on either side of the range (':') operator
+ */
+ private static boolean isValidRangeOperand(ParseNode a) {
+ Ptg tkn = a.getToken();
+ // Note - order is important for these instance-of checks
+ if (tkn instanceof OperandPtg) {
+ // notably cell refs and area refs
+ return true;
+ }
- // one special case of ScalarConstantPtg
- if (tkn == ErrPtg.REF_INVALID) {
- return true;
- }
+ // next 2 are special cases of OperationPtg
+ if (tkn instanceof AbstractFunctionPtg) {
+ AbstractFunctionPtg afp = (AbstractFunctionPtg) tkn;
+ byte returnClass = afp.getDefaultOperandClass();
+ return Ptg.CLASS_REF == returnClass;
+ }
+ if (tkn instanceof ValueOperatorPtg) {
+ return false;
+ }
+ if (tkn instanceof OperationPtg) {
+ return true;
+ }
- // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
- return false;
- }
+ // one special case of ControlPtg
+ if (tkn instanceof ParenthesisPtg) {
+ // parenthesis Ptg should have only one child
+ return isValidRangeOperand(a.getChildren()[0]);
+ }
- /**
- * Parses area refs (things which could be the operand of ':') and simple factors
- * Examples
- *
- * A$1
- * $A$1 : $B1
- * A1 ....... C2
- * Sheet1 !$A1
- * a..b!A1
- * 'my sheet'!A1
- * .my.sheet!A1
- * 'my sheet':'my alt sheet'!A1
- * .my.sheet1:.my.sheet2!$B$2
- * my.named..range.
- * 'my sheet'!my.named.range
- * .my.sheet!my.named.range
- * foo.bar(123.456, "abc")
- * 123.456
- * "abc"
- * true
+ // one special case of ScalarConstantPtg
+ if (tkn == ErrPtg.REF_INVALID) {
+ return true;
+ }
+
+ // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
+ return false;
+ }
+
+ /**
+ * Parses area refs (things which could be the operand of ':') and simple factors
+ * Examples
+ *
+ * A$1
+ * $A$1 : $B1
+ * A1 ....... C2
+ * Sheet1 !$A1
+ * a..b!A1
+ * 'my sheet'!A1
+ * .my.sheet!A1
+ * 'my sheet':'my alt sheet'!A1
+ * .my.sheet1:.my.sheet2!$B$2
+ * my.named..range.
+ * 'my sheet'!my.named.range
+ * .my.sheet!my.named.range
+ * foo.bar(123.456, "abc")
+ * 123.456
+ * "abc"
+ * true
* [Foo.xls]!$A$1
- * [Foo.xls]'my sheet'!$A$1
- * [Foo.xls]!my.named.range
- *
- *
- */
- private ParseNode parseRangeable() {
- SkipWhite();
- int savePointer = _pointer;
- SheetIdentifier sheetIden = parseSheetName();
-
- if (sheetIden == null) {
- resetPointer(savePointer);
- } else {
- SkipWhite();
- savePointer = _pointer;
- }
+ * [Foo.xls]'my sheet'!$A$1
+ * [Foo.xls]!my.named.range
+ *
+ *
+ */
+ private ParseNode parseRangeable() {
+ SkipWhite();
+ int savePointer = _pointer;
+ SheetIdentifier sheetIden = parseSheetName();
+
+ if (sheetIden == null) {
+ resetPointer(savePointer);
+ } else {
+ SkipWhite();
+ savePointer = _pointer;
+ }
- SimpleRangePart part1 = parseSimpleRangePart();
- if (part1 == null) {
- if (sheetIden != null) {
+ SimpleRangePart part1 = parseSimpleRangePart();
+ if (part1 == null) {
+ if (sheetIden != null) {
if(look == '#'){ // error ref like MySheet!#REF!
return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
} else {
@@ -438,152 +484,434 @@ public final class FormulaParser {
}
return new ParseNode(nameXPtg);
}
- }
- return parseNonRange(savePointer);
- }
- boolean whiteAfterPart1 = IsWhite(look);
- if (whiteAfterPart1) {
- SkipWhite();
- }
+ }
+ return parseNonRange(savePointer);
+ }
+ boolean whiteAfterPart1 = IsWhite(look);
+ if (whiteAfterPart1) {
+ SkipWhite();
+ }
- if (look == ':') {
- int colonPos = _pointer;
- GetChar();
- SkipWhite();
- SimpleRangePart part2 = parseSimpleRangePart();
- if (part2 != null && !part1.isCompatibleForArea(part2)) {
- // second part is not compatible with an area ref e.g. S!A1:S!B2
- // where S might be a sheet name (that looks like a column name)
+ if (look == ':') {
+ int colonPos = _pointer;
+ GetChar();
+ SkipWhite();
+ SimpleRangePart part2 = parseSimpleRangePart();
+ if (part2 != null && !part1.isCompatibleForArea(part2)) {
+ // second part is not compatible with an area ref e.g. S!A1:S!B2
+ // where S might be a sheet name (that looks like a column name)
- part2 = null;
- }
- if (part2 == null) {
- // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
- // reset and let caller use explicit range operator
- resetPointer(colonPos);
- if (!part1.isCell()) {
- String prefix = "";
- if (sheetIden != null) {
- prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
- }
- throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
- }
- }
- return createAreaRefParseNode(sheetIden, part1, part2);
- }
+ part2 = null;
+ }
+ if (part2 == null) {
+ // second part is not compatible with an area ref e.g. A1:OFFSET(B2, 1, 2)
+ // reset and let caller use explicit range operator
+ resetPointer(colonPos);
+ if (!part1.isCell()) {
+ String prefix = "";
+ if (sheetIden != null) {
+ prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!';
+ }
+ throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference.");
+ }
+ }
+ return createAreaRefParseNode(sheetIden, part1, part2);
+ }
- if (look == '.') {
- GetChar();
- int dotCount = 1;
- while (look =='.') {
- dotCount ++;
- GetChar();
- }
- boolean whiteBeforePart2 = IsWhite(look);
+ if (look == '.') {
+ GetChar();
+ int dotCount = 1;
+ while (look =='.') {
+ dotCount ++;
+ GetChar();
+ }
+ boolean whiteBeforePart2 = IsWhite(look);
- SkipWhite();
- SimpleRangePart part2 = parseSimpleRangePart();
- String part1And2 = _formulaString.substring(savePointer-1, _pointer-1);
- if (part2 == null) {
- if (sheetIden != null) {
- throw new FormulaParseException("Complete area reference expected after sheet name at index "
- + _pointer + ".");
- }
- return parseNonRange(savePointer);
- }
+ SkipWhite();
+ SimpleRangePart part2 = parseSimpleRangePart();
+ String part1And2 = _formulaString.substring(savePointer-1, _pointer-1);
+ if (part2 == null) {
+ if (sheetIden != null) {
+ throw new FormulaParseException("Complete area reference expected after sheet name at index "
+ + _pointer + ".");
+ }
+ return parseNonRange(savePointer);
+ }
- if (whiteAfterPart1 || whiteBeforePart2) {
- if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
- // "A .. B" not valid syntax for "A:B"
- // and there's no other valid expression that fits this grammar
- throw new FormulaParseException("Dotted range (full row or column) expression '"
- + part1And2 + "' must not contain whitespace.");
- }
- return createAreaRefParseNode(sheetIden, part1, part2);
- }
+ if (whiteAfterPart1 || whiteBeforePart2) {
+ if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
+ // "A .. B" not valid syntax for "A:B"
+ // and there's no other valid expression that fits this grammar
+ throw new FormulaParseException("Dotted range (full row or column) expression '"
+ + part1And2 + "' must not contain whitespace.");
+ }
+ return createAreaRefParseNode(sheetIden, part1, part2);
+ }
- if (dotCount == 1 && part1.isRow() && part2.isRow()) {
- // actually, this is looking more like a number
- return parseNonRange(savePointer);
- }
+ if (dotCount == 1 && part1.isRow() && part2.isRow()) {
+ // actually, this is looking more like a number
+ return parseNonRange(savePointer);
+ }
- if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
- if (dotCount != 2) {
- throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2
- + "' must have exactly 2 dots.");
- }
- }
- return createAreaRefParseNode(sheetIden, part1, part2);
- }
- if (part1.isCell() && isValidCellReference(part1.getRep())) {
- return createAreaRefParseNode(sheetIden, part1, null);
- }
- if (sheetIden != null) {
- throw new FormulaParseException("Second part of cell reference expected after sheet name at index "
- + _pointer + ".");
- }
+ if (part1.isRowOrColumn() || part2.isRowOrColumn()) {
+ if (dotCount != 2) {
+ throw new FormulaParseException("Dotted range (full row or column) expression '" + part1And2
+ + "' must have exactly 2 dots.");
+ }
+ }
+ return createAreaRefParseNode(sheetIden, part1, part2);
+ }
+ if (part1.isCell() && isValidCellReference(part1.getRep())) {
+ return createAreaRefParseNode(sheetIden, part1, null);
+ }
+ if (sheetIden != null) {
+ throw new FormulaParseException("Second part of cell reference expected after sheet name at index "
+ + _pointer + ".");
+ }
- return parseNonRange(savePointer);
- }
+ return parseNonRange(savePointer);
+ }
+
+ private final static String specHeaders = "Headers";
+ private final static String specAll = "All";
+ private final static String specData = "Data";
+ private final static String specTotals = "Totals";
+ private final static String specThisRow = "This Row";
+
+ /**
+ * Parses a structured reference, returns it as area reference.
+ * Examples:
+ *
+ * Table1[col]
+ * Table1[[#Totals],[col]]
+ * Table1[#Totals]
+ * Table1[#All]
+ * Table1[#Data]
+ * Table1[#Headers]
+ * Table1[#Totals]
+ * Table1[#This Row]
+ * Table1[[#All],[col]]
+ * Table1[[#Headers],[col]]
+ * Table1[[#Totals],[col]]
+ * Table1[[#All],[col1]:[col2]]
+ * Table1[[#Data],[col1]:[col2]]
+ * Table1[[#Headers],[col1]:[col2]]
+ * Table1[[#Totals],[col1]:[col2]]
+ * Table1[[#Headers],[#Data],[col2]]
+ * Table1[[#This Row], [col1]]
+ * Table1[ [col1]:[col2] ]
+ *
+ * @param tableName
+ * @return
+ */
+ private ParseNode parseStructuredReference(String tableName) {
+
+ if ( ! (_ssVersion.equals(SpreadsheetVersion.EXCEL2007)) ) {
+ throw new FormulaParseException("Structured references work only on XSSF (Excel 2007+)!");
+ }
+ Table tbl = _book.getTable(tableName);
+ if (tbl == null) {
+ throw new FormulaParseException("Illegal table name: '" + tableName + "'");
+ }
+ String sheetName = tbl.getSheetName();
+
+ int startCol = tbl.getStartColIndex();
+ int endCol = tbl.getEndColIndex();
+ int startRow = tbl.getStartRowIndex();
+ int endRow = tbl.getEndRowIndex();
+
+ // Do NOT return before done reading all the structured reference tokens from the input stream.
+ // Throwing exceptions is okay.
+ int savePtr0 = _pointer;
+ GetChar();
+
+ boolean isTotalsSpec = false;
+ boolean isThisRowSpec = false;
+ boolean isDataSpec = false;
+ boolean isHeadersSpec = false;
+ boolean isAllSpec = false;
+ int nSpecQuantifiers = 0; // The number of special quantifiers
+ while (true) {
+ int savePtr1 = _pointer;
+ String specName = parseAsSpecialQuantifier();
+ if (specName == null) {
+ resetPointer(savePtr1);
+ break;
+ }
+ if (specName.equals(specAll)) {
+ isAllSpec = true;
+ } else if (specName.equals(specData)) {
+ isDataSpec = true;
+ } else if (specName.equals(specHeaders)) {
+ isHeadersSpec = true;
+ } else if (specName.equals(specThisRow)) {
+ isThisRowSpec = true;
+ } else if (specName.equals(specTotals)) {
+ isTotalsSpec = true;
+ } else {
+ throw new FormulaParseException("Unknown special quantifier "+ specName);
+ }
+ nSpecQuantifiers++;
+ if (look == ','){
+ GetChar();
+ } else {
+ break;
+ }
+ }
+ boolean isThisRow = false;
+ SkipWhite();
+ if (look == '@') {
+ isThisRow = true;
+ GetChar();
+ }
+ // parse column quantifier
+ String startColumnName = null;
+ String endColumnName = null;
+ int nColQuantifiers = 0;
+ int savePtr1 = _pointer;
+ startColumnName = parseAsColumnQuantifier();
+ if (startColumnName == null) {
+ resetPointer(savePtr1);
+ } else {
+ nColQuantifiers++;
+ if (look == ','){
+ throw new FormulaParseException("The formula "+ _formulaString + "is illegal: you should not use ',' with column quantifiers");
+ } else if (look == ':') {
+ GetChar();
+ endColumnName = parseAsColumnQuantifier();
+ nColQuantifiers++;
+ if (endColumnName == null) {
+ throw new FormulaParseException("The formula "+ _formulaString + "is illegal: the string after ':' must be column quantifier");
+ }
+ }
+ }
+
+ if(nColQuantifiers == 0 && nSpecQuantifiers == 0){
+ resetPointer(savePtr0);
+ savePtr0 = _pointer;
+ startColumnName = parseAsColumnQuantifier();
+ if (startColumnName != null) {
+ nColQuantifiers++;
+ } else {
+ resetPointer(savePtr0);
+ String name = parseAsSpecialQuantifier();
+ if (name!=null) {
+ if (name.equals(specAll)) {
+ isAllSpec = true;
+ } else if (name.equals(specData)) {
+ isDataSpec = true;
+ } else if (name.equals(specHeaders)) {
+ isHeadersSpec = true;
+ } else if (name.equals(specThisRow)) {
+ isThisRowSpec = true;
+ } else if (name.equals(specTotals)) {
+ isTotalsSpec = true;
+ } else {
+ throw new FormulaParseException("Unknown special quantifier "+ name);
+ }
+ nSpecQuantifiers++;
+ } else {
+ throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
+ }
+ }
+ } else {
+ Match(']');
+ }
+ // Done reading from input stream
+ // Ok to return now
- /**
- * Parses simple factors that are not primitive ranges or range components
- * i.e. '!', ':'(and equiv '...') do not appear
- * Examples
- *
- * my.named...range.
- * foo.bar(123.456, "abc")
- * 123.456
- * "abc"
- * true
- *
- */
- private ParseNode parseNonRange(int savePointer) {
- resetPointer(savePointer);
+ if (isTotalsSpec && !tbl.isHasTotalsRow()) {
+ return new ParseNode(ErrPtg.REF_INVALID);
+ }
+ if ((isThisRow || isThisRowSpec) && (_rowIndex < startRow || endRow < _rowIndex)) {
+ // structured reference is trying to reference a row above or below the table with [#This Row] or [@]
+ if (_rowIndex >= 0) {
+ return new ParseNode(ErrPtg.VALUE_INVALID);
+ } else {
+ throw new FormulaParseException(
+ "Formula contained [#This Row] or [@] structured reference but this row < 0. " +
+ "Row index must be specified for row-referencing structured references.");
+ }
+ }
+
+ int actualStartRow = startRow;
+ int actualEndRow = endRow;
+ int actualStartCol = startCol;
+ int actualEndCol = endCol;
+ if (nSpecQuantifiers > 0) {
+ //Selecting rows
+ if (nSpecQuantifiers == 1 && isAllSpec) {
+ //do nothing
+ } else if (isDataSpec && isHeadersSpec) {
+ if (tbl.isHasTotalsRow()) {
+ actualEndRow = endRow - 1;
+ }
+ } else if (isDataSpec && isTotalsSpec) {
+ actualStartRow = startRow + 1;
+ } else if (nSpecQuantifiers == 1 && isDataSpec) {
+ actualStartRow = startRow + 1;
+ if (tbl.isHasTotalsRow()) {
+ actualEndRow = endRow - 1;
+ }
+ } else if (nSpecQuantifiers == 1 && isHeadersSpec) {
+ actualEndRow = actualStartRow;
+ } else if (nSpecQuantifiers == 1 && isTotalsSpec) {
+ actualStartRow = actualEndRow;
+ } else if ((nSpecQuantifiers == 1 && isThisRowSpec) || isThisRow) {
+ actualStartRow = _rowIndex; //The rowNum is 0 based
+ actualEndRow = _rowIndex;
+ } else {
+ throw new FormulaParseException("The formula "+ _formulaString + " is illegal");
+ }
+ } else {
+ if (isThisRow) { // there is a @
+ actualStartRow = _rowIndex; //The rowNum is 0 based
+ actualEndRow = _rowIndex;
+ } else { // Really no special quantifiers
+ actualStartRow++;
+ }
+ }
- if (Character.isDigit(look)) {
- return new ParseNode(parseNumber());
- }
- if (look == '"') {
- return new ParseNode(new StringPtg(parseStringLiteral()));
- }
-
- // from now on we can only be dealing with non-quoted identifiers
- // which will either be named ranges or functions
- String name = parseAsName();
+ //Selecting cols
- if (look == '(') {
- return function(name);
- }
- if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
- return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
- }
- if (_book == null) {
- // Only test cases omit the book (expecting it not to be needed)
- throw new IllegalStateException("Need book to evaluate name '" + name + "'");
- }
- EvaluationName evalName = _book.getName(name, _sheetIndex);
- if (evalName == null) {
- throw new FormulaParseException("Specified named range '"
- + name + "' does not exist in the current workbook.");
- }
- if (evalName.isRange()) {
- return new ParseNode(evalName.createPtg());
- }
- // TODO - what about NameX ?
- throw new FormulaParseException("Specified name '"
- + name + "' is not a range as expected.");
- }
-
- private String parseAsName() {
+ if (nColQuantifiers == 2) {
+ if (startColumnName == null || endColumnName == null) {
+ throw new IllegalStateException("Fatal error");
+ }
+ int startIdx = tbl.findColumnIndex(startColumnName);
+ int endIdx = tbl.findColumnIndex(endColumnName);
+ if (startIdx == -1 || endIdx == -1) {
+ throw new FormulaParseException("One of the columns "+ startColumnName +", "+ endColumnName +" doesn't exist in table "+ tbl.getName());
+ }
+ actualStartCol = startCol+ startIdx;
+ actualEndCol = startCol + endIdx;
+
+ } else if (nColQuantifiers == 1 && !isThisRow) {
+ if (startColumnName == null) {
+ throw new IllegalStateException("Fatal error");
+ }
+ int idx = tbl.findColumnIndex(startColumnName);
+ if (idx == -1) {
+ throw new FormulaParseException("The column "+ startColumnName + " doesn't exist in table "+ tbl.getName());
+ }
+ actualStartCol = startCol + idx;
+ actualEndCol = actualStartCol;
+ }
+ CellReference topLeft = new CellReference(actualStartRow, actualStartCol);
+ CellReference bottomRight = new CellReference(actualEndRow, actualEndCol);
+ SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true));
+ Ptg ptg = _book.get3DReferencePtg(new AreaReference(topLeft, bottomRight), sheetIden);
+ return new ParseNode(ptg);
+ }
+
+ /**
+ * Tries to parse the next as column - can contain whitespace
+ * Caller should save pointer.
+ * @return
+ */
+ private String parseAsColumnQuantifier() {
+ if ( look != '[') {
+ return null;
+ }
+ GetChar();
+ String name = "";
+ if (look == '#') {
+ return null;
+ }
+ if (look == '@') {
+ GetChar();
+ }
+ while (look!=']') {
+ name += look;
+ GetChar();
+ }
+ Match(']');
+ return name;
+ }
+ /**
+ * Tries to parse the next as special quantifier
+ * Caller should save pointer.
+ * @return
+ */
+ private String parseAsSpecialQuantifier(){
+ if ( look != '[') {
+ return null;
+ }
+ GetChar();
+ if( look != '#') {
+ return null;
+ }
+ GetChar();
+ String name = parseAsName();
+ if ( name.equals("This")) {
+ name = name + ' ' + parseAsName();
+ }
+ Match(']');
+ return name;
+ }
+
+
+ /**
+ * Parses simple factors that are not primitive ranges or range components
+ * i.e. '!', ':'(and equiv '...') do not appear
+ * Examples
+ *
+ * my.named...range.
+ * foo.bar(123.456, "abc")
+ * 123.456
+ * "abc"
+ * true
+ *
+ */
+ private ParseNode parseNonRange(int savePointer) {
+ resetPointer(savePointer);
+
+ if (Character.isDigit(look)) {
+ return new ParseNode(parseNumber());
+ }
+ if (look == '"') {
+ return new ParseNode(new StringPtg(parseStringLiteral()));
+ }
+
+ // from now on we can only be dealing with non-quoted identifiers
+ // which will either be named ranges or functions
+ String name = parseAsName();
+
+ if (look == '(') {
+ return function(name);
+ }
+ if(look == '['){
+ return parseStructuredReference(name);
+ }
+ if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+ return new ParseNode(BoolPtg.valueOf(name.equalsIgnoreCase("TRUE")));
+ }
+ if (_book == null) {
+ // Only test cases omit the book (expecting it not to be needed)
+ throw new IllegalStateException("Need book to evaluate name '" + name + "'");
+ }
+ EvaluationName evalName = _book.getName(name, _sheetIndex);
+ if (evalName == null) {
+ throw new FormulaParseException("Specified named range '"
+ + name + "' does not exist in the current workbook.");
+ }
+ if (evalName.isRange()) {
+ return new ParseNode(evalName.createPtg());
+ }
+ // TODO - what about NameX ?
+ throw new FormulaParseException("Specified name '"
+ + name + "' is not a range as expected.");
+ }
+
+ private String parseAsName() {
StringBuilder sb = new StringBuilder();
- // defined names may begin with a letter or underscore
- if (!Character.isLetter(look) && look != '_') {
- throw expected("number, string, or defined name");
+ // defined names may begin with a letter or underscore or backslash
+ if (!Character.isLetter(look) && look != '_' && look != '\\') {
+ throw expected("number, string, defined name, or data table");
}
while (isValidDefinedNameChar(look)) {
sb.append(look);
@@ -592,1060 +920,1062 @@ public final class FormulaParser {
SkipWhite();
return sb.toString();
- }
+ }
- /**
- *
- * @return true
if the specified character may be used in a defined name
- */
- private static boolean isValidDefinedNameChar(char ch) {
- if (Character.isLetterOrDigit(ch)) {
- return true;
- }
- switch (ch) {
- case '.':
- case '_':
- case '?':
- case '\\': // of all things
- return true;
- }
- return false;
- }
-
- /**
- *
- * @param sheetIden may be null
- * @param part1
- * @param part2 may be null
- */
- private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1,
- SimpleRangePart part2) throws FormulaParseException {
- Ptg ptg;
- if (part2 == null) {
- CellReference cr = part1.getCellReference();
- if (sheetIden == null) {
- ptg = new RefPtg(cr);
- } else {
- ptg = _book.get3DReferencePtg(cr, sheetIden);
- }
- } else {
- AreaReference areaRef = createAreaRef(part1, part2);
-
- if (sheetIden == null) {
- ptg = new AreaPtg(areaRef);
- } else {
- ptg = _book.get3DReferencePtg(areaRef, sheetIden);
- }
- }
- return new ParseNode(ptg);
- }
-
- private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) {
- if (!part1.isCompatibleForArea(part2)) {
- throw new FormulaParseException("has incompatible parts: '"
- + part1.getRep() + "' and '" + part2.getRep() + "'.");
- }
- if (part1.isRow()) {
- return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep());
- }
- if (part1.isColumn()) {
- return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep());
- }
- return new AreaReference(part1.getCellReference(), part2.getCellReference());
- }
-
- /**
- * Matches a zero or one letter-runs followed by zero or one digit-runs.
- * Either or both runs man optionally be prefixed with a single '$'.
- * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN})
- */
- private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?");
-
- /**
- * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef. Normally these are
- * proper cell references but they could also be row or column refs like "$AC" or "10"
- * @return null
(and leaves {@link #_pointer} unchanged if a proper range part does not parse out
- */
- private SimpleRangePart parseSimpleRangePart() {
- int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds
- boolean hasDigits = false;
- boolean hasLetters = false;
- while (ptr < _formulaLength) {
- char ch = _formulaString.charAt(ptr);
- if (Character.isDigit(ch)) {
- hasDigits = true;
- } else if (Character.isLetter(ch)) {
- hasLetters = true;
- } else if (ch =='$' || ch =='_') {
- //
- } else {
- break;
- }
- ptr++;
- }
- if (ptr <= _pointer-1) {
- return null;
- }
- String rep = _formulaString.substring(_pointer-1, ptr);
- if (!CELL_REF_PATTERN.matcher(rep).matches()) {
- return null;
- }
- // Check range bounds against grid max
- if (hasLetters && hasDigits) {
- if (!isValidCellReference(rep)) {
- return null;
- }
- } else if (hasLetters) {
- if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) {
- return null;
- }
- } else if (hasDigits) {
- int i;
- try {
- i = Integer.parseInt(rep.replace("$", ""));
- } catch (NumberFormatException e) {
- return null;
- }
- if (i<1 || i>_ssVersion.getMaxRows()) {
- return null;
- }
- } else {
- // just dollars ? can this happen?
- return null;
- }
-
-
- resetPointer(ptr+1); // stepping forward
- return new SimpleRangePart(rep, hasLetters, hasDigits);
- }
-
-
- /**
- * A1, $A1, A$1, $A$1, A, 1
- */
- private static final class SimpleRangePart {
- private enum Type {
- CELL, ROW, COLUMN;
-
- public static Type get(boolean hasLetters, boolean hasDigits) {
- if (hasLetters) {
- return hasDigits ? CELL : COLUMN;
- }
- if (!hasDigits) {
- throw new IllegalArgumentException("must have either letters or numbers");
- }
- return ROW;
- }
- }
-
- private final Type _type;
- private final String _rep;
-
- public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) {
- _rep = rep;
- _type = Type.get(hasLetters, hasNumbers);
- }
-
- public boolean isCell() {
- return _type == Type.CELL;
- }
-
- public boolean isRowOrColumn() {
- return _type != Type.CELL;
- }
-
- public CellReference getCellReference() {
- if (_type != Type.CELL) {
- throw new IllegalStateException("Not applicable to this type");
- }
- return new CellReference(_rep);
- }
-
- public boolean isColumn() {
- return _type == Type.COLUMN;
- }
-
- public boolean isRow() {
- return _type == Type.ROW;
- }
-
- public String getRep() {
- return _rep;
- }
-
- /**
- * @return true
if the two range parts can be combined in an
- * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid
- * when this method returns false
)
- */
- public boolean isCompatibleForArea(SimpleRangePart part2) {
- return _type == part2._type;
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder(64);
- sb.append(getClass().getName()).append(" [");
- sb.append(_rep);
- sb.append("]");
- return sb.toString();
- }
- }
-
- /**
- * Note - caller should reset {@link #_pointer} upon null
result
- * @return The sheet name as an identifier null
if '!' is not found in the right place
- */
- private SheetIdentifier parseSheetName() {
- String bookName;
- if (look == '[') {
- StringBuilder sb = new StringBuilder();
- GetChar();
- while (look != ']') {
- sb.append(look);
- GetChar();
- }
- GetChar();
- bookName = sb.toString();
- } else {
- bookName = null;
- }
-
- if (look == '\'') {
- StringBuffer sb = new StringBuffer();
-
- Match('\'');
- boolean done = look == '\'';
- while(!done) {
- sb.append(look);
- GetChar();
- if(look == '\'')
- {
- Match('\'');
- done = look != '\'';
- }
- }
-
- NameIdentifier iden = new NameIdentifier(sb.toString(), true);
- // quoted identifier - can't concatenate anything more
- SkipWhite();
- if (look == '!') {
- GetChar();
- return new SheetIdentifier(bookName, iden);
- }
- // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
- if (look == ':') {
- return parseSheetRange(bookName, iden);
+ /**
+ *
+ * @return true
if the specified character may be used in a defined name
+ */
+ private static boolean isValidDefinedNameChar(char ch) {
+ if (Character.isLetterOrDigit(ch)) {
+ return true;
+ }
+ switch (ch) {
+ case '.':
+ case '_':
+ case '?':
+ case '\\': // of all things
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ *
+ * @param sheetIden may be null
+ * @param part1
+ * @param part2 may be null
+ */
+ private ParseNode createAreaRefParseNode(SheetIdentifier sheetIden, SimpleRangePart part1,
+ SimpleRangePart part2) throws FormulaParseException {
+ Ptg ptg;
+ if (part2 == null) {
+ CellReference cr = part1.getCellReference();
+ if (sheetIden == null) {
+ ptg = new RefPtg(cr);
+ } else {
+ ptg = _book.get3DReferencePtg(cr, sheetIden);
}
- return null;
- }
+ } else {
+ AreaReference areaRef = createAreaRef(part1, part2);
- // unquoted sheet names must start with underscore or a letter
- if (look =='_' || Character.isLetter(look)) {
- StringBuilder sb = new StringBuilder();
- // can concatenate idens with dots
- while (isUnquotedSheetNameChar(look)) {
- sb.append(look);
- GetChar();
- }
- NameIdentifier iden = new NameIdentifier(sb.toString(), false);
- SkipWhite();
- if (look == '!') {
- GetChar();
- return new SheetIdentifier(bookName, iden);
- }
+ if (sheetIden == null) {
+ ptg = new AreaPtg(areaRef);
+ } else {
+ ptg = _book.get3DReferencePtg(areaRef, sheetIden);
+ }
+ }
+ return new ParseNode(ptg);
+ }
+
+ private AreaReference createAreaRef(SimpleRangePart part1, SimpleRangePart part2) {
+ if (!part1.isCompatibleForArea(part2)) {
+ throw new FormulaParseException("has incompatible parts: '"
+ + part1.getRep() + "' and '" + part2.getRep() + "'.");
+ }
+ if (part1.isRow()) {
+ return AreaReference.getWholeRow(_ssVersion, part1.getRep(), part2.getRep());
+ }
+ if (part1.isColumn()) {
+ return AreaReference.getWholeColumn(_ssVersion, part1.getRep(), part2.getRep());
+ }
+ return new AreaReference(part1.getCellReference(), part2.getCellReference());
+ }
+
+ /**
+ * Matches a zero or one letter-runs followed by zero or one digit-runs.
+ * Either or both runs man optionally be prefixed with a single '$'.
+ * (copied+modified from {@link org.apache.poi.ss.util.CellReference#CELL_REF_PATTERN})
+ */
+ private static final Pattern CELL_REF_PATTERN = Pattern.compile("(\\$?[A-Za-z]+)?(\\$?[0-9]+)?");
+
+ /**
+ * Parses out a potential LHS or RHS of a ':' intended to produce a plain AreaRef. Normally these are
+ * proper cell references but they could also be row or column refs like "$AC" or "10"
+ * @return null
(and leaves {@link #_pointer} unchanged if a proper range part does not parse out
+ */
+ private SimpleRangePart parseSimpleRangePart() {
+ int ptr = _pointer-1; // TODO avoid StringIndexOutOfBounds
+ boolean hasDigits = false;
+ boolean hasLetters = false;
+ while (ptr < _formulaLength) {
+ char ch = _formulaString.charAt(ptr);
+ if (Character.isDigit(ch)) {
+ hasDigits = true;
+ } else if (Character.isLetter(ch)) {
+ hasLetters = true;
+ } else if (ch =='$' || ch =='_') {
+ //
+ } else {
+ break;
+ }
+ ptr++;
+ }
+ if (ptr <= _pointer-1) {
+ return null;
+ }
+ String rep = _formulaString.substring(_pointer-1, ptr);
+ if (!CELL_REF_PATTERN.matcher(rep).matches()) {
+ return null;
+ }
+ // Check range bounds against grid max
+ if (hasLetters && hasDigits) {
+ if (!isValidCellReference(rep)) {
+ return null;
+ }
+ } else if (hasLetters) {
+ if (!CellReference.isColumnWithnRange(rep.replace("$", ""), _ssVersion)) {
+ return null;
+ }
+ } else if (hasDigits) {
+ int i;
+ try {
+ i = Integer.parseInt(rep.replace("$", ""));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ if (i<1 || i>_ssVersion.getMaxRows()) {
+ return null;
+ }
+ } else {
+ // just dollars ? can this happen?
+ return null;
+ }
+
+
+ resetPointer(ptr+1); // stepping forward
+ return new SimpleRangePart(rep, hasLetters, hasDigits);
+ }
+
+
+ /**
+ * A1, $A1, A$1, $A$1, A, 1
+ */
+ private static final class SimpleRangePart {
+ private enum Type {
+ CELL, ROW, COLUMN;
+
+ public static Type get(boolean hasLetters, boolean hasDigits) {
+ if (hasLetters) {
+ return hasDigits ? CELL : COLUMN;
+ }
+ if (!hasDigits) {
+ throw new IllegalArgumentException("must have either letters or numbers");
+ }
+ return ROW;
+ }
+ }
+
+ private final Type _type;
+ private final String _rep;
+
+ public SimpleRangePart(String rep, boolean hasLetters, boolean hasNumbers) {
+ _rep = rep;
+ _type = Type.get(hasLetters, hasNumbers);
+ }
+
+ public boolean isCell() {
+ return _type == Type.CELL;
+ }
+
+ public boolean isRowOrColumn() {
+ return _type != Type.CELL;
+ }
+
+ public CellReference getCellReference() {
+ if (_type != Type.CELL) {
+ throw new IllegalStateException("Not applicable to this type");
+ }
+ return new CellReference(_rep);
+ }
+
+ public boolean isColumn() {
+ return _type == Type.COLUMN;
+ }
+
+ public boolean isRow() {
+ return _type == Type.ROW;
+ }
+
+ public String getRep() {
+ return _rep;
+ }
+
+ /**
+ * @return true
if the two range parts can be combined in an
+ * {@link AreaPtg} ( Note - the explicit range operator (:) may still be valid
+ * when this method returns false
)
+ */
+ public boolean isCompatibleForArea(SimpleRangePart part2) {
+ return _type == part2._type;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append(getClass().getName()).append(" [");
+ sb.append(_rep);
+ sb.append("]");
+ return sb.toString();
+ }
+ }
+
+ /**
+ * Note - caller should reset {@link #_pointer} upon null
result
+ * @return The sheet name as an identifier null
if '!' is not found in the right place
+ */
+ private SheetIdentifier parseSheetName() {
+ String bookName;
+ if (look == '[') {
+ StringBuilder sb = new StringBuilder();
+ GetChar();
+ while (look != ']') {
+ sb.append(look);
+ GetChar();
+ }
+ GetChar();
+ bookName = sb.toString();
+ } else {
+ bookName = null;
+ }
+
+ if (look == '\'') {
+ StringBuffer sb = new StringBuffer();
+
+ Match('\'');
+ boolean done = look == '\'';
+ while(!done) {
+ sb.append(look);
+ GetChar();
+ if(look == '\'')
+ {
+ Match('\'');
+ done = look != '\'';
+ }
+ }
+
+ NameIdentifier iden = new NameIdentifier(sb.toString(), true);
+ // quoted identifier - can't concatenate anything more
+ SkipWhite();
+ if (look == '!') {
+ GetChar();
+ return new SheetIdentifier(bookName, iden);
+ }
// See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
if (look == ':') {
return parseSheetRange(bookName, iden);
}
- return null;
- }
- if (look == '!' && bookName != null) {
- // Raw book reference, without a sheet
+ return null;
+ }
+
+ // unquoted sheet names must start with underscore or a letter
+ if (look =='_' || Character.isLetter(look)) {
+ StringBuilder sb = new StringBuilder();
+ // can concatenate idens with dots
+ while (isUnquotedSheetNameChar(look)) {
+ sb.append(look);
+ GetChar();
+ }
+ NameIdentifier iden = new NameIdentifier(sb.toString(), false);
+ SkipWhite();
+ if (look == '!') {
+ GetChar();
+ return new SheetIdentifier(bookName, iden);
+ }
+ // See if it's a multi-sheet range, eg Sheet1:Sheet3!A1
+ if (look == ':') {
+ return parseSheetRange(bookName, iden);
+ }
+ return null;
+ }
+ if (look == '!' && bookName != null) {
+ // Raw book reference, without a sheet
GetChar();
- return new SheetIdentifier(bookName, null);
- }
- return null;
- }
-
- /**
- * If we have something that looks like [book]Sheet1: or
- * Sheet1, see if it's actually a range eg Sheet1:Sheet2!
- */
- private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) {
+ return new SheetIdentifier(bookName, null);
+ }
+ return null;
+ }
+
+ /**
+ * If we have something that looks like [book]Sheet1: or
+ * Sheet1, see if it's actually a range eg Sheet1:Sheet2!
+ */
+ private SheetIdentifier parseSheetRange(String bookname, NameIdentifier sheet1Name) {
GetChar();
SheetIdentifier sheet2 = parseSheetName();
if (sheet2 != null) {
return new SheetRangeIdentifier(bookname, sheet1Name, sheet2.getSheetIdentifier());
}
return null;
- }
+ }
- /**
- * very similar to {@link SheetNameFormatter#isSpecialChar(char)}
- */
- private static boolean isUnquotedSheetNameChar(char ch) {
- if(Character.isLetterOrDigit(ch)) {
- return true;
- }
- switch(ch) {
- case '.': // dot is OK
- case '_': // underscore is OK
- return true;
- }
- return false;
- }
+ /**
+ * very similar to {@link SheetNameFormatter#isSpecialChar(char)}
+ */
+ private static boolean isUnquotedSheetNameChar(char ch) {
+ if(Character.isLetterOrDigit(ch)) {
+ return true;
+ }
+ switch(ch) {
+ case '.': // dot is OK
+ case '_': // underscore is OK
+ return true;
+ }
+ return false;
+ }
- /**
- * @return true
if the specified name is a valid cell reference
- */
- private boolean isValidCellReference(String str) {
- //check range bounds against grid max
- boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL;
+ /**
+ * @return true
if the specified name is a valid cell reference
+ */
+ private boolean isValidCellReference(String str) {
+ //check range bounds against grid max
+ boolean result = CellReference.classifyCellReference(str, _ssVersion) == NameType.CELL;
- if(result){
- /**
- * Check if the argument is a function. Certain names can be either a cell reference or a function name
- * depending on the contenxt. Compare the following examples in Excel 2007:
- * (a) LOG10(100) + 1
- * (b) LOG10 + 1
- * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference
- */
- boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null;
- if(isFunc){
- int savePointer = _pointer;
- resetPointer(_pointer + str.length());
- SkipWhite();
- // open bracket indicates that the argument is a function,
- // the returning value should be false, i.e. "not a valid cell reference"
- result = look != '(';
- resetPointer(savePointer);
- }
- }
- return result;
- }
+ if(result){
+ /**
+ * Check if the argument is a function. Certain names can be either a cell reference or a function name
+ * depending on the contenxt. Compare the following examples in Excel 2007:
+ * (a) LOG10(100) + 1
+ * (b) LOG10 + 1
+ * In (a) LOG10 is a name of a built-in function. In (b) LOG10 is a cell reference
+ */
+ boolean isFunc = FunctionMetadataRegistry.getFunctionByName(str.toUpperCase(Locale.ROOT)) != null;
+ if(isFunc){
+ int savePointer = _pointer;
+ resetPointer(_pointer + str.length());
+ SkipWhite();
+ // open bracket indicates that the argument is a function,
+ // the returning value should be false, i.e. "not a valid cell reference"
+ result = look != '(';
+ resetPointer(savePointer);
+ }
+ }
+ return result;
+ }
- /**
- * Note - Excel function names are 'case aware but not case sensitive'. This method may end
- * up creating a defined name record in the workbook if the specified name is not an internal
- * Excel function, and has not been encountered before.
- *
- * Side effect: creates workbook name if name is not recognized (name is probably a UDF)
- *
- * @param name case preserved function name (as it was entered/appeared in the formula).
- */
- private ParseNode function(String name) {
- Ptg nameToken = null;
- if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
- // user defined function
- // in the token tree, the name is more or less the first argument
+ /**
+ * Note - Excel function names are 'case aware but not case sensitive'. This method may end
+ * up creating a defined name record in the workbook if the specified name is not an internal
+ * Excel function, and has not been encountered before.
+ *
+ * Side effect: creates workbook name if name is not recognized (name is probably a UDF)
+ *
+ * @param name case preserved function name (as it was entered/appeared in the formula).
+ */
+ private ParseNode function(String name) {
+ Ptg nameToken = null;
+ if(!AbstractFunctionPtg.isBuiltInFunctionName(name)) {
+ // user defined function
+ // in the token tree, the name is more or less the first argument
- if (_book == null) {
- // Only test cases omit the book (expecting it not to be needed)
- throw new IllegalStateException("Need book to evaluate name '" + name + "'");
- }
- // Check to see if name is a named range in the workbook
- EvaluationName hName = _book.getName(name, _sheetIndex);
- if (hName != null) {
- if (!hName.isFunctionName()) {
- throw new FormulaParseException("Attempt to use name '" + name
- + "' as a function, but defined name in workbook does not refer to a function");
- }
-
- // calls to user-defined functions within the workbook
- // get a Name token which points to a defined name record
- nameToken = hName.createPtg();
- } else {
- // Check if name is an external names table
- nameToken = _book.getNameXPtg(name, null);
- if (nameToken == null) {
- // name is not an internal or external name
- if (log.check(POILogger.WARN)) {
- log.log(POILogger.WARN,
- "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook.");
- }
- // name is probably the name of an unregistered User-Defined Function
- switch (_book.getSpreadsheetVersion()) {
- case EXCEL97:
- // HSSFWorkbooks require a name to be added to Workbook defined names table
- addName(name);
- hName = _book.getName(name, _sheetIndex);
- nameToken = hName.createPtg();
- break;
- case EXCEL2007:
- // XSSFWorkbooks store formula names as strings.
- nameToken = new NameXPxg(name);
- break;
- default:
- throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name());
- }
- }
- }
- }
+ if (_book == null) {
+ // Only test cases omit the book (expecting it not to be needed)
+ throw new IllegalStateException("Need book to evaluate name '" + name + "'");
+ }
+ // Check to see if name is a named range in the workbook
+ EvaluationName hName = _book.getName(name, _sheetIndex);
+ if (hName != null) {
+ if (!hName.isFunctionName()) {
+ throw new FormulaParseException("Attempt to use name '" + name
+ + "' as a function, but defined name in workbook does not refer to a function");
+ }
+
+ // calls to user-defined functions within the workbook
+ // get a Name token which points to a defined name record
+ nameToken = hName.createPtg();
+ } else {
+ // Check if name is an external names table
+ nameToken = _book.getNameXPtg(name, null);
+ if (nameToken == null) {
+ // name is not an internal or external name
+ if (log.check(POILogger.WARN)) {
+ log.log(POILogger.WARN,
+ "FormulaParser.function: Name '" + name + "' is completely unknown in the current workbook.");
+ }
+ // name is probably the name of an unregistered User-Defined Function
+ switch (_book.getSpreadsheetVersion()) {
+ case EXCEL97:
+ // HSSFWorkbooks require a name to be added to Workbook defined names table
+ addName(name);
+ hName = _book.getName(name, _sheetIndex);
+ nameToken = hName.createPtg();
+ break;
+ case EXCEL2007:
+ // XSSFWorkbooks store formula names as strings.
+ nameToken = new NameXPxg(name);
+ break;
+ default:
+ throw new IllegalStateException("Unexpected spreadsheet version: " + _book.getSpreadsheetVersion().name());
+ }
+ }
+ }
+ }
- Match('(');
- ParseNode[] args = Arguments();
- Match(')');
+ Match('(');
+ ParseNode[] args = Arguments();
+ Match(')');
- return getFunction(name, nameToken, args);
- }
-
- /**
- * Adds a name (named range or user defined function) to underlying workbook's names table
- * @param functionName
- */
- private final void addName(String functionName) {
- final Name name = _book.createName();
- name.setFunction(true);
- name.setNameName(functionName);
- name.setSheetIndex(_sheetIndex);
- }
+ return getFunction(name, nameToken, args);
+ }
+
+ /**
+ * Adds a name (named range or user defined function) to underlying workbook's names table
+ * @param functionName
+ */
+ private final void addName(String functionName) {
+ final Name name = _book.createName();
+ name.setFunction(true);
+ name.setNameName(functionName);
+ name.setSheetIndex(_sheetIndex);
+ }
- /**
- * Generates the variable function ptg for the formula.
- *
- * For IF Formulas, additional PTGs are added to the tokens
- * @param name a {@link NamePtg} or {@link NameXPtg} or null
- * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
- */
- private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
+ /**
+ * Generates the variable function ptg for the formula.
+ *
+ * For IF Formulas, additional PTGs are added to the tokens
+ * @param name a {@link NamePtg} or {@link NameXPtg} or null
+ * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
+ */
+ private ParseNode getFunction(String name, Ptg namePtg, ParseNode[] args) {
- FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
- int numArgs = args.length;
- if(fm == null) {
- if (namePtg == null) {
- throw new IllegalStateException("NamePtg must be supplied for external functions");
- }
- // must be external function
- ParseNode[] allArgs = new ParseNode[numArgs+1];
- allArgs[0] = new ParseNode(namePtg);
- System.arraycopy(args, 0, allArgs, 1, numArgs);
- return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs);
- }
+ FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase(Locale.ROOT));
+ int numArgs = args.length;
+ if(fm == null) {
+ if (namePtg == null) {
+ throw new IllegalStateException("NamePtg must be supplied for external functions");
+ }
+ // must be external function
+ ParseNode[] allArgs = new ParseNode[numArgs+1];
+ allArgs[0] = new ParseNode(namePtg);
+ System.arraycopy(args, 0, allArgs, 1, numArgs);
+ return new ParseNode(FuncVarPtg.create(name, numArgs+1), allArgs);
+ }
- if (namePtg != null) {
- throw new IllegalStateException("NamePtg no applicable to internal functions");
- }
- boolean isVarArgs = !fm.hasFixedArgsLength();
- int funcIx = fm.getIndex();
- if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
- // Excel encodes the sum of a single argument as tAttrSum
- // POI does the same for consistency, but this is not critical
- return new ParseNode(AttrPtg.getSumSingle(), args);
- // The code below would encode tFuncVar(SUM) which seems to do no harm
- }
- validateNumArgs(args.length, fm);
+ if (namePtg != null) {
+ throw new IllegalStateException("NamePtg no applicable to internal functions");
+ }
+ boolean isVarArgs = !fm.hasFixedArgsLength();
+ int funcIx = fm.getIndex();
+ if (funcIx == FunctionMetadataRegistry.FUNCTION_INDEX_SUM && args.length == 1) {
+ // Excel encodes the sum of a single argument as tAttrSum
+ // POI does the same for consistency, but this is not critical
+ return new ParseNode(AttrPtg.getSumSingle(), args);
+ // The code below would encode tFuncVar(SUM) which seems to do no harm
+ }
+ validateNumArgs(args.length, fm);
- AbstractFunctionPtg retval;
- if(isVarArgs) {
- retval = FuncVarPtg.create(name, numArgs);
- } else {
- retval = FuncPtg.create(funcIx);
- }
- return new ParseNode(retval, args);
- }
+ AbstractFunctionPtg retval;
+ if(isVarArgs) {
+ retval = FuncVarPtg.create(name, numArgs);
+ } else {
+ retval = FuncPtg.create(funcIx);
+ }
+ return new ParseNode(retval, args);
+ }
- private void validateNumArgs(int numArgs, FunctionMetadata fm) {
- if(numArgs < fm.getMinParams()) {
- String msg = "Too few arguments to function '" + fm.getName() + "'. ";
- if(fm.hasFixedArgsLength()) {
- msg += "Expected " + fm.getMinParams();
- } else {
- msg += "At least " + fm.getMinParams() + " were expected";
- }
- msg += " but got " + numArgs + ".";
- throw new FormulaParseException(msg);
- }
- //the maximum number of arguments depends on the Excel version
- int maxArgs;
- if (fm.hasUnlimitedVarags()) {
- if(_book != null) {
- maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
- } else {
- //_book can be omitted by test cases
- maxArgs = fm.getMaxParams(); // just use BIFF8
- }
- } else {
- maxArgs = fm.getMaxParams();
- }
+ private void validateNumArgs(int numArgs, FunctionMetadata fm) {
+ if(numArgs < fm.getMinParams()) {
+ String msg = "Too few arguments to function '" + fm.getName() + "'. ";
+ if(fm.hasFixedArgsLength()) {
+ msg += "Expected " + fm.getMinParams();
+ } else {
+ msg += "At least " + fm.getMinParams() + " were expected";
+ }
+ msg += " but got " + numArgs + ".";
+ throw new FormulaParseException(msg);
+ }
+ //the maximum number of arguments depends on the Excel version
+ int maxArgs;
+ if (fm.hasUnlimitedVarags()) {
+ if(_book != null) {
+ maxArgs = _book.getSpreadsheetVersion().getMaxFunctionArgs();
+ } else {
+ //_book can be omitted by test cases
+ maxArgs = fm.getMaxParams(); // just use BIFF8
+ }
+ } else {
+ maxArgs = fm.getMaxParams();
+ }
- if(numArgs > maxArgs) {
- String msg = "Too many arguments to function '" + fm.getName() + "'. ";
- if(fm.hasFixedArgsLength()) {
- msg += "Expected " + maxArgs;
- } else {
- msg += "At most " + maxArgs + " were expected";
- }
- msg += " but got " + numArgs + ".";
- throw new FormulaParseException(msg);
- }
- }
+ if(numArgs > maxArgs) {
+ String msg = "Too many arguments to function '" + fm.getName() + "'. ";
+ if(fm.hasFixedArgsLength()) {
+ msg += "Expected " + maxArgs;
+ } else {
+ msg += "At most " + maxArgs + " were expected";
+ }
+ msg += " but got " + numArgs + ".";
+ throw new FormulaParseException(msg);
+ }
+ }
- private static boolean isArgumentDelimiter(char ch) {
- return ch == ',' || ch == ')';
- }
+ private static boolean isArgumentDelimiter(char ch) {
+ return ch == ',' || ch == ')';
+ }
- /** get arguments to a function */
- private ParseNode[] Arguments() {
- //average 2 args per function
- List temp = new ArrayList(2);
- SkipWhite();
- if(look == ')') {
- return ParseNode.EMPTY_ARRAY;
- }
+ /** get arguments to a function */
+ private ParseNode[] Arguments() {
+ //average 2 args per function
+ List temp = new ArrayList(2);
+ SkipWhite();
+ if(look == ')') {
+ return ParseNode.EMPTY_ARRAY;
+ }
- boolean missedPrevArg = true;
- while (true) {
- SkipWhite();
- if (isArgumentDelimiter(look)) {
- if (missedPrevArg) {
- temp.add(new ParseNode(MissingArgPtg.instance));
- }
- if (look == ')') {
- break;
- }
- Match(',');
- missedPrevArg = true;
- continue;
- }
- temp.add(comparisonExpression());
- missedPrevArg = false;
- SkipWhite();
- if (!isArgumentDelimiter(look)) {
- throw expected("',' or ')'");
- }
- }
- ParseNode[] result = new ParseNode[temp.size()];
- temp.toArray(result);
- return result;
- }
+ boolean missedPrevArg = true;
+ while (true) {
+ SkipWhite();
+ if (isArgumentDelimiter(look)) {
+ if (missedPrevArg) {
+ temp.add(new ParseNode(MissingArgPtg.instance));
+ }
+ if (look == ')') {
+ break;
+ }
+ Match(',');
+ missedPrevArg = true;
+ continue;
+ }
+ temp.add(comparisonExpression());
+ missedPrevArg = false;
+ SkipWhite();
+ if (!isArgumentDelimiter(look)) {
+ throw expected("',' or ')'");
+ }
+ }
+ ParseNode[] result = new ParseNode[temp.size()];
+ temp.toArray(result);
+ return result;
+ }
/** Parse and Translate a Math Factor */
- private ParseNode powerFactor() {
- ParseNode result = percentFactor();
- while(true) {
- SkipWhite();
- if(look != '^') {
- return result;
- }
- Match('^');
- ParseNode other = percentFactor();
- result = new ParseNode(PowerPtg.instance, result, other);
- }
- }
-
- private ParseNode percentFactor() {
- ParseNode result = parseSimpleFactor();
- while(true) {
- SkipWhite();
- if(look != '%') {
- return result;
- }
- Match('%');
- result = new ParseNode(PercentPtg.instance, result);
- }
- }
-
-
- /**
- * factors (without ^ or % )
- */
- private ParseNode parseSimpleFactor() {
- SkipWhite();
- switch(look) {
- case '#':
- return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
- case '-':
- Match('-');
- return parseUnary(false);
- case '+':
- Match('+');
- return parseUnary(true);
- case '(':
- Match('(');
- ParseNode inside = unionExpression();
- Match(')');
- return new ParseNode(ParenthesisPtg.instance, inside);
- case '"':
- return new ParseNode(new StringPtg(parseStringLiteral()));
- case '{':
- Match('{');
- ParseNode arrayNode = parseArray();
- Match('}');
- return arrayNode;
- }
- if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '['){
- return parseRangeExpression();
- }
- if (look == '.') {
- return new ParseNode(parseNumber());
- }
- throw expected("cell ref or constant literal");
- }
-
-
- private ParseNode parseUnary(boolean isPlus) {
-
- boolean numberFollows = IsDigit(look) || look=='.';
- ParseNode factor = powerFactor();
-
- if (numberFollows) {
- // + or - directly next to a number is parsed with the number
-
- Ptg token = factor.getToken();
- if (token instanceof NumberPtg) {
- if (isPlus) {
- return factor;
- }
- token = new NumberPtg(-((NumberPtg)token).getValue());
- return new ParseNode(token);
- }
- if (token instanceof IntPtg) {
- if (isPlus) {
- return factor;
- }
- int intVal = ((IntPtg)token).getValue();
- // note - cannot use IntPtg for negatives
- token = new NumberPtg(-intVal);
- return new ParseNode(token);
- }
- }
- return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor);
- }
-
- private ParseNode parseArray() {
- List rowsData = new ArrayList();
- while(true) {
- Object[] singleRowData = parseArrayRow();
- rowsData.add(singleRowData);
- if (look == '}') {
- break;
- }
- if (look != ';') {
- throw expected("'}' or ';'");
- }
- Match(';');
- }
- int nRows = rowsData.size();
- Object[][] values2d = new Object[nRows][];
- rowsData.toArray(values2d);
- int nColumns = values2d[0].length;
- checkRowLengths(values2d, nColumns);
-
- return new ParseNode(new ArrayPtg(values2d));
- }
- private void checkRowLengths(Object[][] values2d, int nColumns) {
- for (int i = 0; i < values2d.length; i++) {
- int rowLen = values2d[i].length;
- if (rowLen != nColumns) {
- throw new FormulaParseException("Array row " + i + " has length " + rowLen
- + " but row 0 has length " + nColumns);
- }
- }
- }
-
- private Object[] parseArrayRow() {
- List temp = new ArrayList();
- while (true) {
- temp.add(parseArrayItem());
- SkipWhite();
- switch(look) {
- case '}':
- case ';':
- break;
- case ',':
- Match(',');
- continue;
- default:
- throw expected("'}' or ','");
-
- }
- break;
- }
-
- Object[] result = new Object[temp.size()];
- temp.toArray(result);
- return result;
- }
-
- private Object parseArrayItem() {
- SkipWhite();
- switch(look) {
- case '"': return parseStringLiteral();
- case '#': return ErrorConstant.valueOf(parseErrorLiteral());
- case 'F': case 'f':
- case 'T': case 't':
- return parseBooleanLiteral();
- case '-':
- Match('-');
- SkipWhite();
- return convertArrayNumber(parseNumber(), false);
- }
- // else assume number
- return convertArrayNumber(parseNumber(), true);
- }
-
- private Boolean parseBooleanLiteral() {
- String iden = parseUnquotedIdentifier();
- if ("TRUE".equalsIgnoreCase(iden)) {
- return Boolean.TRUE;
- }
- if ("FALSE".equalsIgnoreCase(iden)) {
- return Boolean.FALSE;
- }
- throw expected("'TRUE' or 'FALSE'");
- }
-
- private static Double convertArrayNumber(Ptg ptg, boolean isPositive) {
- double value;
- if (ptg instanceof IntPtg) {
- value = ((IntPtg)ptg).getValue();
- } else if (ptg instanceof NumberPtg) {
- value = ((NumberPtg)ptg).getValue();
- } else {
- throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
- }
- if (!isPositive) {
- value = -value;
- }
- return new Double(value);
- }
-
- private Ptg parseNumber() {
- String number2 = null;
- String exponent = null;
- String number1 = GetNum();
-
- if (look == '.') {
- GetChar();
- number2 = GetNum();
- }
-
- if (look == 'E') {
- GetChar();
-
- String sign = "";
- if (look == '+') {
- GetChar();
- } else if (look == '-') {
- GetChar();
- sign = "-";
- }
-
- String number = GetNum();
- if (number == null) {
- throw expected("Integer");
- }
- exponent = sign + number;
- }
-
- if (number1 == null && number2 == null) {
- throw expected("Integer");
- }
-
- return getNumberPtgFromString(number1, number2, exponent);
- }
-
-
- private int parseErrorLiteral() {
- Match('#');
- String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT);
- if (part1 == null) {
- throw expected("remainder of error constant literal");
- }
-
- switch(part1.charAt(0)) {
- case 'V': {
- FormulaError fe = FormulaError.VALUE;
- if(part1.equals(fe.name())) {
- Match('!');
- return fe.getCode();
- }
- throw expected(fe.getString());
- }
- case 'R': {
- FormulaError fe = FormulaError.REF;
- if(part1.equals(fe.name())) {
- Match('!');
- return fe.getCode();
- }
- throw expected(fe.getString());
+ private ParseNode powerFactor() {
+ ParseNode result = percentFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '^') {
+ return result;
}
- case 'D': {
- FormulaError fe = FormulaError.DIV0;
- if(part1.equals("DIV")) {
- Match('/');
- Match('0');
- Match('!');
- return fe.getCode();
- }
- throw expected(fe.getString());
- }
- case 'N': {
- FormulaError fe = FormulaError.NAME;
- if(part1.equals(fe.name())) {
- // only one that ends in '?'
- Match('?');
- return fe.getCode();
- }
- fe = FormulaError.NUM;
- if(part1.equals(fe.name())) {
- Match('!');
- return fe.getCode();
- }
- fe = FormulaError.NULL;
- if(part1.equals(fe.name())) {
- Match('!');
- return fe.getCode();
- }
- fe = FormulaError.NA;
- if(part1.equals("N")) {
- Match('/');
- if(look != 'A' && look != 'a') {
- throw expected(fe.getString());
- }
- Match(look);
- // Note - no '!' or '?' suffix
- return fe.getCode();
- }
- throw expected("#NAME?, #NUM!, #NULL! or #N/A");
- }
- }
- throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
- }
+ Match('^');
+ ParseNode other = percentFactor();
+ result = new ParseNode(PowerPtg.instance, result, other);
+ }
+ }
- private String parseUnquotedIdentifier() {
- if (look == '\'') {
- throw expected("unquoted identifier");
- }
- StringBuilder sb = new StringBuilder();
- while (Character.isLetterOrDigit(look) || look == '.') {
- sb.append(look);
- GetChar();
- }
- if (sb.length() < 1) {
- return null;
- }
-
- return sb.toString();
- }
-
- /**
- * Get a PTG for an integer from its string representation.
- * return Int or Number Ptg based on size of input
- */
- private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
- StringBuffer number = new StringBuffer();
-
- if (number2 == null) {
- number.append(number1);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
- }
-
- String numberStr = number.toString();
- int intVal;
- try {
- intVal = Integer.parseInt(numberStr);
- } catch (NumberFormatException e) {
- return new NumberPtg(numberStr);
- }
- if (IntPtg.isInRange(intVal)) {
- return new IntPtg(intVal);
- }
- return new NumberPtg(numberStr);
- }
-
- if (number1 != null) {
- number.append(number1);
- }
-
- number.append('.');
- number.append(number2);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
- }
-
- return new NumberPtg(number.toString());
- }
+ private ParseNode percentFactor() {
+ ParseNode result = parseSimpleFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '%') {
+ return result;
+ }
+ Match('%');
+ result = new ParseNode(PercentPtg.instance, result);
+ }
+ }
- private String parseStringLiteral() {
- Match('"');
+ /**
+ * factors (without ^ or % )
+ */
+ private ParseNode parseSimpleFactor() {
+ SkipWhite();
+ switch(look) {
+ case '#':
+ return new ParseNode(ErrPtg.valueOf(parseErrorLiteral()));
+ case '-':
+ Match('-');
+ return parseUnary(false);
+ case '+':
+ Match('+');
+ return parseUnary(true);
+ case '(':
+ Match('(');
+ ParseNode inside = unionExpression();
+ Match(')');
+ return new ParseNode(ParenthesisPtg.instance, inside);
+ case '"':
+ return new ParseNode(new StringPtg(parseStringLiteral()));
+ case '{':
+ Match('{');
+ ParseNode arrayNode = parseArray();
+ Match('}');
+ return arrayNode;
+ }
+ // named ranges and tables can start with underscore or backslash
+ // see https://support.office.com/en-us/article/Define-and-use-names-in-formulas-4d0f13ac-53b7-422e-afd2-abd7ff379c64?ui=en-US&rs=en-US&ad=US#bmsyntax_rules_for_names
+ if (IsAlpha(look) || Character.isDigit(look) || look == '\'' || look == '[' || look == '_' || look == '\\' ) {
+ return parseRangeExpression();
+ }
+ if (look == '.') {
+ return new ParseNode(parseNumber());
+ }
+ throw expected("cell ref or constant literal");
+ }
- StringBuffer token = new StringBuffer();
- while (true) {
- if (look == '"') {
- GetChar();
- if (look != '"') {
- break;
- }
- }
- token.append(look);
- GetChar();
- }
- return token.toString();
- }
- /** Parse and Translate a Math Term */
- private ParseNode Term() {
- ParseNode result = powerFactor();
- while(true) {
- SkipWhite();
- Ptg operator;
- switch(look) {
- case '*':
- Match('*');
- operator = MultiplyPtg.instance;
- break;
- case '/':
- Match('/');
- operator = DividePtg.instance;
- break;
- default:
- return result; // finished with Term
- }
- ParseNode other = powerFactor();
- result = new ParseNode(operator, result, other);
- }
- }
+ private ParseNode parseUnary(boolean isPlus) {
- private ParseNode unionExpression() {
- ParseNode result = intersectionExpression();
- boolean hasUnions = false;
- while (true) {
- SkipWhite();
- switch(look) {
- case ',':
- GetChar();
- hasUnions = true;
- ParseNode other = intersectionExpression();
- result = new ParseNode(UnionPtg.instance, result, other);
- continue;
- }
- if (hasUnions) {
- return augmentWithMemPtg(result);
- }
- return result;
- }
- }
+ boolean numberFollows = IsDigit(look) || look=='.';
+ ParseNode factor = powerFactor();
+
+ if (numberFollows) {
+ // + or - directly next to a number is parsed with the number
+
+ Ptg token = factor.getToken();
+ if (token instanceof NumberPtg) {
+ if (isPlus) {
+ return factor;
+ }
+ token = new NumberPtg(-((NumberPtg)token).getValue());
+ return new ParseNode(token);
+ }
+ if (token instanceof IntPtg) {
+ if (isPlus) {
+ return factor;
+ }
+ int intVal = ((IntPtg)token).getValue();
+ // note - cannot use IntPtg for negatives
+ token = new NumberPtg(-intVal);
+ return new ParseNode(token);
+ }
+ }
+ return new ParseNode(isPlus ? UnaryPlusPtg.instance : UnaryMinusPtg.instance, factor);
+ }
+
+ private ParseNode parseArray() {
+ List rowsData = new ArrayList();
+ while(true) {
+ Object[] singleRowData = parseArrayRow();
+ rowsData.add(singleRowData);
+ if (look == '}') {
+ break;
+ }
+ if (look != ';') {
+ throw expected("'}' or ';'");
+ }
+ Match(';');
+ }
+ int nRows = rowsData.size();
+ Object[][] values2d = new Object[nRows][];
+ rowsData.toArray(values2d);
+ int nColumns = values2d[0].length;
+ checkRowLengths(values2d, nColumns);
+
+ return new ParseNode(new ArrayPtg(values2d));
+ }
+ private void checkRowLengths(Object[][] values2d, int nColumns) {
+ for (int i = 0; i < values2d.length; i++) {
+ int rowLen = values2d[i].length;
+ if (rowLen != nColumns) {
+ throw new FormulaParseException("Array row " + i + " has length " + rowLen
+ + " but row 0 has length " + nColumns);
+ }
+ }
+ }
+
+ private Object[] parseArrayRow() {
+ List temp = new ArrayList();
+ while (true) {
+ temp.add(parseArrayItem());
+ SkipWhite();
+ switch(look) {
+ case '}':
+ case ';':
+ break;
+ case ',':
+ Match(',');
+ continue;
+ default:
+ throw expected("'}' or ','");
+
+ }
+ break;
+ }
+
+ Object[] result = new Object[temp.size()];
+ temp.toArray(result);
+ return result;
+ }
+
+ private Object parseArrayItem() {
+ SkipWhite();
+ switch(look) {
+ case '"': return parseStringLiteral();
+ case '#': return ErrorConstant.valueOf(parseErrorLiteral());
+ case 'F': case 'f':
+ case 'T': case 't':
+ return parseBooleanLiteral();
+ case '-':
+ Match('-');
+ SkipWhite();
+ return convertArrayNumber(parseNumber(), false);
+ }
+ // else assume number
+ return convertArrayNumber(parseNumber(), true);
+ }
+
+ private Boolean parseBooleanLiteral() {
+ String iden = parseUnquotedIdentifier();
+ if ("TRUE".equalsIgnoreCase(iden)) {
+ return Boolean.TRUE;
+ }
+ if ("FALSE".equalsIgnoreCase(iden)) {
+ return Boolean.FALSE;
+ }
+ throw expected("'TRUE' or 'FALSE'");
+ }
+
+ private static Double convertArrayNumber(Ptg ptg, boolean isPositive) {
+ double value;
+ if (ptg instanceof IntPtg) {
+ value = ((IntPtg)ptg).getValue();
+ } else if (ptg instanceof NumberPtg) {
+ value = ((NumberPtg)ptg).getValue();
+ } else {
+ throw new RuntimeException("Unexpected ptg (" + ptg.getClass().getName() + ")");
+ }
+ if (!isPositive) {
+ value = -value;
+ }
+ return new Double(value);
+ }
+
+ private Ptg parseNumber() {
+ String number2 = null;
+ String exponent = null;
+ String number1 = GetNum();
+
+ if (look == '.') {
+ GetChar();
+ number2 = GetNum();
+ }
+
+ if (look == 'E') {
+ GetChar();
+
+ String sign = "";
+ if (look == '+') {
+ GetChar();
+ } else if (look == '-') {
+ GetChar();
+ sign = "-";
+ }
+
+ String number = GetNum();
+ if (number == null) {
+ throw expected("Integer");
+ }
+ exponent = sign + number;
+ }
+
+ if (number1 == null && number2 == null) {
+ throw expected("Integer");
+ }
+
+ return getNumberPtgFromString(number1, number2, exponent);
+ }
+
+
+ private int parseErrorLiteral() {
+ Match('#');
+ String part1 = parseUnquotedIdentifier().toUpperCase(Locale.ROOT);
+ if (part1 == null) {
+ throw expected("remainder of error constant literal");
+ }
+
+ switch(part1.charAt(0)) {
+ case 'V': {
+ FormulaError fe = FormulaError.VALUE;
+ if(part1.equals(fe.name())) {
+ Match('!');
+ return fe.getCode();
+ }
+ throw expected(fe.getString());
+ }
+ case 'R': {
+ FormulaError fe = FormulaError.REF;
+ if(part1.equals(fe.name())) {
+ Match('!');
+ return fe.getCode();
+ }
+ throw expected(fe.getString());
+ }
+ case 'D': {
+ FormulaError fe = FormulaError.DIV0;
+ if(part1.equals("DIV")) {
+ Match('/');
+ Match('0');
+ Match('!');
+ return fe.getCode();
+ }
+ throw expected(fe.getString());
+ }
+ case 'N': {
+ FormulaError fe = FormulaError.NAME;
+ if(part1.equals(fe.name())) {
+ // only one that ends in '?'
+ Match('?');
+ return fe.getCode();
+ }
+ fe = FormulaError.NUM;
+ if(part1.equals(fe.name())) {
+ Match('!');
+ return fe.getCode();
+ }
+ fe = FormulaError.NULL;
+ if(part1.equals(fe.name())) {
+ Match('!');
+ return fe.getCode();
+ }
+ fe = FormulaError.NA;
+ if(part1.equals("N")) {
+ Match('/');
+ if(look != 'A' && look != 'a') {
+ throw expected(fe.getString());
+ }
+ Match(look);
+ // Note - no '!' or '?' suffix
+ return fe.getCode();
+ }
+ throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+ }
+ }
+ throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+ }
+
+ private String parseUnquotedIdentifier() {
+ if (look == '\'') {
+ throw expected("unquoted identifier");
+ }
+ StringBuilder sb = new StringBuilder();
+ while (Character.isLetterOrDigit(look) || look == '.') {
+ sb.append(look);
+ GetChar();
+ }
+ if (sb.length() < 1) {
+ return null;
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Get a PTG for an integer from its string representation.
+ * return Int or Number Ptg based on size of input
+ */
+ private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+ StringBuffer number = new StringBuffer();
+
+ if (number2 == null) {
+ number.append(number1);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
+ String numberStr = number.toString();
+ int intVal;
+ try {
+ intVal = Integer.parseInt(numberStr);
+ } catch (NumberFormatException e) {
+ return new NumberPtg(numberStr);
+ }
+ if (IntPtg.isInRange(intVal)) {
+ return new IntPtg(intVal);
+ }
+ return new NumberPtg(numberStr);
+ }
+
+ if (number1 != null) {
+ number.append(number1);
+ }
+
+ number.append('.');
+ number.append(number2);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
+ return new NumberPtg(number.toString());
+ }
+
+
+ private String parseStringLiteral() {
+ Match('"');
+
+ StringBuffer token = new StringBuffer();
+ while (true) {
+ if (look == '"') {
+ GetChar();
+ if (look != '"') {
+ break;
+ }
+ }
+ token.append(look);
+ GetChar();
+ }
+ return token.toString();
+ }
+
+ /** Parse and Translate a Math Term */
+ private ParseNode Term() {
+ ParseNode result = powerFactor();
+ while(true) {
+ SkipWhite();
+ Ptg operator;
+ switch(look) {
+ case '*':
+ Match('*');
+ operator = MultiplyPtg.instance;
+ break;
+ case '/':
+ Match('/');
+ operator = DividePtg.instance;
+ break;
+ default:
+ return result; // finished with Term
+ }
+ ParseNode other = powerFactor();
+ result = new ParseNode(operator, result, other);
+ }
+ }
+
+ private ParseNode unionExpression() {
+ ParseNode result = intersectionExpression();
+ boolean hasUnions = false;
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case ',':
+ GetChar();
+ hasUnions = true;
+ ParseNode other = intersectionExpression();
+ result = new ParseNode(UnionPtg.instance, result, other);
+ continue;
+ }
+ if (hasUnions) {
+ return augmentWithMemPtg(result);
+ }
+ return result;
+ }
+ }
private ParseNode intersectionExpression() {
- ParseNode result = comparisonExpression();
- boolean hasIntersections = false;
- while (true) {
- SkipWhite();
- if (_inIntersection) {
- int savePointer = _pointer;
+ ParseNode result = comparisonExpression();
+ boolean hasIntersections = false;
+ while (true) {
+ SkipWhite();
+ if (_inIntersection) {
+ int savePointer = _pointer;
- // Don't getChar() as the space has already been eaten and recorded by SkipWhite().
- try {
- ParseNode other = comparisonExpression();
- result = new ParseNode(IntersectionPtg.instance, result, other);
- hasIntersections = true;
- continue;
- } catch (FormulaParseException e) {
- // if parsing for intersection fails we assume that we actually had an arbitrary
- // whitespace and thus should simply skip this whitespace
- resetPointer(savePointer);
- }
- }
- if (hasIntersections) {
- return augmentWithMemPtg(result);
- }
- return result;
- }
- }
-
- private ParseNode comparisonExpression() {
- ParseNode result = concatExpression();
- while (true) {
- SkipWhite();
- switch(look) {
- case '=':
- case '>':
- case '<':
- Ptg comparisonToken = getComparisonToken();
- ParseNode other = concatExpression();
- result = new ParseNode(comparisonToken, result, other);
- continue;
- }
- return result; // finished with predicate expression
- }
- }
+ // Don't getChar() as the space has already been eaten and recorded by SkipWhite().
+ try {
+ ParseNode other = comparisonExpression();
+ result = new ParseNode(IntersectionPtg.instance, result, other);
+ hasIntersections = true;
+ continue;
+ } catch (FormulaParseException e) {
+ // if parsing for intersection fails we assume that we actually had an arbitrary
+ // whitespace and thus should simply skip this whitespace
+ resetPointer(savePointer);
+ }
+ }
+ if (hasIntersections) {
+ return augmentWithMemPtg(result);
+ }
+ return result;
+ }
+ }
+
+ private ParseNode comparisonExpression() {
+ ParseNode result = concatExpression();
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case '=':
+ case '>':
+ case '<':
+ Ptg comparisonToken = getComparisonToken();
+ ParseNode other = concatExpression();
+ result = new ParseNode(comparisonToken, result, other);
+ continue;
+ }
+ return result; // finished with predicate expression
+ }
+ }
- private Ptg getComparisonToken() {
- if(look == '=') {
- Match(look);
- return EqualPtg.instance;
- }
- boolean isGreater = look == '>';
- Match(look);
- if(isGreater) {
- if(look == '=') {
- Match('=');
- return GreaterEqualPtg.instance;
- }
- return GreaterThanPtg.instance;
- }
- switch(look) {
- case '=':
- Match('=');
- return LessEqualPtg.instance;
- case '>':
- Match('>');
- return NotEqualPtg.instance;
- }
- return LessThanPtg.instance;
- }
+ private Ptg getComparisonToken() {
+ if(look == '=') {
+ Match(look);
+ return EqualPtg.instance;
+ }
+ boolean isGreater = look == '>';
+ Match(look);
+ if(isGreater) {
+ if(look == '=') {
+ Match('=');
+ return GreaterEqualPtg.instance;
+ }
+ return GreaterThanPtg.instance;
+ }
+ switch(look) {
+ case '=':
+ Match('=');
+ return LessEqualPtg.instance;
+ case '>':
+ Match('>');
+ return NotEqualPtg.instance;
+ }
+ return LessThanPtg.instance;
+ }
- private ParseNode concatExpression() {
- ParseNode result = additiveExpression();
- while (true) {
- SkipWhite();
- if(look != '&') {
- break; // finished with concat expression
- }
- Match('&');
- ParseNode other = additiveExpression();
- result = new ParseNode(ConcatPtg.instance, result, other);
- }
- return result;
- }
+ private ParseNode concatExpression() {
+ ParseNode result = additiveExpression();
+ while (true) {
+ SkipWhite();
+ if(look != '&') {
+ break; // finished with concat expression
+ }
+ Match('&');
+ ParseNode other = additiveExpression();
+ result = new ParseNode(ConcatPtg.instance, result, other);
+ }
+ return result;
+ }
- /** Parse and Translate an Expression */
- private ParseNode additiveExpression() {
- ParseNode result = Term();
- while (true) {
- SkipWhite();
- Ptg operator;
- switch(look) {
- case '+':
- Match('+');
- operator = AddPtg.instance;
- break;
- case '-':
- Match('-');
- operator = SubtractPtg.instance;
- break;
- default:
- return result; // finished with additive expression
- }
- ParseNode other = Term();
- result = new ParseNode(operator, result, other);
- }
- }
+ /** Parse and Translate an Expression */
+ private ParseNode additiveExpression() {
+ ParseNode result = Term();
+ while (true) {
+ SkipWhite();
+ Ptg operator;
+ switch(look) {
+ case '+':
+ Match('+');
+ operator = AddPtg.instance;
+ break;
+ case '-':
+ Match('-');
+ operator = SubtractPtg.instance;
+ break;
+ default:
+ return result; // finished with additive expression
+ }
+ ParseNode other = Term();
+ result = new ParseNode(operator, result, other);
+ }
+ }
- //{--------------------------------------------------------------}
- //{ Parse and Translate an Assignment Statement }
- /**
+ //{--------------------------------------------------------------}
+ //{ Parse and Translate an Assignment Statement }
+ /**
procedure Assignment;
var Name: string[8];
begin
@@ -1654,29 +1984,29 @@ begin
Expression;
end;
- **/
+ **/
- /**
- * API call to execute the parsing of the formula
- *
- */
- private void parse() {
- _pointer=0;
- GetChar();
- _rootNode = unionExpression();
+ /**
+ * API call to execute the parsing of the formula
+ *
+ */
+ private void parse() {
+ _pointer=0;
+ GetChar();
+ _rootNode = unionExpression();
- if(_pointer <= _formulaLength) {
- String msg = "Unused input [" + _formulaString.substring(_pointer-1)
- + "] after attempting to parse the formula [" + _formulaString + "]";
- throw new FormulaParseException(msg);
- }
- }
+ if(_pointer <= _formulaLength) {
+ String msg = "Unused input [" + _formulaString.substring(_pointer-1)
+ + "] after attempting to parse the formula [" + _formulaString + "]";
+ throw new FormulaParseException(msg);
+ }
+ }
- private Ptg[] getRPNPtg(int formulaType) {
- OperandClassTransformer oct = new OperandClassTransformer(formulaType);
- // RVA is for 'operand class': 'reference', 'value', 'array'
- oct.transformFormula(_rootNode);
- return ParseNode.toTokenArray(_rootNode);
- }
+ private Ptg[] getRPNPtg(int formulaType) {
+ OperandClassTransformer oct = new OperandClassTransformer(formulaType);
+ // RVA is for 'operand class': 'reference', 'value', 'array'
+ oct.transformFormula(_rootNode);
+ return ParseNode.toTokenArray(_rootNode);
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
index fe907fbb1..7ddcc944c 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaParsingWorkbook.java
@@ -20,6 +20,7 @@ package org.apache.poi.ss.formula;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.usermodel.Name;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
@@ -31,46 +32,51 @@ import org.apache.poi.ss.util.CellReference;
* @author Josh Micich
*/
public interface FormulaParsingWorkbook {
- /**
- * named range name matching is case insensitive
- */
- EvaluationName getName(String name, int sheetIndex);
-
- /**
- * Return the underlying workbook
- */
- Name createName();
+ /**
+ * named range name matching is case insensitive
+ */
+ EvaluationName getName(String name, int sheetIndex);
+
+ /**
+ * Return the underlying workbook
+ */
+ Name createName();
- /**
- * Return an external name (named range, function, user-defined function) Ptg
- */
- Ptg getNameXPtg(String name, SheetIdentifier sheet);
-
- /**
- * Produce the appropriate Ptg for a 3d cell reference
- */
- Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet);
+ /**
+ * XSSF Only - gets a table that exists in the worksheet
+ */
+ Table getTable(String name);
+
+ /**
+ * Return an external name (named range, function, user-defined function) Ptg
+ */
+ Ptg getNameXPtg(String name, SheetIdentifier sheet);
+
+ /**
+ * Produce the appropriate Ptg for a 3d cell reference
+ */
+ Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet);
/**
* Produce the appropriate Ptg for a 3d area reference
*/
Ptg get3DReferencePtg(AreaReference area, SheetIdentifier sheet);
- /**
- * gets the externSheet index for a sheet from this workbook
- */
- int getExternalSheetIndex(String sheetName);
- /**
- * gets the externSheet index for a sheet from an external workbook
- * @param workbookName e.g. "Budget.xls"
- * @param sheetName a name of a sheet in that workbook
- */
- int getExternalSheetIndex(String workbookName, String sheetName);
+ /**
+ * gets the externSheet index for a sheet from this workbook
+ */
+ int getExternalSheetIndex(String sheetName);
+ /**
+ * gets the externSheet index for a sheet from an external workbook
+ * @param workbookName e.g. "Budget.xls"
+ * @param sheetName a name of a sheet in that workbook
+ */
+ int getExternalSheetIndex(String workbookName, String sheetName);
- /**
- * Returns an enum holding spreadhseet properties specific to an Excel version (
- * max column and row numbers, max arguments to a function, etc.)
- */
- SpreadsheetVersion getSpreadsheetVersion();
+ /**
+ * Returns an enum holding spreadhseet properties specific to an Excel version (
+ * max column and row numbers, max arguments to a function, etc.)
+ */
+ SpreadsheetVersion getSpreadsheetVersion();
}
diff --git a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
index cc590772d..d7345f7cb 100644
--- a/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
+++ b/src/java/org/apache/poi/ss/formula/OperationEvaluationContext.java
@@ -46,199 +46,199 @@ import org.apache.poi.ss.util.CellReference.NameType;
* For POI internal use only
*/
public final class OperationEvaluationContext {
- public static final FreeRefFunction UDF = UserDefinedFunction.instance;
- private final EvaluationWorkbook _workbook;
- private final int _sheetIndex;
- private final int _rowIndex;
- private final int _columnIndex;
- private final EvaluationTracker _tracker;
- private final WorkbookEvaluator _bookEvaluator;
+ public static final FreeRefFunction UDF = UserDefinedFunction.instance;
+ private final EvaluationWorkbook _workbook;
+ private final int _sheetIndex;
+ private final int _rowIndex;
+ private final int _columnIndex;
+ private final EvaluationTracker _tracker;
+ private final WorkbookEvaluator _bookEvaluator;
- public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
- int srcColNum, EvaluationTracker tracker) {
- _bookEvaluator = bookEvaluator;
- _workbook = workbook;
- _sheetIndex = sheetIndex;
- _rowIndex = srcRowNum;
- _columnIndex = srcColNum;
- _tracker = tracker;
- }
+ public OperationEvaluationContext(WorkbookEvaluator bookEvaluator, EvaluationWorkbook workbook, int sheetIndex, int srcRowNum,
+ int srcColNum, EvaluationTracker tracker) {
+ _bookEvaluator = bookEvaluator;
+ _workbook = workbook;
+ _sheetIndex = sheetIndex;
+ _rowIndex = srcRowNum;
+ _columnIndex = srcColNum;
+ _tracker = tracker;
+ }
- public EvaluationWorkbook getWorkbook() {
- return _workbook;
- }
+ public EvaluationWorkbook getWorkbook() {
+ return _workbook;
+ }
- public int getRowIndex() {
- return _rowIndex;
- }
+ public int getRowIndex() {
+ return _rowIndex;
+ }
- public int getColumnIndex() {
- return _columnIndex;
- }
+ public int getColumnIndex() {
+ return _columnIndex;
+ }
- SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
- return createExternSheetRefEvaluator(ptg.getExternSheetIndex());
- }
- SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
+ SheetRangeEvaluator createExternSheetRefEvaluator(ExternSheetReferenceToken ptg) {
+ return createExternSheetRefEvaluator(ptg.getExternSheetIndex());
+ }
+ SheetRangeEvaluator createExternSheetRefEvaluator(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
ExternalSheet externalSheet = _workbook.getExternalSheet(firstSheetName, lastSheetName, externalWorkbookNumber);
return createExternSheetRefEvaluator(externalSheet);
}
- SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) {
- ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
+ SheetRangeEvaluator createExternSheetRefEvaluator(int externSheetIndex) {
+ ExternalSheet externalSheet = _workbook.getExternalSheet(externSheetIndex);
return createExternSheetRefEvaluator(externalSheet);
- }
- SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) {
- WorkbookEvaluator targetEvaluator;
- int otherFirstSheetIndex;
- int otherLastSheetIndex = -1;
- if (externalSheet == null || externalSheet.getWorkbookName() == null) {
- // sheet is in same workbook
- targetEvaluator = _bookEvaluator;
- if(externalSheet == null) {
- otherFirstSheetIndex = 0;
- } else {
- otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName());
- }
-
- if (externalSheet instanceof ExternalSheetRange) {
- String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
- otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName);
- }
- } else {
- // look up sheet by name from external workbook
- String workbookName = externalSheet.getWorkbookName();
- try {
- targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
- } catch (WorkbookNotFoundException e) {
- throw new RuntimeException(e.getMessage(), e);
- }
-
- otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
+ }
+ SheetRangeEvaluator createExternSheetRefEvaluator(ExternalSheet externalSheet) {
+ WorkbookEvaluator targetEvaluator;
+ int otherFirstSheetIndex;
+ int otherLastSheetIndex = -1;
+ if (externalSheet == null || externalSheet.getWorkbookName() == null) {
+ // sheet is in same workbook
+ targetEvaluator = _bookEvaluator;
+ if(externalSheet == null) {
+ otherFirstSheetIndex = 0;
+ } else {
+ otherFirstSheetIndex = _workbook.getSheetIndex(externalSheet.getSheetName());
+ }
+
+ if (externalSheet instanceof ExternalSheetRange) {
+ String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
+ otherLastSheetIndex = _workbook.getSheetIndex(lastSheetName);
+ }
+ } else {
+ // look up sheet by name from external workbook
+ String workbookName = externalSheet.getWorkbookName();
+ try {
+ targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
+ } catch (WorkbookNotFoundException e) {
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ otherFirstSheetIndex = targetEvaluator.getSheetIndex(externalSheet.getSheetName());
if (externalSheet instanceof ExternalSheetRange) {
String lastSheetName = ((ExternalSheetRange)externalSheet).getLastSheetName();
otherLastSheetIndex = targetEvaluator.getSheetIndex(lastSheetName);
}
-
- if (otherFirstSheetIndex < 0) {
- throw new RuntimeException("Invalid sheet name '" + externalSheet.getSheetName()
- + "' in bool '" + workbookName + "'.");
- }
- }
-
- if (otherLastSheetIndex == -1) {
- // Reference to just one sheet
- otherLastSheetIndex = otherFirstSheetIndex;
- }
-
- SheetRefEvaluator[] evals = new SheetRefEvaluator[otherLastSheetIndex-otherFirstSheetIndex+1];
- for (int i=0; inull if either workbook or sheet is not found
- */
- private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) {
- WorkbookEvaluator targetEvaluator;
- if (workbookName == null) {
- targetEvaluator = _bookEvaluator;
- } else {
- if (sheetName == null) {
- throw new IllegalArgumentException("sheetName must not be null if workbookName is provided");
- }
- try {
- targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
- } catch (WorkbookNotFoundException e) {
- return null;
- }
- }
- int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName);
- if (otherSheetIndex < 0) {
- return null;
- }
- return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
- }
+ /**
+ * @return null
if either workbook or sheet is not found
+ */
+ private SheetRefEvaluator createExternSheetRefEvaluator(String workbookName, String sheetName) {
+ WorkbookEvaluator targetEvaluator;
+ if (workbookName == null) {
+ targetEvaluator = _bookEvaluator;
+ } else {
+ if (sheetName == null) {
+ throw new IllegalArgumentException("sheetName must not be null if workbookName is provided");
+ }
+ try {
+ targetEvaluator = _bookEvaluator.getOtherWorkbookEvaluator(workbookName);
+ } catch (WorkbookNotFoundException e) {
+ return null;
+ }
+ }
+ int otherSheetIndex = sheetName == null ? _sheetIndex : targetEvaluator.getSheetIndex(sheetName);
+ if (otherSheetIndex < 0) {
+ return null;
+ }
+ return new SheetRefEvaluator(targetEvaluator, _tracker, otherSheetIndex);
+ }
- public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() {
- SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
- return new SheetRangeEvaluator(_sheetIndex, sre);
- }
+ public SheetRangeEvaluator getRefEvaluatorForCurrentSheet() {
+ SheetRefEvaluator sre = new SheetRefEvaluator(_bookEvaluator, _tracker, _sheetIndex);
+ return new SheetRangeEvaluator(_sheetIndex, sre);
+ }
- /**
- * Resolves a cell or area reference dynamically.
- * @param workbookName the name of the workbook containing the reference. If null
- * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks,
- * a {@link CollaboratingWorkbooksEnvironment} must be set up.
- * @param sheetName the name of the sheet containing the reference. May be null
- * (when workbookName is also null) in which case the current workbook and sheet is
- * assumed.
- * @param refStrPart1 the single cell reference or first part of the area reference. Must not
- * be null
.
- * @param refStrPart2 the second part of the area reference. For single cell references this
- * parameter must be null
- * @param isA1Style specifies the format for refStrPart1 and refStrPart2 .
- * Pass true
for 'A1' style and false
for 'R1C1' style.
- * TODO - currently POI only supports 'A1' reference style
- * @return a {@link RefEval} or {@link AreaEval}
- */
- public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
- String refStrPart2, boolean isA1Style) {
- if (!isA1Style) {
- throw new RuntimeException("R1C1 style not supported yet");
- }
- SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName);
- if (se == null) {
- return ErrorEval.REF_INVALID;
- }
- SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se);
-
- // ugly typecast - TODO - make spreadsheet version more easily accessible
- SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion();
+ /**
+ * Resolves a cell or area reference dynamically.
+ * @param workbookName the name of the workbook containing the reference. If null
+ * the current workbook is assumed. Note - to evaluate formulas which use multiple workbooks,
+ * a {@link CollaboratingWorkbooksEnvironment} must be set up.
+ * @param sheetName the name of the sheet containing the reference. May be null
+ * (when workbookName is also null) in which case the current workbook and sheet is
+ * assumed.
+ * @param refStrPart1 the single cell reference or first part of the area reference. Must not
+ * be null
.
+ * @param refStrPart2 the second part of the area reference. For single cell references this
+ * parameter must be null
+ * @param isA1Style specifies the format for refStrPart1 and refStrPart2 .
+ * Pass true
for 'A1' style and false
for 'R1C1' style.
+ * TODO - currently POI only supports 'A1' reference style
+ * @return a {@link RefEval} or {@link AreaEval}
+ */
+ public ValueEval getDynamicReference(String workbookName, String sheetName, String refStrPart1,
+ String refStrPart2, boolean isA1Style) {
+ if (!isA1Style) {
+ throw new RuntimeException("R1C1 style not supported yet");
+ }
+ SheetRefEvaluator se = createExternSheetRefEvaluator(workbookName, sheetName);
+ if (se == null) {
+ return ErrorEval.REF_INVALID;
+ }
+ SheetRangeEvaluator sre = new SheetRangeEvaluator(_sheetIndex, se);
+
+ // ugly typecast - TODO - make spreadsheet version more easily accessible
+ SpreadsheetVersion ssVersion = ((FormulaParsingWorkbook)_workbook).getSpreadsheetVersion();
- NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
- switch (part1refType) {
- case BAD_CELL_OR_NAMED_RANGE:
- return ErrorEval.REF_INVALID;
- case NAMED_RANGE:
+ NameType part1refType = classifyCellReference(refStrPart1, ssVersion);
+ switch (part1refType) {
+ case BAD_CELL_OR_NAMED_RANGE:
+ return ErrorEval.REF_INVALID;
+ case NAMED_RANGE:
EvaluationName nm = ((FormulaParsingWorkbook)_workbook).getName(refStrPart1, _sheetIndex);
if(!nm.isRange()){
throw new RuntimeException("Specified name '" + refStrPart1 + "' is not a range as expected.");
}
return _bookEvaluator.evaluateNameFormula(nm.getNameDefinition(), this);
- }
- if (refStrPart2 == null) {
- // no ':'
- switch (part1refType) {
- case COLUMN:
- case ROW:
- return ErrorEval.REF_INVALID;
- case CELL:
- CellReference cr = new CellReference(refStrPart1);
- return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
- }
- throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
- }
- NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
- switch (part2refType) {
- case BAD_CELL_OR_NAMED_RANGE:
- return ErrorEval.REF_INVALID;
- case NAMED_RANGE:
- throw new RuntimeException("Cannot evaluate '" + refStrPart1
- + "'. Indirect evaluation of defined names not supported yet");
- }
+ }
+ if (refStrPart2 == null) {
+ // no ':'
+ switch (part1refType) {
+ case COLUMN:
+ case ROW:
+ return ErrorEval.REF_INVALID;
+ case CELL:
+ CellReference cr = new CellReference(refStrPart1);
+ return new LazyRefEval(cr.getRow(), cr.getCol(), sre);
+ }
+ throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
+ }
+ NameType part2refType = classifyCellReference(refStrPart1, ssVersion);
+ switch (part2refType) {
+ case BAD_CELL_OR_NAMED_RANGE:
+ return ErrorEval.REF_INVALID;
+ case NAMED_RANGE:
+ throw new RuntimeException("Cannot evaluate '" + refStrPart1
+ + "'. Indirect evaluation of defined names not supported yet");
+ }
- if (part2refType != part1refType) {
- // LHS and RHS of ':' must be compatible
- return ErrorEval.REF_INVALID;
- }
- int firstRow, firstCol, lastRow, lastCol;
- switch (part1refType) {
- case COLUMN:
+ if (part2refType != part1refType) {
+ // LHS and RHS of ':' must be compatible
+ return ErrorEval.REF_INVALID;
+ }
+ int firstRow, firstCol, lastRow, lastCol;
+ switch (part1refType) {
+ case COLUMN:
firstRow =0;
if (part2refType.equals(NameType.COLUMN))
{
@@ -252,7 +252,7 @@ public final class OperationEvaluationContext {
lastCol = parseColRef(refStrPart2);
}
break;
- case ROW:
+ case ROW:
// support of cell range in the form of integer:integer
firstCol = 0;
if (part2refType.equals(NameType.ROW))
@@ -265,61 +265,61 @@ public final class OperationEvaluationContext {
firstRow = parseRowRef(refStrPart1);
lastRow = parseRowRef(refStrPart2);
}
- break;
- case CELL:
- CellReference cr;
- cr = new CellReference(refStrPart1);
- firstRow = cr.getRow();
- firstCol = cr.getCol();
- cr = new CellReference(refStrPart2);
- lastRow = cr.getRow();
- lastCol = cr.getCol();
- break;
- default:
- throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
- }
- return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre);
- }
+ break;
+ case CELL:
+ CellReference cr;
+ cr = new CellReference(refStrPart1);
+ firstRow = cr.getRow();
+ firstCol = cr.getCol();
+ cr = new CellReference(refStrPart2);
+ lastRow = cr.getRow();
+ lastCol = cr.getCol();
+ break;
+ default:
+ throw new IllegalStateException("Unexpected reference classification of '" + refStrPart1 + "'.");
+ }
+ return new LazyAreaEval(firstRow, firstCol, lastRow, lastCol, sre);
+ }
- private static int parseRowRef(String refStrPart) {
- return CellReference.convertColStringToIndex(refStrPart);
- }
+ private static int parseRowRef(String refStrPart) {
+ return CellReference.convertColStringToIndex(refStrPart);
+ }
- private static int parseColRef(String refStrPart) {
- return Integer.parseInt(refStrPart) - 1;
- }
+ private static int parseColRef(String refStrPart) {
+ return Integer.parseInt(refStrPart) - 1;
+ }
- private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
- int len = str.length();
- if (len < 1) {
- return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE;
- }
- return CellReference.classifyCellReference(str, ssVersion);
- }
+ private static NameType classifyCellReference(String str, SpreadsheetVersion ssVersion) {
+ int len = str.length();
+ if (len < 1) {
+ return CellReference.NameType.BAD_CELL_OR_NAMED_RANGE;
+ }
+ return CellReference.classifyCellReference(str, ssVersion);
+ }
- public FreeRefFunction findUserDefinedFunction(String functionName) {
- return _bookEvaluator.findUserDefinedFunction(functionName);
- }
+ public FreeRefFunction findUserDefinedFunction(String functionName) {
+ return _bookEvaluator.findUserDefinedFunction(functionName);
+ }
- public ValueEval getRefEval(int rowIndex, int columnIndex) {
- SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
- return new LazyRefEval(rowIndex, columnIndex, sre);
- }
- public ValueEval getRef3DEval(Ref3DPtg rptg) {
- SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex());
- return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
- }
+ public ValueEval getRefEval(int rowIndex, int columnIndex) {
+ SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
+ return new LazyRefEval(rowIndex, columnIndex, sre);
+ }
+ public ValueEval getRef3DEval(Ref3DPtg rptg) {
+ SheetRangeEvaluator sre = createExternSheetRefEvaluator(rptg.getExternSheetIndex());
+ return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
+ }
public ValueEval getRef3DEval(Ref3DPxg rptg) {
SheetRangeEvaluator sre = createExternSheetRefEvaluator(
rptg.getSheetName(), rptg.getLastSheetName(), rptg.getExternalWorkbookNumber());
return new LazyRefEval(rptg.getRow(), rptg.getColumn(), sre);
}
- public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex,
- int lastRowIndex, int lastColumnIndex) {
- SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
- return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
- }
+ public ValueEval getAreaEval(int firstRowIndex, int firstColumnIndex,
+ int lastRowIndex, int lastColumnIndex) {
+ SheetRangeEvaluator sre = getRefEvaluatorForCurrentSheet();
+ return new LazyAreaEval(firstRowIndex, firstColumnIndex, lastRowIndex, lastColumnIndex, sre);
+ }
public ValueEval getArea3DEval(Area3DPtg aptg) {
SheetRangeEvaluator sre = createExternSheetRefEvaluator(aptg.getExternSheetIndex());
return new LazyAreaEval(aptg.getFirstRow(), aptg.getFirstColumn(),
@@ -348,8 +348,8 @@ public final class OperationEvaluationContext {
);
return getExternalNameXEval(externName, workbookName);
}
- public ValueEval getNameXEval(NameXPxg nameXPxg) {
- ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber());
+ public ValueEval getNameXEval(NameXPxg nameXPxg) {
+ ExternalSheet externSheet = _workbook.getExternalSheet(nameXPxg.getSheetName(), null, nameXPxg.getExternalWorkbookNumber());
if(externSheet == null || externSheet.getWorkbookName() == null) {
// External reference to our own workbook's name
return getLocalNameXEval(nameXPxg);
@@ -363,8 +363,8 @@ public final class OperationEvaluationContext {
nameXPxg.getExternalWorkbookNumber()
);
return getExternalNameXEval(externName, workbookName);
- }
-
+ }
+
private ValueEval getLocalNameXEval(NameXPxg nameXPxg) {
// Look up the sheet, if present
int sIdx = -1;
@@ -383,7 +383,7 @@ public final class OperationEvaluationContext {
return new FunctionNameEval(name);
}
}
- private ValueEval getLocalNameXEval(NameXPtg nameXPtg) {
+ private ValueEval getLocalNameXEval(NameXPtg nameXPtg) {
String name = _workbook.resolveNameXText(nameXPtg);
// Try to parse it as a name
@@ -406,8 +406,11 @@ public final class OperationEvaluationContext {
// Must be an external function
return new FunctionNameEval(name);
}
- }
-
+ }
+ public int getSheetIndex() {
+ return _sheetIndex;
+ }
+
private ValueEval getExternalNameXEval(ExternalName externName, String workbookName) {
try {
// Fetch the workbook this refers to, and the name as defined with that
diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
index f4b30c4f0..7a964912a 100644
--- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
+++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java
@@ -26,20 +26,60 @@ import java.util.TreeSet;
import org.apache.poi.ss.formula.CollaboratingWorkbooksEnvironment.WorkbookNotFoundException;
import org.apache.poi.ss.formula.atp.AnalysisToolPak;
-import org.apache.poi.ss.formula.eval.*;
+import org.apache.poi.ss.formula.eval.BlankEval;
+import org.apache.poi.ss.formula.eval.BoolEval;
+import org.apache.poi.ss.formula.eval.ErrorEval;
+import org.apache.poi.ss.formula.eval.EvaluationException;
+import org.apache.poi.ss.formula.eval.ExternalNameEval;
+import org.apache.poi.ss.formula.eval.FunctionEval;
+import org.apache.poi.ss.formula.eval.FunctionNameEval;
+import org.apache.poi.ss.formula.eval.MissingArgEval;
+import org.apache.poi.ss.formula.eval.NotImplementedException;
+import org.apache.poi.ss.formula.eval.NumberEval;
+import org.apache.poi.ss.formula.eval.OperandResolver;
+import org.apache.poi.ss.formula.eval.StringEval;
+import org.apache.poi.ss.formula.eval.ValueEval;
import org.apache.poi.ss.formula.function.FunctionMetadataRegistry;
import org.apache.poi.ss.formula.functions.Choose;
import org.apache.poi.ss.formula.functions.FreeRefFunction;
import org.apache.poi.ss.formula.functions.Function;
import org.apache.poi.ss.formula.functions.IfFunc;
-import org.apache.poi.ss.formula.ptg.*;
+import org.apache.poi.ss.formula.ptg.Area3DPtg;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.formula.ptg.AreaErrPtg;
+import org.apache.poi.ss.formula.ptg.AreaPtg;
+import org.apache.poi.ss.formula.ptg.AttrPtg;
+import org.apache.poi.ss.formula.ptg.BoolPtg;
+import org.apache.poi.ss.formula.ptg.ControlPtg;
+import org.apache.poi.ss.formula.ptg.DeletedArea3DPtg;
+import org.apache.poi.ss.formula.ptg.DeletedRef3DPtg;
+import org.apache.poi.ss.formula.ptg.ErrPtg;
+import org.apache.poi.ss.formula.ptg.ExpPtg;
+import org.apache.poi.ss.formula.ptg.FuncVarPtg;
+import org.apache.poi.ss.formula.ptg.IntPtg;
+import org.apache.poi.ss.formula.ptg.MemAreaPtg;
+import org.apache.poi.ss.formula.ptg.MemErrPtg;
+import org.apache.poi.ss.formula.ptg.MemFuncPtg;
+import org.apache.poi.ss.formula.ptg.MissingArgPtg;
+import org.apache.poi.ss.formula.ptg.NamePtg;
+import org.apache.poi.ss.formula.ptg.NameXPtg;
+import org.apache.poi.ss.formula.ptg.NameXPxg;
+import org.apache.poi.ss.formula.ptg.NumberPtg;
+import org.apache.poi.ss.formula.ptg.OperationPtg;
+import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.formula.ptg.Ref3DPtg;
+import org.apache.poi.ss.formula.ptg.Ref3DPxg;
+import org.apache.poi.ss.formula.ptg.RefErrorPtg;
+import org.apache.poi.ss.formula.ptg.RefPtg;
+import org.apache.poi.ss.formula.ptg.StringPtg;
+import org.apache.poi.ss.formula.ptg.UnionPtg;
+import org.apache.poi.ss.formula.ptg.UnknownPtg;
import org.apache.poi.ss.formula.udf.AggregatingUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
-
/**
* Evaluates formula cells.
*
@@ -53,39 +93,39 @@ import org.apache.poi.util.POILogger;
* @author Thies Wellpott (debug output enhancements)
*/
public final class WorkbookEvaluator {
-
- private static final POILogger LOG = POILogFactory.getLogger(WorkbookEvaluator.class);
+
+ private static final POILogger LOG = POILogFactory.getLogger(WorkbookEvaluator.class);
private final EvaluationWorkbook _workbook;
- private EvaluationCache _cache;
- /** part of cache entry key (useful when evaluating multiple workbooks) */
- private int _workbookIx;
+ private EvaluationCache _cache;
+ /** part of cache entry key (useful when evaluating multiple workbooks) */
+ private int _workbookIx;
- private final IEvaluationListener _evaluationListener;
- private final Map _sheetIndexesBySheet;
- private final Map _sheetIndexesByName;
- private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
- private final IStabilityClassifier _stabilityClassifier;
- private final AggregatingUDFFinder _udfFinder;
+ private final IEvaluationListener _evaluationListener;
+ private final Map _sheetIndexesBySheet;
+ private final Map _sheetIndexesByName;
+ private CollaboratingWorkbooksEnvironment _collaboratingWorkbookEnvironment;
+ private final IStabilityClassifier _stabilityClassifier;
+ private final AggregatingUDFFinder _udfFinder;
private boolean _ignoreMissingWorkbooks = false;
- /**
- * @param udfFinder pass null
for default (AnalysisToolPak only)
- */
- public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) {
- this (workbook, null, stabilityClassifier, udfFinder);
- }
- /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener,
- IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) {
- _workbook = workbook;
- _evaluationListener = evaluationListener;
- _cache = new EvaluationCache(evaluationListener);
- _sheetIndexesBySheet = new IdentityHashMap();
- _sheetIndexesByName = new IdentityHashMap();
- _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
- _workbookIx = 0;
- _stabilityClassifier = stabilityClassifier;
+ /**
+ * @param udfFinder pass null
for default (AnalysisToolPak only)
+ */
+ public WorkbookEvaluator(EvaluationWorkbook workbook, IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) {
+ this (workbook, null, stabilityClassifier, udfFinder);
+ }
+ /* package */ WorkbookEvaluator(EvaluationWorkbook workbook, IEvaluationListener evaluationListener,
+ IStabilityClassifier stabilityClassifier, UDFFinder udfFinder) {
+ _workbook = workbook;
+ _evaluationListener = evaluationListener;
+ _cache = new EvaluationCache(evaluationListener);
+ _sheetIndexesBySheet = new IdentityHashMap();
+ _sheetIndexesByName = new IdentityHashMap();
+ _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
+ _workbookIx = 0;
+ _stabilityClassifier = stabilityClassifier;
AggregatingUDFFinder defaultToolkit = // workbook can be null in unit tests
workbook == null ? null : (AggregatingUDFFinder)workbook.getUDFFinder();
@@ -93,492 +133,492 @@ public final class WorkbookEvaluator {
defaultToolkit.add(udfFinder);
}
_udfFinder = defaultToolkit;
- }
+ }
- /**
- * also for debug use. Used in toString methods
- */
- /* package */ String getSheetName(int sheetIndex) {
- return _workbook.getSheetName(sheetIndex);
- }
+ /**
+ * also for debug use. Used in toString methods
+ */
+ /* package */ String getSheetName(int sheetIndex) {
+ return _workbook.getSheetName(sheetIndex);
+ }
- /* package */ EvaluationSheet getSheet(int sheetIndex) {
- return _workbook.getSheet(sheetIndex);
- }
-
- /* package */ EvaluationWorkbook getWorkbook() {
- return _workbook;
- }
+ /* package */ EvaluationSheet getSheet(int sheetIndex) {
+ return _workbook.getSheet(sheetIndex);
+ }
+
+ /* package */ EvaluationWorkbook getWorkbook() {
+ return _workbook;
+ }
- /* package */ EvaluationName getName(String name, int sheetIndex) {
- EvaluationName evalName = _workbook.getName(name, sheetIndex);
- return evalName;
- }
+ /* package */ EvaluationName getName(String name, int sheetIndex) {
+ EvaluationName evalName = _workbook.getName(name, sheetIndex);
+ return evalName;
+ }
- private static boolean isDebugLogEnabled() {
- return LOG.check(POILogger.DEBUG);
- }
- private static boolean isInfoLogEnabled() {
- return LOG.check(POILogger.INFO);
- }
- private static void logDebug(String s) {
- if (isDebugLogEnabled()) {
- LOG.log(POILogger.DEBUG, s);
- }
- }
- private static void logInfo(String s) {
- if (isInfoLogEnabled()) {
- LOG.log(POILogger.INFO, s);
- }
- }
- /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache, int workbookIx) {
- _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
- _cache = cache;
- _workbookIx = workbookIx;
- }
- /* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
- return _collaboratingWorkbookEnvironment;
- }
+ private static boolean isDebugLogEnabled() {
+ return LOG.check(POILogger.DEBUG);
+ }
+ private static boolean isInfoLogEnabled() {
+ return LOG.check(POILogger.INFO);
+ }
+ private static void logDebug(String s) {
+ if (isDebugLogEnabled()) {
+ LOG.log(POILogger.DEBUG, s);
+ }
+ }
+ private static void logInfo(String s) {
+ if (isInfoLogEnabled()) {
+ LOG.log(POILogger.INFO, s);
+ }
+ }
+ /* package */ void attachToEnvironment(CollaboratingWorkbooksEnvironment collaboratingWorkbooksEnvironment, EvaluationCache cache, int workbookIx) {
+ _collaboratingWorkbookEnvironment = collaboratingWorkbooksEnvironment;
+ _cache = cache;
+ _workbookIx = workbookIx;
+ }
+ /* package */ CollaboratingWorkbooksEnvironment getEnvironment() {
+ return _collaboratingWorkbookEnvironment;
+ }
- /**
- * Discards the current workbook environment and attaches to the default 'empty' environment.
- * Also resets evaluation cache.
- */
- /* package */ void detachFromEnvironment() {
- _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
- _cache = new EvaluationCache(_evaluationListener);
- _workbookIx = 0;
- }
- /**
- * @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment}
- */
- /* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
- return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName);
- }
+ /**
+ * Discards the current workbook environment and attaches to the default 'empty' environment.
+ * Also resets evaluation cache.
+ */
+ /* package */ void detachFromEnvironment() {
+ _collaboratingWorkbookEnvironment = CollaboratingWorkbooksEnvironment.EMPTY;
+ _cache = new EvaluationCache(_evaluationListener);
+ _workbookIx = 0;
+ }
+ /**
+ * @return the evaluator for another workbook which is part of the same {@link CollaboratingWorkbooksEnvironment}
+ */
+ /* package */ WorkbookEvaluator getOtherWorkbookEvaluator(String workbookName) throws WorkbookNotFoundException {
+ return _collaboratingWorkbookEnvironment.getWorkbookEvaluator(workbookName);
+ }
- /* package */ IEvaluationListener getEvaluationListener() {
- return _evaluationListener;
- }
+ /* package */ IEvaluationListener getEvaluationListener() {
+ return _evaluationListener;
+ }
- /**
- * Should be called whenever there are changes to input cells in the evaluated workbook.
- * Failure to call this method after changing cell values will cause incorrect behaviour
- * of the evaluate~ methods of this class
- */
- public void clearAllCachedResultValues() {
- _cache.clear();
- _sheetIndexesBySheet.clear();
- }
+ /**
+ * Should be called whenever there are changes to input cells in the evaluated workbook.
+ * Failure to call this method after changing cell values will cause incorrect behaviour
+ * of the evaluate~ methods of this class
+ */
+ public void clearAllCachedResultValues() {
+ _cache.clear();
+ _sheetIndexesBySheet.clear();
+ }
- /**
- * Should be called to tell the cell value cache that the specified (value or formula) cell
- * has changed.
- */
- public void notifyUpdateCell(EvaluationCell cell) {
- int sheetIndex = getSheetIndex(cell.getSheet());
- _cache.notifyUpdateCell(_workbookIx, sheetIndex, cell);
- }
- /**
- * Should be called to tell the cell value cache that the specified cell has just been
- * deleted.
- */
- public void notifyDeleteCell(EvaluationCell cell) {
- int sheetIndex = getSheetIndex(cell.getSheet());
- _cache.notifyDeleteCell(_workbookIx, sheetIndex, cell);
- }
-
- private int getSheetIndex(EvaluationSheet sheet) {
- Integer result = _sheetIndexesBySheet.get(sheet);
- if (result == null) {
- int sheetIndex = _workbook.getSheetIndex(sheet);
- if (sheetIndex < 0) {
- throw new RuntimeException("Specified sheet from a different book");
- }
- result = Integer.valueOf(sheetIndex);
- _sheetIndexesBySheet.put(sheet, result);
- }
- return result.intValue();
- }
+ /**
+ * Should be called to tell the cell value cache that the specified (value or formula) cell
+ * has changed.
+ */
+ public void notifyUpdateCell(EvaluationCell cell) {
+ int sheetIndex = getSheetIndex(cell.getSheet());
+ _cache.notifyUpdateCell(_workbookIx, sheetIndex, cell);
+ }
+ /**
+ * Should be called to tell the cell value cache that the specified cell has just been
+ * deleted.
+ */
+ public void notifyDeleteCell(EvaluationCell cell) {
+ int sheetIndex = getSheetIndex(cell.getSheet());
+ _cache.notifyDeleteCell(_workbookIx, sheetIndex, cell);
+ }
+
+ private int getSheetIndex(EvaluationSheet sheet) {
+ Integer result = _sheetIndexesBySheet.get(sheet);
+ if (result == null) {
+ int sheetIndex = _workbook.getSheetIndex(sheet);
+ if (sheetIndex < 0) {
+ throw new RuntimeException("Specified sheet from a different book");
+ }
+ result = Integer.valueOf(sheetIndex);
+ _sheetIndexesBySheet.put(sheet, result);
+ }
+ return result.intValue();
+ }
- public ValueEval evaluate(EvaluationCell srcCell) {
- int sheetIndex = getSheetIndex(srcCell.getSheet());
- return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache));
- }
+ public ValueEval evaluate(EvaluationCell srcCell) {
+ int sheetIndex = getSheetIndex(srcCell.getSheet());
+ return evaluateAny(srcCell, sheetIndex, srcCell.getRowIndex(), srcCell.getColumnIndex(), new EvaluationTracker(_cache));
+ }
- /**
- * Case-insensitive.
- * @return -1 if sheet with specified name does not exist
- */
- /* package */ int getSheetIndex(String sheetName) {
- Integer result = _sheetIndexesByName.get(sheetName);
- if (result == null) {
- int sheetIndex = _workbook.getSheetIndex(sheetName);
- if (sheetIndex < 0) {
- return -1;
- }
- result = Integer.valueOf(sheetIndex);
- _sheetIndexesByName.put(sheetName, result);
- }
- return result.intValue();
- }
-
- /* package */ int getSheetIndexByExternIndex(int externSheetIndex) {
- return _workbook.convertFromExternSheetIndex(externSheetIndex);
- }
+ /**
+ * Case-insensitive.
+ * @return -1 if sheet with specified name does not exist
+ */
+ /* package */ int getSheetIndex(String sheetName) {
+ Integer result = _sheetIndexesByName.get(sheetName);
+ if (result == null) {
+ int sheetIndex = _workbook.getSheetIndex(sheetName);
+ if (sheetIndex < 0) {
+ return -1;
+ }
+ result = Integer.valueOf(sheetIndex);
+ _sheetIndexesByName.put(sheetName, result);
+ }
+ return result.intValue();
+ }
+
+ /* package */ int getSheetIndexByExternIndex(int externSheetIndex) {
+ return _workbook.convertFromExternSheetIndex(externSheetIndex);
+ }
- /**
- * @return never null
, never {@link BlankEval}
- */
- private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
- int rowIndex, int columnIndex, EvaluationTracker tracker) {
+ /**
+ * @return never null
, never {@link BlankEval}
+ */
+ private ValueEval evaluateAny(EvaluationCell srcCell, int sheetIndex,
+ int rowIndex, int columnIndex, EvaluationTracker tracker) {
- // avoid tracking dependencies to cells that have constant definition
- boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true
- : !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex);
- if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
- ValueEval result = getValueFromNonFormulaCell(srcCell);
- if (shouldCellDependencyBeRecorded) {
- tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
- }
- return result;
- }
+ // avoid tracking dependencies to cells that have constant definition
+ boolean shouldCellDependencyBeRecorded = _stabilityClassifier == null ? true
+ : !_stabilityClassifier.isCellFinal(sheetIndex, rowIndex, columnIndex);
+ if (srcCell == null || srcCell.getCellType() != Cell.CELL_TYPE_FORMULA) {
+ ValueEval result = getValueFromNonFormulaCell(srcCell);
+ if (shouldCellDependencyBeRecorded) {
+ tracker.acceptPlainValueDependency(_workbookIx, sheetIndex, rowIndex, columnIndex, result);
+ }
+ return result;
+ }
- FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
- if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) {
- tracker.acceptFormulaDependency(cce);
- }
- IEvaluationListener evalListener = _evaluationListener;
- ValueEval result;
- if (cce.getValue() == null) {
- if (!tracker.startEvaluate(cce)) {
- return ErrorEval.CIRCULAR_REF_ERROR;
- }
- OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker);
+ FormulaCellCacheEntry cce = _cache.getOrCreateFormulaCellEntry(srcCell);
+ if (shouldCellDependencyBeRecorded || cce.isInputSensitive()) {
+ tracker.acceptFormulaDependency(cce);
+ }
+ IEvaluationListener evalListener = _evaluationListener;
+ ValueEval result;
+ if (cce.getValue() == null) {
+ if (!tracker.startEvaluate(cce)) {
+ return ErrorEval.CIRCULAR_REF_ERROR;
+ }
+ OperationEvaluationContext ec = new OperationEvaluationContext(this, _workbook, sheetIndex, rowIndex, columnIndex, tracker);
- try {
+ try {
- Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
- if (evalListener == null) {
- result = evaluateFormula(ec, ptgs);
- } else {
- evalListener.onStartEvaluate(srcCell, cce);
- result = evaluateFormula(ec, ptgs);
- evalListener.onEndEvaluate(cce, result);
- }
+ Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
+ if (evalListener == null) {
+ result = evaluateFormula(ec, ptgs);
+ } else {
+ evalListener.onStartEvaluate(srcCell, cce);
+ result = evaluateFormula(ec, ptgs);
+ evalListener.onEndEvaluate(cce, result);
+ }
- tracker.updateCacheResult(result);
- }
- catch (NotImplementedException e) {
- throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
- } catch (RuntimeException re) {
- if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
- logInfo(re.getCause().getMessage() + " - Continuing with cached value!");
- switch(srcCell.getCachedFormulaResultType()) {
- case Cell.CELL_TYPE_NUMERIC:
- result = new NumberEval(srcCell.getNumericCellValue());
- break;
- case Cell.CELL_TYPE_STRING:
- result = new StringEval(srcCell.getStringCellValue());
- break;
- case Cell.CELL_TYPE_BLANK:
- result = BlankEval.instance;
- break;
- case Cell.CELL_TYPE_BOOLEAN:
- result = BoolEval.valueOf(srcCell.getBooleanCellValue());
- break;
- case Cell.CELL_TYPE_ERROR:
- result = ErrorEval.valueOf(srcCell.getErrorCellValue());
- break;
- case Cell.CELL_TYPE_FORMULA:
- default:
- throw new RuntimeException("Unexpected cell type '" + srcCell.getCellType()+"' found!");
- }
- } else {
- throw re;
- }
- } finally {
- tracker.endEvaluate(cce);
- }
- } else {
- if(evalListener != null) {
- evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, cce.getValue());
- }
- return cce.getValue();
- }
- if (isDebugLogEnabled()) {
- String sheetName = getSheetName(sheetIndex);
- CellReference cr = new CellReference(rowIndex, columnIndex);
- logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
- }
- // Usually (result === cce.getValue())
- // But sometimes: (result==ErrorEval.CIRCULAR_REF_ERROR, cce.getValue()==null)
- // When circular references are detected, the cache entry is only updated for
- // the top evaluation frame
- return result;
- }
+ tracker.updateCacheResult(result);
+ }
+ catch (NotImplementedException e) {
+ throw addExceptionInfo(e, sheetIndex, rowIndex, columnIndex);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof WorkbookNotFoundException && _ignoreMissingWorkbooks) {
+ logInfo(re.getCause().getMessage() + " - Continuing with cached value!");
+ switch(srcCell.getCachedFormulaResultType()) {
+ case Cell.CELL_TYPE_NUMERIC:
+ result = new NumberEval(srcCell.getNumericCellValue());
+ break;
+ case Cell.CELL_TYPE_STRING:
+ result = new StringEval(srcCell.getStringCellValue());
+ break;
+ case Cell.CELL_TYPE_BLANK:
+ result = BlankEval.instance;
+ break;
+ case Cell.CELL_TYPE_BOOLEAN:
+ result = BoolEval.valueOf(srcCell.getBooleanCellValue());
+ break;
+ case Cell.CELL_TYPE_ERROR:
+ result = ErrorEval.valueOf(srcCell.getErrorCellValue());
+ break;
+ case Cell.CELL_TYPE_FORMULA:
+ default:
+ throw new RuntimeException("Unexpected cell type '" + srcCell.getCellType()+"' found!");
+ }
+ } else {
+ throw re;
+ }
+ } finally {
+ tracker.endEvaluate(cce);
+ }
+ } else {
+ if(evalListener != null) {
+ evalListener.onCacheHit(sheetIndex, rowIndex, columnIndex, cce.getValue());
+ }
+ return cce.getValue();
+ }
+ if (isDebugLogEnabled()) {
+ String sheetName = getSheetName(sheetIndex);
+ CellReference cr = new CellReference(rowIndex, columnIndex);
+ logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
+ }
+ // Usually (result === cce.getValue())
+ // But sometimes: (result==ErrorEval.CIRCULAR_REF_ERROR, cce.getValue()==null)
+ // When circular references are detected, the cache entry is only updated for
+ // the top evaluation frame
+ return result;
+ }
- /**
- * Adds the current cell reference to the exception for easier debugging.
- * Would be nice to get the formula text as well, but that seems to require
- * too much digging around and casting to get the FormulaRenderingWorkbook.
- */
- private NotImplementedException addExceptionInfo(NotImplementedException inner, int sheetIndex, int rowIndex, int columnIndex) {
+ /**
+ * Adds the current cell reference to the exception for easier debugging.
+ * Would be nice to get the formula text as well, but that seems to require
+ * too much digging around and casting to get the FormulaRenderingWorkbook.
+ */
+ private NotImplementedException addExceptionInfo(NotImplementedException inner, int sheetIndex, int rowIndex, int columnIndex) {
- try {
- String sheetName = _workbook.getSheetName(sheetIndex);
- CellReference cr = new CellReference(sheetName, rowIndex, columnIndex, false, false);
- String msg = "Error evaluating cell " + cr.formatAsString();
- return new NotImplementedException(msg, inner);
- } catch (Exception e) {
- // avoid bombing out during exception handling
- LOG.log(POILogger.ERROR, "Can't add exception info", e);
- return inner; // preserve original exception
- }
- }
- /**
- * Gets the value from a non-formula cell.
- * @param cell may be null
- * @return {@link BlankEval} if cell is null
or blank, never null
- */
- /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) {
- if (cell == null) {
- return BlankEval.instance;
- }
- int cellType = cell.getCellType();
- switch (cellType) {
- case Cell.CELL_TYPE_NUMERIC:
- return new NumberEval(cell.getNumericCellValue());
- case Cell.CELL_TYPE_STRING:
- return new StringEval(cell.getStringCellValue());
- case Cell.CELL_TYPE_BOOLEAN:
- return BoolEval.valueOf(cell.getBooleanCellValue());
- case Cell.CELL_TYPE_BLANK:
- return BlankEval.instance;
- case Cell.CELL_TYPE_ERROR:
- return ErrorEval.valueOf(cell.getErrorCellValue());
- }
- throw new RuntimeException("Unexpected cell type (" + cellType + ")");
- }
+ try {
+ String sheetName = _workbook.getSheetName(sheetIndex);
+ CellReference cr = new CellReference(sheetName, rowIndex, columnIndex, false, false);
+ String msg = "Error evaluating cell " + cr.formatAsString();
+ return new NotImplementedException(msg, inner);
+ } catch (Exception e) {
+ // avoid bombing out during exception handling
+ LOG.log(POILogger.ERROR, "Can't add exception info", e);
+ return inner; // preserve original exception
+ }
+ }
+ /**
+ * Gets the value from a non-formula cell.
+ * @param cell may be null
+ * @return {@link BlankEval} if cell is null
or blank, never null
+ */
+ /* package */ static ValueEval getValueFromNonFormulaCell(EvaluationCell cell) {
+ if (cell == null) {
+ return BlankEval.instance;
+ }
+ int cellType = cell.getCellType();
+ switch (cellType) {
+ case Cell.CELL_TYPE_NUMERIC:
+ return new NumberEval(cell.getNumericCellValue());
+ case Cell.CELL_TYPE_STRING:
+ return new StringEval(cell.getStringCellValue());
+ case Cell.CELL_TYPE_BOOLEAN:
+ return BoolEval.valueOf(cell.getBooleanCellValue());
+ case Cell.CELL_TYPE_BLANK:
+ return BlankEval.instance;
+ case Cell.CELL_TYPE_ERROR:
+ return ErrorEval.valueOf(cell.getErrorCellValue());
+ }
+ throw new RuntimeException("Unexpected cell type (" + cellType + ")");
+ }
/**
* whether print detailed messages about the next formula evaluation
*/
- private boolean dbgEvaluationOutputForNextEval = false;
+ private boolean dbgEvaluationOutputForNextEval = false;
- // special logger for formula evaluation output (because of possibly very large output)
- private final POILogger EVAL_LOG = POILogFactory.getLogger("POI.FormulaEval");
- // current indent level for evalution; negative value for no output
- private int dbgEvaluationOutputIndent = -1;
+ // special logger for formula evaluation output (because of possibly very large output)
+ private final POILogger EVAL_LOG = POILogFactory.getLogger("POI.FormulaEval");
+ // current indent level for evalution; negative value for no output
+ private int dbgEvaluationOutputIndent = -1;
- // visibility raised for testing
- /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) {
+ // visibility raised for testing
+ /* package */ ValueEval evaluateFormula(OperationEvaluationContext ec, Ptg[] ptgs) {
- String dbgIndentStr = ""; // always init. to non-null just for defensive avoiding NPE
- if (dbgEvaluationOutputForNextEval) {
- // first evaluation call when ouput is desired, so iit. this evaluator instance
- dbgEvaluationOutputIndent = 1;
- dbgEvaluationOutputForNextEval = false;
- }
- if (dbgEvaluationOutputIndent > 0) {
- // init. indent string to needed spaces (create as substring vom very long space-only string;
- // limit indendation for deep recursions)
- dbgIndentStr = " ";
- dbgIndentStr = dbgIndentStr.substring(0, Math.min(dbgIndentStr.length(), dbgEvaluationOutputIndent*2));
- EVAL_LOG.log(POILogger.WARN, dbgIndentStr
- + "- evaluateFormula('" + ec.getRefEvaluatorForCurrentSheet().getSheetNameRange()
- + "'/" + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
- + "): " + Arrays.toString(ptgs).replaceAll("\\Qorg.apache.poi.ss.formula.ptg.\\E", ""));
- dbgEvaluationOutputIndent++;
- }
+ String dbgIndentStr = ""; // always init. to non-null just for defensive avoiding NPE
+ if (dbgEvaluationOutputForNextEval) {
+ // first evaluation call when ouput is desired, so iit. this evaluator instance
+ dbgEvaluationOutputIndent = 1;
+ dbgEvaluationOutputForNextEval = false;
+ }
+ if (dbgEvaluationOutputIndent > 0) {
+ // init. indent string to needed spaces (create as substring vom very long space-only string;
+ // limit indendation for deep recursions)
+ dbgIndentStr = " ";
+ dbgIndentStr = dbgIndentStr.substring(0, Math.min(dbgIndentStr.length(), dbgEvaluationOutputIndent*2));
+ EVAL_LOG.log(POILogger.WARN, dbgIndentStr
+ + "- evaluateFormula('" + ec.getRefEvaluatorForCurrentSheet().getSheetNameRange()
+ + "'/" + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
+ + "): " + Arrays.toString(ptgs).replaceAll("\\Qorg.apache.poi.ss.formula.ptg.\\E", ""));
+ dbgEvaluationOutputIndent++;
+ }
- Stack stack = new Stack();
- for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
+ Stack stack = new Stack();
+ for (int i = 0, iSize = ptgs.length; i < iSize; i++) {
- // since we don't know how to handle these yet :(
- Ptg ptg = ptgs[i];
- if (dbgEvaluationOutputIndent > 0) {
- EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg);
- }
- if (ptg instanceof AttrPtg) {
- AttrPtg attrPtg = (AttrPtg) ptg;
- if (attrPtg.isSum()) {
- // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator
- // expects the equivalent function token
- ptg = FuncVarPtg.SUM;
- }
- if (attrPtg.isOptimizedChoose()) {
- ValueEval arg0 = stack.pop();
- int[] jumpTable = attrPtg.getJumpTable();
- int dist;
- int nChoices = jumpTable.length;
- try {
- int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
- if (switchIndex<1 || switchIndex > nChoices) {
- stack.push(ErrorEval.VALUE_INVALID);
- dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
- } else {
- dist = jumpTable[switchIndex-1];
- }
- } catch (EvaluationException e) {
- stack.push(e.getErrorEval());
- dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
- }
- // Encoded dist for tAttrChoose includes size of jump table, but
- // countTokensToBeSkipped() does not (it counts whole tokens).
- dist -= nChoices*2+2; // subtract jump table size
- i+= countTokensToBeSkipped(ptgs, i, dist);
- continue;
- }
- if (attrPtg.isOptimizedIf()) {
- ValueEval arg0 = stack.pop();
- boolean evaluatedPredicate;
- try {
- evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
- } catch (EvaluationException e) {
- stack.push(e.getErrorEval());
- int dist = attrPtg.getData();
- i+= countTokensToBeSkipped(ptgs, i, dist);
- attrPtg = (AttrPtg) ptgs[i];
- dist = attrPtg.getData()+1;
- i+= countTokensToBeSkipped(ptgs, i, dist);
- continue;
- }
- if (evaluatedPredicate) {
- // nothing to skip - true param follows
- } else {
- int dist = attrPtg.getData();
- i+= countTokensToBeSkipped(ptgs, i, dist);
- Ptg nextPtg = ptgs[i+1];
- if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg &&
- // in order to verify that there is no third param, we need to check
- // if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()!
- ((FuncVarPtg)nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) {
- // this is an if statement without a false param (as opposed to MissingArgPtg as the false param)
- i++;
- stack.push(BoolEval.FALSE);
- }
- }
- continue;
- }
- if (attrPtg.isSkip()) {
- int dist = attrPtg.getData()+1;
- i+= countTokensToBeSkipped(ptgs, i, dist);
- if (stack.peek() == MissingArgEval.instance) {
- stack.pop();
- stack.push(BlankEval.instance);
- }
- continue;
- }
- }
- if (ptg instanceof ControlPtg) {
- // skip Parentheses, Attr, etc
- continue;
- }
- if (ptg instanceof MemFuncPtg || ptg instanceof MemAreaPtg) {
- // can ignore, rest of tokens for this expression are in OK RPN order
- continue;
- }
- if (ptg instanceof MemErrPtg) {
- continue;
- }
+ // since we don't know how to handle these yet :(
+ Ptg ptg = ptgs[i];
+ if (dbgEvaluationOutputIndent > 0) {
+ EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " * ptg " + i + ": " + ptg);
+ }
+ if (ptg instanceof AttrPtg) {
+ AttrPtg attrPtg = (AttrPtg) ptg;
+ if (attrPtg.isSum()) {
+ // Excel prefers to encode 'SUM()' as a tAttr token, but this evaluator
+ // expects the equivalent function token
+ ptg = FuncVarPtg.SUM;
+ }
+ if (attrPtg.isOptimizedChoose()) {
+ ValueEval arg0 = stack.pop();
+ int[] jumpTable = attrPtg.getJumpTable();
+ int dist;
+ int nChoices = jumpTable.length;
+ try {
+ int switchIndex = Choose.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
+ if (switchIndex<1 || switchIndex > nChoices) {
+ stack.push(ErrorEval.VALUE_INVALID);
+ dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
+ } else {
+ dist = jumpTable[switchIndex-1];
+ }
+ } catch (EvaluationException e) {
+ stack.push(e.getErrorEval());
+ dist = attrPtg.getChooseFuncOffset() + 4; // +4 for tFuncFar(CHOOSE)
+ }
+ // Encoded dist for tAttrChoose includes size of jump table, but
+ // countTokensToBeSkipped() does not (it counts whole tokens).
+ dist -= nChoices*2+2; // subtract jump table size
+ i+= countTokensToBeSkipped(ptgs, i, dist);
+ continue;
+ }
+ if (attrPtg.isOptimizedIf()) {
+ ValueEval arg0 = stack.pop();
+ boolean evaluatedPredicate;
+ try {
+ evaluatedPredicate = IfFunc.evaluateFirstArg(arg0, ec.getRowIndex(), ec.getColumnIndex());
+ } catch (EvaluationException e) {
+ stack.push(e.getErrorEval());
+ int dist = attrPtg.getData();
+ i+= countTokensToBeSkipped(ptgs, i, dist);
+ attrPtg = (AttrPtg) ptgs[i];
+ dist = attrPtg.getData()+1;
+ i+= countTokensToBeSkipped(ptgs, i, dist);
+ continue;
+ }
+ if (evaluatedPredicate) {
+ // nothing to skip - true param follows
+ } else {
+ int dist = attrPtg.getData();
+ i+= countTokensToBeSkipped(ptgs, i, dist);
+ Ptg nextPtg = ptgs[i+1];
+ if (ptgs[i] instanceof AttrPtg && nextPtg instanceof FuncVarPtg &&
+ // in order to verify that there is no third param, we need to check
+ // if we really have the IF next or some other FuncVarPtg as third param, e.g. ROW()/COLUMN()!
+ ((FuncVarPtg)nextPtg).getFunctionIndex() == FunctionMetadataRegistry.FUNCTION_INDEX_IF) {
+ // this is an if statement without a false param (as opposed to MissingArgPtg as the false param)
+ i++;
+ stack.push(BoolEval.FALSE);
+ }
+ }
+ continue;
+ }
+ if (attrPtg.isSkip()) {
+ int dist = attrPtg.getData()+1;
+ i+= countTokensToBeSkipped(ptgs, i, dist);
+ if (stack.peek() == MissingArgEval.instance) {
+ stack.pop();
+ stack.push(BlankEval.instance);
+ }
+ continue;
+ }
+ }
+ if (ptg instanceof ControlPtg) {
+ // skip Parentheses, Attr, etc
+ continue;
+ }
+ if (ptg instanceof MemFuncPtg || ptg instanceof MemAreaPtg) {
+ // can ignore, rest of tokens for this expression are in OK RPN order
+ continue;
+ }
+ if (ptg instanceof MemErrPtg) {
+ continue;
+ }
- ValueEval opResult;
- if (ptg instanceof OperationPtg) {
- OperationPtg optg = (OperationPtg) ptg;
+ ValueEval opResult;
+ if (ptg instanceof OperationPtg) {
+ OperationPtg optg = (OperationPtg) ptg;
- if (optg instanceof UnionPtg) { continue; }
+ if (optg instanceof UnionPtg) { continue; }
- int numops = optg.getNumberOfOperands();
- ValueEval[] ops = new ValueEval[numops];
+ int numops = optg.getNumberOfOperands();
+ ValueEval[] ops = new ValueEval[numops];
- // storing the ops in reverse order since they are popping
- for (int j = numops - 1; j >= 0; j--) {
- ValueEval p = stack.pop();
- ops[j] = p;
- }
-// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
- opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec);
- } else {
- opResult = getEvalForPtg(ptg, ec);
- }
- if (opResult == null) {
- throw new RuntimeException("Evaluation result must not be null");
- }
-// logDebug("push " + opResult);
- stack.push(opResult);
- if (dbgEvaluationOutputIndent > 0) {
- EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " = " + opResult);
- }
- }
+ // storing the ops in reverse order since they are popping
+ for (int j = numops - 1; j >= 0; j--) {
+ ValueEval p = stack.pop();
+ ops[j] = p;
+ }
+// logDebug("invoke " + operation + " (nAgs=" + numops + ")");
+ opResult = OperationEvaluatorFactory.evaluate(optg, ops, ec);
+ } else {
+ opResult = getEvalForPtg(ptg, ec);
+ }
+ if (opResult == null) {
+ throw new RuntimeException("Evaluation result must not be null");
+ }
+// logDebug("push " + opResult);
+ stack.push(opResult);
+ if (dbgEvaluationOutputIndent > 0) {
+ EVAL_LOG.log(POILogger.INFO, dbgIndentStr + " = " + opResult);
+ }
+ }
- ValueEval value = stack.pop();
- if (!stack.isEmpty()) {
- throw new IllegalStateException("evaluation stack not empty");
- }
- ValueEval result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex());
- if (dbgEvaluationOutputIndent > 0) {
- EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of "
- + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
- + ": " + result);
- dbgEvaluationOutputIndent--;
- if (dbgEvaluationOutputIndent == 1) {
- // this evaluation is done, reset indent to stop logging
- dbgEvaluationOutputIndent = -1;
- }
- } // if
- return result;
+ ValueEval value = stack.pop();
+ if (!stack.isEmpty()) {
+ throw new IllegalStateException("evaluation stack not empty");
+ }
+ ValueEval result = dereferenceResult(value, ec.getRowIndex(), ec.getColumnIndex());
+ if (dbgEvaluationOutputIndent > 0) {
+ EVAL_LOG.log(POILogger.INFO, dbgIndentStr + "finshed eval of "
+ + new CellReference(ec.getRowIndex(), ec.getColumnIndex()).formatAsString()
+ + ": " + result);
+ dbgEvaluationOutputIndent--;
+ if (dbgEvaluationOutputIndent == 1) {
+ // this evaluation is done, reset indent to stop logging
+ dbgEvaluationOutputIndent = -1;
+ }
+ } // if
+ return result;
- }
+ }
- /**
- * Calculates the number of tokens that the evaluator should skip upon reaching a tAttrSkip.
- *
- * @return the number of tokens (starting from startIndex+1 ) that need to be skipped
- * to achieve the specified distInBytes skip distance.
- */
- private static int countTokensToBeSkipped(Ptg[] ptgs, int startIndex, int distInBytes) {
- int remBytes = distInBytes;
- int index = startIndex;
- while (remBytes != 0) {
- index++;
- remBytes -= ptgs[index].getSize();
- if (remBytes < 0) {
- throw new RuntimeException("Bad skip distance (wrong token size calculation).");
- }
- if (index >= ptgs.length) {
- throw new RuntimeException("Skip distance too far (ran out of formula tokens).");
- }
- }
- return index-startIndex;
- }
+ /**
+ * Calculates the number of tokens that the evaluator should skip upon reaching a tAttrSkip.
+ *
+ * @return the number of tokens (starting from startIndex+1 ) that need to be skipped
+ * to achieve the specified distInBytes skip distance.
+ */
+ private static int countTokensToBeSkipped(Ptg[] ptgs, int startIndex, int distInBytes) {
+ int remBytes = distInBytes;
+ int index = startIndex;
+ while (remBytes != 0) {
+ index++;
+ remBytes -= ptgs[index].getSize();
+ if (remBytes < 0) {
+ throw new RuntimeException("Bad skip distance (wrong token size calculation).");
+ }
+ if (index >= ptgs.length) {
+ throw new RuntimeException("Skip distance too far (ran out of formula tokens).");
+ }
+ }
+ return index-startIndex;
+ }
- /**
- * Dereferences a single value from any AreaEval or RefEval evaluation
- * result. If the supplied evaluationResult is just a plain value, it is
- * returned as-is.
- *
- * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or
- * {@link ErrorEval}. Never null
. {@link BlankEval} is
- * converted to {@link NumberEval#ZERO}
- */
- public static ValueEval dereferenceResult(ValueEval evaluationResult, int srcRowNum, int srcColNum) {
- ValueEval value;
- try {
- value = OperandResolver.getSingleValue(evaluationResult, srcRowNum, srcColNum);
- } catch (EvaluationException e) {
- return e.getErrorEval();
- }
- if (value == BlankEval.instance) {
- // Note Excel behaviour here. A blank final final value is converted to zero.
- return NumberEval.ZERO;
- // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
- // blank, the actual value is empty string. This can be verified with ISBLANK().
- }
- return value;
- }
+ /**
+ * Dereferences a single value from any AreaEval or RefEval evaluation
+ * result. If the supplied evaluationResult is just a plain value, it is
+ * returned as-is.
+ *
+ * @return a {@link NumberEval}, {@link StringEval}, {@link BoolEval}, or
+ * {@link ErrorEval}. Never null
. {@link BlankEval} is
+ * converted to {@link NumberEval#ZERO}
+ */
+ public static ValueEval dereferenceResult(ValueEval evaluationResult, int srcRowNum, int srcColNum) {
+ ValueEval value;
+ try {
+ value = OperandResolver.getSingleValue(evaluationResult, srcRowNum, srcColNum);
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
+ if (value == BlankEval.instance) {
+ // Note Excel behaviour here. A blank final final value is converted to zero.
+ return NumberEval.ZERO;
+ // Formulas _never_ evaluate to blank. If a formula appears to have evaluated to
+ // blank, the actual value is empty string. This can be verified with ISBLANK().
+ }
+ return value;
+ }
/**
@@ -670,7 +710,7 @@ public final class WorkbookEvaluator {
}
return eval;
}
-
+
private ValueEval getEvalForNameRecord(EvaluationName nameRecord, OperationEvaluationContext ec) {
if (nameRecord.isFunctionName()) {
return new FunctionNameEval(nameRecord.getNameText());
@@ -685,25 +725,25 @@ public final class WorkbookEvaluator {
/**
* YK: Used by OperationEvaluationContext to resolve indirect names.
*/
- /*package*/ ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) {
+ /*package*/ ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) {
if (ptgs.length == 1) {
return getEvalForPtg(ptgs[0], ec);
}
- return evaluateFormula(ec, ptgs);
- }
+ return evaluateFormula(ec, ptgs);
+ }
- /**
- * Used by the lazy ref evals whenever they need to get the value of a contained cell.
- */
- /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex,
- int columnIndex, EvaluationTracker tracker) {
+ /**
+ * Used by the lazy ref evals whenever they need to get the value of a contained cell.
+ */
+ /* package */ ValueEval evaluateReference(EvaluationSheet sheet, int sheetIndex, int rowIndex,
+ int columnIndex, EvaluationTracker tracker) {
- EvaluationCell cell = sheet.getCell(rowIndex, columnIndex);
- return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker);
- }
- public FreeRefFunction findUserDefinedFunction(String functionName) {
- return _udfFinder.findFunction(functionName);
- }
+ EvaluationCell cell = sheet.getCell(rowIndex, columnIndex);
+ return evaluateAny(cell, sheetIndex, rowIndex, columnIndex, tracker);
+ }
+ public FreeRefFunction findUserDefinedFunction(String functionName) {
+ return _udfFinder.findFunction(functionName);
+ }
/**
* Whether to ignore missing references to external workbooks and
diff --git a/src/java/org/apache/poi/ss/formula/functions/Indirect.java b/src/java/org/apache/poi/ss/formula/functions/Indirect.java
index 7774af506..1ea3dcd38 100644
--- a/src/java/org/apache/poi/ss/formula/functions/Indirect.java
+++ b/src/java/org/apache/poi/ss/formula/functions/Indirect.java
@@ -17,13 +17,18 @@
package org.apache.poi.ss.formula.functions;
+import org.apache.poi.ss.formula.FormulaParseException;
+import org.apache.poi.ss.formula.FormulaParser;
+import org.apache.poi.ss.formula.FormulaParsingWorkbook;
+import org.apache.poi.ss.formula.OperationEvaluationContext;
import org.apache.poi.ss.formula.eval.BlankEval;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.MissingArgEval;
import org.apache.poi.ss.formula.eval.OperandResolver;
import org.apache.poi.ss.formula.eval.ValueEval;
-import org.apache.poi.ss.formula.OperationEvaluationContext;
+import org.apache.poi.ss.formula.ptg.Area3DPxg;
+import org.apache.poi.ss.usermodel.Table;
/**
* Implementation for Excel function INDIRECT
@@ -42,198 +47,210 @@ import org.apache.poi.ss.formula.OperationEvaluationContext;
*/
public final class Indirect implements FreeRefFunction {
- public static final FreeRefFunction instance = new Indirect();
+ public static final FreeRefFunction instance = new Indirect();
- private Indirect() {
- // enforce singleton
- }
+ private Indirect() {
+ // enforce singleton
+ }
- public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
- if (args.length < 1) {
- return ErrorEval.VALUE_INVALID;
- }
+ public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) {
+ if (args.length < 1) {
+ return ErrorEval.VALUE_INVALID;
+ }
- boolean isA1style;
- String text;
- try {
- ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
- .getColumnIndex());
- text = OperandResolver.coerceValueToString(ve);
- switch (args.length) {
- case 1:
- isA1style = true;
- break;
- case 2:
- isA1style = evaluateBooleanArg(args[1], ec);
- break;
- default:
- return ErrorEval.VALUE_INVALID;
- }
- } catch (EvaluationException e) {
- return e.getErrorEval();
- }
+ boolean isA1style;
+ String text;
+ try {
+ ValueEval ve = OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec
+ .getColumnIndex());
+ text = OperandResolver.coerceValueToString(ve);
+ switch (args.length) {
+ case 1:
+ isA1style = true;
+ break;
+ case 2:
+ isA1style = evaluateBooleanArg(args[1], ec);
+ break;
+ default:
+ return ErrorEval.VALUE_INVALID;
+ }
+ } catch (EvaluationException e) {
+ return e.getErrorEval();
+ }
- return evaluateIndirect(ec, text, isA1style);
- }
+ return evaluateIndirect(ec, text, isA1style);
+ }
- private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
- throws EvaluationException {
- ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
+ private static boolean evaluateBooleanArg(ValueEval arg, OperationEvaluationContext ec)
+ throws EvaluationException {
+ ValueEval ve = OperandResolver.getSingleValue(arg, ec.getRowIndex(), ec.getColumnIndex());
- if (ve == BlankEval.instance || ve == MissingArgEval.instance) {
- return false;
- }
- // numeric quantities follow standard boolean conversion rules
- // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
- return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
- }
+ if (ve == BlankEval.instance || ve == MissingArgEval.instance) {
+ return false;
+ }
+ // numeric quantities follow standard boolean conversion rules
+ // for strings, only "TRUE" and "FALSE" (case insensitive) are valid
+ return OperandResolver.coerceValueToBoolean(ve, false).booleanValue();
+ }
- private static ValueEval evaluateIndirect(OperationEvaluationContext ec, String text,
- boolean isA1style) {
- // Search backwards for '!' because sheet names can contain '!'
- int plingPos = text.lastIndexOf('!');
+ private static ValueEval evaluateIndirect(final OperationEvaluationContext ec, String text,
+ boolean isA1style) {
+
+ // Search backwards for '!' because sheet names can contain '!'
+ int plingPos = text.lastIndexOf('!');
- String workbookName;
- String sheetName;
- String refText; // whitespace around this gets trimmed OK
- if (plingPos < 0) {
- workbookName = null;
- sheetName = null;
- refText = text;
- } else {
- String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
- if (parts == null) {
- return ErrorEval.REF_INVALID;
- }
- workbookName = parts[0];
- sheetName = parts[1];
- refText = text.substring(plingPos + 1);
- }
+ String workbookName;
+ String sheetName;
+ String refText; // whitespace around this gets trimmed OK
+ if (plingPos < 0) {
+ workbookName = null;
+ sheetName = null;
+ refText = text;
+ } else {
+ String[] parts = parseWorkbookAndSheetName(text.subSequence(0, plingPos));
+ if (parts == null) {
+ return ErrorEval.REF_INVALID;
+ }
+ workbookName = parts[0];
+ sheetName = parts[1];
+ refText = text.substring(plingPos + 1);
+ }
- String refStrPart1;
- String refStrPart2;
+ if (Table.isStructuredReference.matcher(refText).matches()) {
+ // The argument is structured reference
+ Area3DPxg areaPtg = null;
+ try {
+ areaPtg = FormulaParser.parseStructuredReference(refText, (FormulaParsingWorkbook) ec.getWorkbook(), ec.getRowIndex());
+ } catch (FormulaParseException e) {
+ return ErrorEval.REF_INVALID;
+ }
+ return ec.getArea3DEval(areaPtg);
+ } else {
+ // The argument is regular reference
+ String refStrPart1;
+ String refStrPart2;
+ int colonPos = refText.indexOf(':');
+ if (colonPos < 0) {
+ refStrPart1 = refText.trim();
+ refStrPart2 = null;
+ } else {
+ refStrPart1 = refText.substring(0, colonPos).trim();
+ refStrPart2 = refText.substring(colonPos + 1).trim();
+ }
+ return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
+ }
+ }
- int colonPos = refText.indexOf(':');
- if (colonPos < 0) {
- refStrPart1 = refText.trim();
- refStrPart2 = null;
- } else {
- refStrPart1 = refText.substring(0, colonPos).trim();
- refStrPart2 = refText.substring(colonPos + 1).trim();
- }
- return ec.getDynamicReference(workbookName, sheetName, refStrPart1, refStrPart2, isA1style);
- }
+ /**
+ * @return array of length 2: {workbookName, sheetName,}. Second element will always be
+ * present. First element may be null if sheetName is unqualified.
+ * Returns null
if text cannot be parsed.
+ */
+ private static String[] parseWorkbookAndSheetName(CharSequence text) {
+ int lastIx = text.length() - 1;
+ if (lastIx < 0) {
+ return null;
+ }
+ if (canTrim(text)) {
+ return null;
+ }
+ char firstChar = text.charAt(0);
+ if (Character.isWhitespace(firstChar)) {
+ return null;
+ }
+ if (firstChar == '\'') {
+ // workbookName or sheetName needs quoting
+ // quotes go around both
+ if (text.charAt(lastIx) != '\'') {
+ return null;
+ }
+ firstChar = text.charAt(1);
+ if (Character.isWhitespace(firstChar)) {
+ return null;
+ }
+ String wbName;
+ int sheetStartPos;
+ if (firstChar == '[') {
+ int rbPos = text.toString().lastIndexOf(']');
+ if (rbPos < 0) {
+ return null;
+ }
+ wbName = unescapeString(text.subSequence(2, rbPos));
+ if (wbName == null || canTrim(wbName)) {
+ return null;
+ }
+ sheetStartPos = rbPos + 1;
+ } else {
+ wbName = null;
+ sheetStartPos = 1;
+ }
- /**
- * @return array of length 2: {workbookName, sheetName,}. Second element will always be
- * present. First element may be null if sheetName is unqualified.
- * Returns null
if text cannot be parsed.
- */
- private static String[] parseWorkbookAndSheetName(CharSequence text) {
- int lastIx = text.length() - 1;
- if (lastIx < 0) {
- return null;
- }
- if (canTrim(text)) {
- return null;
- }
- char firstChar = text.charAt(0);
- if (Character.isWhitespace(firstChar)) {
- return null;
- }
- if (firstChar == '\'') {
- // workbookName or sheetName needs quoting
- // quotes go around both
- if (text.charAt(lastIx) != '\'') {
- return null;
- }
- firstChar = text.charAt(1);
- if (Character.isWhitespace(firstChar)) {
- return null;
- }
- String wbName;
- int sheetStartPos;
- if (firstChar == '[') {
- int rbPos = text.toString().lastIndexOf(']');
- if (rbPos < 0) {
- return null;
- }
- wbName = unescapeString(text.subSequence(2, rbPos));
- if (wbName == null || canTrim(wbName)) {
- return null;
- }
- sheetStartPos = rbPos + 1;
- } else {
- wbName = null;
- sheetStartPos = 1;
- }
+ // else - just sheet name
+ String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
+ if (sheetName == null) { // note - when quoted, sheetName can
+ // start/end with whitespace
+ return null;
+ }
+ return new String[] { wbName, sheetName, };
+ }
- // else - just sheet name
- String sheetName = unescapeString(text.subSequence(sheetStartPos, lastIx));
- if (sheetName == null) { // note - when quoted, sheetName can
- // start/end with whitespace
- return null;
- }
- return new String[] { wbName, sheetName, };
- }
+ if (firstChar == '[') {
+ int rbPos = text.toString().lastIndexOf(']');
+ if (rbPos < 0) {
+ return null;
+ }
+ CharSequence wbName = text.subSequence(1, rbPos);
+ if (canTrim(wbName)) {
+ return null;
+ }
+ CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
+ if (canTrim(sheetName)) {
+ return null;
+ }
+ return new String[] { wbName.toString(), sheetName.toString(), };
+ }
+ // else - just sheet name
+ return new String[] { null, text.toString(), };
+ }
- if (firstChar == '[') {
- int rbPos = text.toString().lastIndexOf(']');
- if (rbPos < 0) {
- return null;
- }
- CharSequence wbName = text.subSequence(1, rbPos);
- if (canTrim(wbName)) {
- return null;
- }
- CharSequence sheetName = text.subSequence(rbPos + 1, text.length());
- if (canTrim(sheetName)) {
- return null;
- }
- return new String[] { wbName.toString(), sheetName.toString(), };
- }
- // else - just sheet name
- return new String[] { null, text.toString(), };
- }
+ /**
+ * @return null
if there is a syntax error in any escape sequence
+ * (the typical syntax error is a single quote character not followed by another).
+ */
+ private static String unescapeString(CharSequence text) {
+ int len = text.length();
+ StringBuilder sb = new StringBuilder(len);
+ int i = 0;
+ while (i < len) {
+ char ch = text.charAt(i);
+ if (ch == '\'') {
+ // every quote must be followed by another
+ i++;
+ if (i >= len) {
+ return null;
+ }
+ ch = text.charAt(i);
+ if (ch != '\'') {
+ return null;
+ }
+ }
+ sb.append(ch);
+ i++;
+ }
+ return sb.toString();
+ }
- /**
- * @return null
if there is a syntax error in any escape sequence
- * (the typical syntax error is a single quote character not followed by another).
- */
- private static String unescapeString(CharSequence text) {
- int len = text.length();
- StringBuilder sb = new StringBuilder(len);
- int i = 0;
- while (i < len) {
- char ch = text.charAt(i);
- if (ch == '\'') {
- // every quote must be followed by another
- i++;
- if (i >= len) {
- return null;
- }
- ch = text.charAt(i);
- if (ch != '\'') {
- return null;
- }
- }
- sb.append(ch);
- i++;
- }
- return sb.toString();
- }
-
- private static boolean canTrim(CharSequence text) {
- int lastIx = text.length() - 1;
- if (lastIx < 0) {
- return false;
- }
- if (Character.isWhitespace(text.charAt(0))) {
- return true;
- }
- if (Character.isWhitespace(text.charAt(lastIx))) {
- return true;
- }
- return false;
- }
+ private static boolean canTrim(CharSequence text) {
+ int lastIx = text.length() - 1;
+ if (lastIx < 0) {
+ return false;
+ }
+ if (Character.isWhitespace(text.charAt(0))) {
+ return true;
+ }
+ if (Character.isWhitespace(text.charAt(lastIx))) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java b/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java
index 665ac98cf..2916211b6 100644
--- a/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java
+++ b/src/java/org/apache/poi/ss/formula/ptg/Area3DPxg.java
@@ -36,9 +36,9 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
private String firstSheetName;
private String lastSheetName;
- public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) {
- this(externalWorkbookNumber, sheetName, new AreaReference(arearef));
- }
+ public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, String arearef) {
+ this(externalWorkbookNumber, sheetName, new AreaReference(arearef));
+ }
public Area3DPxg(int externalWorkbookNumber, SheetIdentifier sheetName, AreaReference arearef) {
super(arearef);
this.externalWorkbookNumber = externalWorkbookNumber;
@@ -57,8 +57,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
this(-1, sheetName, arearef);
}
- @Override
- public String toString() {
+ @Override
+ public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getName());
sb.append(" [");
@@ -76,8 +76,8 @@ public final class Area3DPxg extends AreaPtgBase implements Pxg3D {
sb.append(formatReferenceAsString());
sb.append("]");
return sb.toString();
- }
-
+ }
+
public int getExternalWorkbookNumber() {
return externalWorkbookNumber;
}
diff --git a/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java b/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java
index bc808b0f0..816287620 100644
--- a/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java
+++ b/src/java/org/apache/poi/ss/formula/ptg/AreaPtgBase.java
@@ -31,272 +31,272 @@ import org.apache.poi.util.LittleEndianOutput;
* @author Jason Height (jheight at chariot dot net dot au)
*/
public abstract class AreaPtgBase extends OperandPtg implements AreaI {
- /**
- * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
- * see similar comment in ReferencePtg
- */
- protected final RuntimeException notImplemented() {
- return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
- }
+ /**
+ * TODO - (May-2008) fix subclasses of AreaPtg 'AreaN~' which are used in shared formulas.
+ * see similar comment in ReferencePtg
+ */
+ protected final RuntimeException notImplemented() {
+ return new RuntimeException("Coding Error: This method should never be called. This ptg should be converted");
+ }
- /** zero based, unsigned 16 bit */
- private int field_1_first_row;
- /** zero based, unsigned 16 bit */
- private int field_2_last_row;
- /** zero based, unsigned 8 bit */
- private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number)
- /** zero based, unsigned 8 bit */
- private int field_4_last_column; //BitFields: (last row relative, last col relative, last column number)
+ /** zero based, unsigned 16 bit */
+ private int field_1_first_row;
+ /** zero based, unsigned 16 bit */
+ private int field_2_last_row;
+ /** zero based, unsigned 8 bit */
+ private int field_3_first_column; //BitFields: (first row relative, first col relative, first column number)
+ /** zero based, unsigned 8 bit */
+ private int field_4_last_column; //BitFields: (last row relative, last col relative, last column number)
- private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
- private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
- private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
+ private final static BitField rowRelative = BitFieldFactory.getInstance(0x8000);
+ private final static BitField colRelative = BitFieldFactory.getInstance(0x4000);
+ private final static BitField columnMask = BitFieldFactory.getInstance(0x3FFF);
- protected AreaPtgBase() {
- // do nothing
- }
+ protected AreaPtgBase() {
+ // do nothing
+ }
- protected AreaPtgBase(AreaReference ar) {
- CellReference firstCell = ar.getFirstCell();
- CellReference lastCell = ar.getLastCell();
- setFirstRow(firstCell.getRow());
- setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol());
- setLastRow(lastCell.getRow());
- setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol());
- setFirstColRelative(!firstCell.isColAbsolute());
- setLastColRelative(!lastCell.isColAbsolute());
- setFirstRowRelative(!firstCell.isRowAbsolute());
- setLastRowRelative(!lastCell.isRowAbsolute());
- }
+ protected AreaPtgBase(AreaReference ar) {
+ CellReference firstCell = ar.getFirstCell();
+ CellReference lastCell = ar.getLastCell();
+ setFirstRow(firstCell.getRow());
+ setFirstColumn(firstCell.getCol() == -1 ? 0 : firstCell.getCol());
+ setLastRow(lastCell.getRow());
+ setLastColumn(lastCell.getCol() == -1 ? 0xFF : lastCell.getCol());
+ setFirstColRelative(!firstCell.isColAbsolute());
+ setLastColRelative(!lastCell.isColAbsolute());
+ setFirstRowRelative(!firstCell.isRowAbsolute());
+ setLastRowRelative(!lastCell.isRowAbsolute());
+ }
- protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
- boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
+ protected AreaPtgBase(int firstRow, int lastRow, int firstColumn, int lastColumn,
+ boolean firstRowRelative, boolean lastRowRelative, boolean firstColRelative, boolean lastColRelative) {
- if (lastRow >= firstRow) {
- setFirstRow(firstRow);
- setLastRow(lastRow);
- setFirstRowRelative(firstRowRelative);
- setLastRowRelative(lastRowRelative);
- } else {
- setFirstRow(lastRow);
- setLastRow(firstRow);
- setFirstRowRelative(lastRowRelative);
- setLastRowRelative(firstRowRelative);
- }
+ if (lastRow >= firstRow) {
+ setFirstRow(firstRow);
+ setLastRow(lastRow);
+ setFirstRowRelative(firstRowRelative);
+ setLastRowRelative(lastRowRelative);
+ } else {
+ setFirstRow(lastRow);
+ setLastRow(firstRow);
+ setFirstRowRelative(lastRowRelative);
+ setLastRowRelative(firstRowRelative);
+ }
- if (lastColumn >= firstColumn) {
- setFirstColumn(firstColumn);
- setLastColumn(lastColumn);
- setFirstColRelative(firstColRelative);
- setLastColRelative(lastColRelative);
- } else {
- setFirstColumn(lastColumn);
- setLastColumn(firstColumn);
- setFirstColRelative(lastColRelative);
- setLastColRelative(firstColRelative);
- }
- }
-
- /**
- * Sort the first and last row and columns in-place to the preferred (top left:bottom right) order
- * Note: Sort only occurs when an instance is constructed or when this method is called.
- *
- * For example, $E5:B$10
becomes B5:$E$10
- */
- public void sortTopLeftToBottomRight() {
- if (getFirstRow() > getLastRow()) {
- //swap first row and last row numbers and relativity
- //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
- final int firstRow = getFirstRow();
- final boolean firstRowRel = isFirstRowRelative();
- setFirstRow(getLastRow());
- setFirstRowRelative(isLastRowRelative());
- setLastRow(firstRow);
- setLastRowRelative(firstRowRel);
- }
- if (getFirstColumn() > getLastColumn()) {
- //swap first column and last column numbers and relativity
- //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
- final int firstCol = getFirstColumn();
- final boolean firstColRel = isFirstColRelative();
- setFirstColumn(getLastColumn());
- setFirstColRelative(isLastColRelative());
- setLastColumn(firstCol);
- setLastColRelative(firstColRel);
- }
- }
+ if (lastColumn >= firstColumn) {
+ setFirstColumn(firstColumn);
+ setLastColumn(lastColumn);
+ setFirstColRelative(firstColRelative);
+ setLastColRelative(lastColRelative);
+ } else {
+ setFirstColumn(lastColumn);
+ setLastColumn(firstColumn);
+ setFirstColRelative(lastColRelative);
+ setLastColRelative(firstColRelative);
+ }
+ }
+
+ /**
+ * Sort the first and last row and columns in-place to the preferred (top left:bottom right) order
+ * Note: Sort only occurs when an instance is constructed or when this method is called.
+ *
+ * For example, $E5:B$10
becomes B5:$E$10
+ */
+ public void sortTopLeftToBottomRight() {
+ if (getFirstRow() > getLastRow()) {
+ //swap first row and last row numbers and relativity
+ //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
+ final int firstRow = getFirstRow();
+ final boolean firstRowRel = isFirstRowRelative();
+ setFirstRow(getLastRow());
+ setFirstRowRelative(isLastRowRelative());
+ setLastRow(firstRow);
+ setLastRowRelative(firstRowRel);
+ }
+ if (getFirstColumn() > getLastColumn()) {
+ //swap first column and last column numbers and relativity
+ //Note: cannot just swap the fields because row relativity is stored in fields 3 and 4
+ final int firstCol = getFirstColumn();
+ final boolean firstColRel = isFirstColRelative();
+ setFirstColumn(getLastColumn());
+ setFirstColRelative(isLastColRelative());
+ setLastColumn(firstCol);
+ setLastColRelative(firstColRel);
+ }
+ }
- protected final void readCoordinates(LittleEndianInput in) {
- field_1_first_row = in.readUShort();
- field_2_last_row = in.readUShort();
- field_3_first_column = in.readUShort();
- field_4_last_column = in.readUShort();
- }
- protected final void writeCoordinates(LittleEndianOutput out) {
- out.writeShort(field_1_first_row);
- out.writeShort(field_2_last_row);
- out.writeShort(field_3_first_column);
- out.writeShort(field_4_last_column);
- }
+ protected final void readCoordinates(LittleEndianInput in) {
+ field_1_first_row = in.readUShort();
+ field_2_last_row = in.readUShort();
+ field_3_first_column = in.readUShort();
+ field_4_last_column = in.readUShort();
+ }
+ protected final void writeCoordinates(LittleEndianOutput out) {
+ out.writeShort(field_1_first_row);
+ out.writeShort(field_2_last_row);
+ out.writeShort(field_3_first_column);
+ out.writeShort(field_4_last_column);
+ }
- /**
- * @return the first row in the area
- */
- public final int getFirstRow() {
- return field_1_first_row;
- }
+ /**
+ * @return the first row in the area
+ */
+ public final int getFirstRow() {
+ return field_1_first_row;
+ }
- /**
- * sets the first row
- * @param rowIx number (0-based)
- */
- public final void setFirstRow(int rowIx) {
- field_1_first_row = rowIx;
- }
+ /**
+ * sets the first row
+ * @param rowIx number (0-based)
+ */
+ public final void setFirstRow(int rowIx) {
+ field_1_first_row = rowIx;
+ }
- /**
- * @return last row in the range (x2 in x1,y1-x2,y2)
- */
- public final int getLastRow() {
- return field_2_last_row;
- }
+ /**
+ * @return last row in the range (x2 in x1,y1-x2,y2)
+ */
+ public final int getLastRow() {
+ return field_2_last_row;
+ }
- /**
- * @param rowIx last row number in the area
- */
- public final void setLastRow(int rowIx) {
- field_2_last_row = rowIx;
- }
+ /**
+ * @param rowIx last row number in the area
+ */
+ public final void setLastRow(int rowIx) {
+ field_2_last_row = rowIx;
+ }
- /**
- * @return the first column number in the area.
- */
- public final int getFirstColumn() {
- return columnMask.getValue(field_3_first_column);
- }
+ /**
+ * @return the first column number in the area.
+ */
+ public final int getFirstColumn() {
+ return columnMask.getValue(field_3_first_column);
+ }
- /**
- * @return the first column number + the options bit settings unstripped
- */
- public final short getFirstColumnRaw() {
- return (short) field_3_first_column; // TODO
- }
+ /**
+ * @return the first column number + the options bit settings unstripped
+ */
+ public final short getFirstColumnRaw() {
+ return (short) field_3_first_column; // TODO
+ }
- /**
- * @return whether or not the first row is a relative reference or not.
- */
- public final boolean isFirstRowRelative() {
- return rowRelative.isSet(field_3_first_column);
- }
+ /**
+ * @return whether or not the first row is a relative reference or not.
+ */
+ public final boolean isFirstRowRelative() {
+ return rowRelative.isSet(field_3_first_column);
+ }
- /**
- * sets the first row to relative or not
- * @param rel is relative or not.
- */
- public final void setFirstRowRelative(boolean rel) {
- field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
- }
+ /**
+ * sets the first row to relative or not
+ * @param rel is relative or not.
+ */
+ public final void setFirstRowRelative(boolean rel) {
+ field_3_first_column=rowRelative.setBoolean(field_3_first_column,rel);
+ }
- /**
- * @return isrelative first column to relative or not
- */
- public final boolean isFirstColRelative() {
- return colRelative.isSet(field_3_first_column);
- }
+ /**
+ * @return isrelative first column to relative or not
+ */
+ public final boolean isFirstColRelative() {
+ return colRelative.isSet(field_3_first_column);
+ }
- /**
- * set whether the first column is relative
- */
- public final void setFirstColRelative(boolean rel) {
- field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
- }
+ /**
+ * set whether the first column is relative
+ */
+ public final void setFirstColRelative(boolean rel) {
+ field_3_first_column=colRelative.setBoolean(field_3_first_column,rel);
+ }
- /**
- * set the first column in the area
- */
- public final void setFirstColumn(int colIx) {
- field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
- }
+ /**
+ * set the first column in the area
+ */
+ public final void setFirstColumn(int colIx) {
+ field_3_first_column=columnMask.setValue(field_3_first_column, colIx);
+ }
- /**
- * set the first column irrespective of the bitmasks
- */
- public final void setFirstColumnRaw(int column) {
- field_3_first_column = column;
- }
+ /**
+ * set the first column irrespective of the bitmasks
+ */
+ public final void setFirstColumnRaw(int column) {
+ field_3_first_column = column;
+ }
- /**
- * @return lastcolumn in the area
- */
- public final int getLastColumn() {
- return columnMask.getValue(field_4_last_column);
- }
+ /**
+ * @return lastcolumn in the area
+ */
+ public final int getLastColumn() {
+ return columnMask.getValue(field_4_last_column);
+ }
- /**
- * @return last column and bitmask (the raw field)
- */
- public final short getLastColumnRaw() {
- return (short) field_4_last_column;
- }
+ /**
+ * @return last column and bitmask (the raw field)
+ */
+ public final short getLastColumnRaw() {
+ return (short) field_4_last_column;
+ }
- /**
- * @return last row relative or not
- */
- public final boolean isLastRowRelative() {
- return rowRelative.isSet(field_4_last_column);
- }
+ /**
+ * @return last row relative or not
+ */
+ public final boolean isLastRowRelative() {
+ return rowRelative.isSet(field_4_last_column);
+ }
- /**
- * set whether the last row is relative or not
- * @param rel true
if the last row relative, else
- * false
- */
- public final void setLastRowRelative(boolean rel) {
- field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
- }
+ /**
+ * set whether the last row is relative or not
+ * @param rel true
if the last row relative, else
+ * false
+ */
+ public final void setLastRowRelative(boolean rel) {
+ field_4_last_column=rowRelative.setBoolean(field_4_last_column,rel);
+ }
- /**
- * @return lastcol relative or not
- */
- public final boolean isLastColRelative() {
- return colRelative.isSet(field_4_last_column);
- }
+ /**
+ * @return lastcol relative or not
+ */
+ public final boolean isLastColRelative() {
+ return colRelative.isSet(field_4_last_column);
+ }
- /**
- * set whether the last column should be relative or not
- */
- public final void setLastColRelative(boolean rel) {
- field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
- }
+ /**
+ * set whether the last column should be relative or not
+ */
+ public final void setLastColRelative(boolean rel) {
+ field_4_last_column=colRelative.setBoolean(field_4_last_column,rel);
+ }
- /**
- * set the last column in the area
- */
- public final void setLastColumn(int colIx) {
- field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
- }
+ /**
+ * set the last column in the area
+ */
+ public final void setLastColumn(int colIx) {
+ field_4_last_column=columnMask.setValue(field_4_last_column, colIx);
+ }
- /**
- * 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());
+ /**
+ * 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());
- if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) {
- return (new AreaReference(topLeft, botRight)).formatAsString();
- }
- return topLeft.formatAsString() + ":" + botRight.formatAsString();
- }
+ if(AreaReference.isWholeColumnReference(SpreadsheetVersion.EXCEL97, topLeft, botRight)) {
+ return (new AreaReference(topLeft, botRight)).formatAsString();
+ }
+ return topLeft.formatAsString() + ":" + botRight.formatAsString();
+ }
- public String toFormulaString() {
- return formatReferenceAsString();
- }
+ public String toFormulaString() {
+ return formatReferenceAsString();
+ }
- public byte getDefaultOperandClass() {
- return Ptg.CLASS_REF;
- }
+ public byte getDefaultOperandClass() {
+ return Ptg.CLASS_REF;
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java
index b873588db..3582daefc 100644
--- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFEvaluationWorkbook.java
@@ -28,7 +28,7 @@ import org.apache.poi.xssf.usermodel.BaseXSSFEvaluationWorkbook;
* SXSSF wrapper around the SXSSF and XSSF workbooks
*/
public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
- private SXSSFWorkbook _uBook;
+ private final SXSSFWorkbook _uBook;
public static SXSSFEvaluationWorkbook create(SXSSFWorkbook book) {
if (book == null) {
@@ -41,16 +41,19 @@ public final class SXSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
super(book.getXSSFWorkbook());
_uBook = book;
}
-
+
+ @Override
public int getSheetIndex(EvaluationSheet evalSheet) {
SXSSFSheet sheet = ((SXSSFEvaluationSheet)evalSheet).getSXSSFSheet();
return _uBook.getSheetIndex(sheet);
}
-
+
+ @Override
public EvaluationSheet getSheet(int sheetIndex) {
return new SXSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
}
-
+
+ @Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
SXSSFCell cell = ((SXSSFEvaluationCell)evalCell).getSXSSFCell();
SXSSFEvaluationWorkbook frBook = SXSSFEvaluationWorkbook.create(_uBook);
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java
index 4a6882ac4..28a9ecf7d 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/BaseXSSFEvaluationWorkbook.java
@@ -17,7 +17,10 @@
package org.apache.poi.xssf.usermodel;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.EvaluationName;
@@ -36,6 +39,8 @@ import org.apache.poi.ss.formula.ptg.Ptg;
import org.apache.poi.ss.formula.ptg.Ref3DPxg;
import org.apache.poi.ss.formula.udf.IndexedUDFFinder;
import org.apache.poi.ss.formula.udf.UDFFinder;
+import org.apache.poi.ss.usermodel.Table;
+import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.util.NotImplemented;
@@ -46,31 +51,31 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDefinedName;
* Internal POI use only - parent of XSSF and SXSSF evaluation workbooks
*/
public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWorkbook, EvaluationWorkbook, FormulaParsingWorkbook {
- protected final XSSFWorkbook _uBook;
+ protected final XSSFWorkbook _uBook;
- protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) {
- _uBook = book;
- }
+ protected BaseXSSFEvaluationWorkbook(XSSFWorkbook book) {
+ _uBook = book;
+ }
- private int convertFromExternalSheetIndex(int externSheetIndex) {
- return externSheetIndex;
- }
- /**
- * XSSF doesn't use external sheet indexes, so when asked treat
- * it just as a local index
- */
- public int convertFromExternSheetIndex(int externSheetIndex) {
- return externSheetIndex;
- }
- /**
- * @return the external sheet index of the sheet with the given internal
- * index. Used by some of the more obscure formula and named range things.
- * Fairly easy on XSSF (we think...) since the internal and external
- * indices are the same
- */
- private int convertToExternalSheetIndex(int sheetIndex) {
- return sheetIndex;
- }
+ private int convertFromExternalSheetIndex(int externSheetIndex) {
+ return externSheetIndex;
+ }
+ /**
+ * XSSF doesn't use external sheet indexes, so when asked treat
+ * it just as a local index
+ */
+ public int convertFromExternSheetIndex(int externSheetIndex) {
+ return externSheetIndex;
+ }
+ /**
+ * @return the external sheet index of the sheet with the given internal
+ * index. Used by some of the more obscure formula and named range things.
+ * Fairly easy on XSSF (we think...) since the internal and external
+ * indices are the same
+ */
+ private int convertToExternalSheetIndex(int sheetIndex) {
+ return sheetIndex;
+ }
public int getExternalSheetIndex(String sheetName) {
int sheetIndex = _uBook.getSheetIndex(sheetName);
@@ -131,37 +136,37 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
}
}
- /**
- * Return EvaluationName wrapper around the matching XSSFName (named range)
- * @param name case-aware but case-insensitive named range in workbook
- * @param sheetIndex index of sheet if named range scope is limited to one sheet
- * if named range scope is global to the workbook, sheetIndex is -1.
- * @return If name is a named range in the workbook, returns
- * EvaluationName corresponding to that named range
- * Returns null if there is no named range with the same name and scope in the workbook
- */
- public EvaluationName getName(String name, int sheetIndex) {
- for (int i = 0; i < _uBook.getNumberOfNames(); i++) {
- XSSFName nm = _uBook.getNameAt(i);
- String nameText = nm.getNameName();
- int nameSheetindex = nm.getSheetIndex();
- if (name.equalsIgnoreCase(nameText) &&
- (nameSheetindex == -1 || nameSheetindex == sheetIndex)) {
- return new Name(nm, i, this);
- }
- }
- return sheetIndex == -1 ? null : getName(name, -1);
- }
+ /**
+ * Return EvaluationName wrapper around the matching XSSFName (named range)
+ * @param name case-aware but case-insensitive named range in workbook
+ * @param sheetIndex index of sheet if named range scope is limited to one sheet
+ * if named range scope is global to the workbook, sheetIndex is -1.
+ * @return If name is a named range in the workbook, returns
+ * EvaluationName corresponding to that named range
+ * Returns null if there is no named range with the same name and scope in the workbook
+ */
+ public EvaluationName getName(String name, int sheetIndex) {
+ for (int i = 0; i < _uBook.getNumberOfNames(); i++) {
+ XSSFName nm = _uBook.getNameAt(i);
+ String nameText = nm.getNameName();
+ int nameSheetindex = nm.getSheetIndex();
+ if (name.equalsIgnoreCase(nameText) &&
+ (nameSheetindex == -1 || nameSheetindex == sheetIndex)) {
+ return new Name(nm, i, this);
+ }
+ }
+ return sheetIndex == -1 ? null : getName(name, -1);
+ }
- public String getSheetName(int sheetIndex) {
- return _uBook.getSheetName(sheetIndex);
- }
-
- public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
+ public String getSheetName(int sheetIndex) {
+ return _uBook.getSheetName(sheetIndex);
+ }
+
+ public ExternalName getExternalName(int externSheetIndex, int externNameIndex) {
throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
- }
+ }
- public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) {
+ public ExternalName getExternalName(String nameName, String sheetName, int externalWorkbookNumber) {
if (externalWorkbookNumber > 0) {
// External reference - reference is 1 based, link table is 0 based
int linkNumber = externalWorkbookNumber - 1;
@@ -186,7 +191,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
int nameIdx = _uBook.getNameIndex(nameName);
return new ExternalName(nameName, nameIdx, 0); // TODO Is this right?
}
-
+
}
/**
@@ -194,7 +199,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
*/
@Override
public NameXPxg getNameXPtg(String name, SheetIdentifier sheet) {
- // First, try to find it as a User Defined Function
+ // First, try to find it as a User Defined Function
IndexedUDFFinder udfFinder = (IndexedUDFFinder)getUDFFinder();
FreeRefFunction func = udfFinder.findFunction(name);
if (func != null) {
@@ -223,7 +228,7 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
} else {
return new NameXPxg(sheetName, name);
}
- }
+ }
public Ptg get3DReferencePtg(CellReference cell, SheetIdentifier sheet) {
if (sheet._bookName != null) {
int bookIndex = resolveBookIndex(sheet._bookName);
@@ -259,102 +264,151 @@ public abstract class BaseXSSFEvaluationWorkbook implements FormulaRenderingWork
return name;
}
- public ExternalSheet getExternalSheet(int externSheetIndex) {
- throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
- }
- public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
- String workbookName;
- if (externalWorkbookNumber > 0) {
- // External reference - reference is 1 based, link table is 0 based
- int linkNumber = externalWorkbookNumber - 1;
- ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
- workbookName = linkTable.getLinkedFileName();
- } else {
- // Internal reference
- workbookName = null;
- }
-
- if (lastSheetName == null || firstSheetName.equals(lastSheetName)) {
- return new ExternalSheet(workbookName, firstSheetName);
- } else {
- return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName);
- }
+ public ExternalSheet getExternalSheet(int externSheetIndex) {
+ throw new IllegalStateException("HSSF-style external references are not supported for XSSF");
+ }
+ public ExternalSheet getExternalSheet(String firstSheetName, String lastSheetName, int externalWorkbookNumber) {
+ String workbookName;
+ if (externalWorkbookNumber > 0) {
+ // External reference - reference is 1 based, link table is 0 based
+ int linkNumber = externalWorkbookNumber - 1;
+ ExternalLinksTable linkTable = _uBook.getExternalLinksTable().get(linkNumber);
+ workbookName = linkTable.getLinkedFileName();
+ } else {
+ // Internal reference
+ workbookName = null;
+ }
+
+ if (lastSheetName == null || firstSheetName.equals(lastSheetName)) {
+ return new ExternalSheet(workbookName, firstSheetName);
+ } else {
+ return new ExternalSheetRange(workbookName, firstSheetName, lastSheetName);
+ }
}
@NotImplemented
public int getExternalSheetIndex(String workbookName, String sheetName) {
- throw new RuntimeException("not implemented yet");
- }
- public int getSheetIndex(String sheetName) {
- return _uBook.getSheetIndex(sheetName);
- }
+ throw new RuntimeException("not implemented yet");
+ }
+ public int getSheetIndex(String sheetName) {
+ return _uBook.getSheetIndex(sheetName);
+ }
- public String getSheetFirstNameByExternSheet(int externSheetIndex) {
- int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
- return _uBook.getSheetName(sheetIndex);
- }
+ public String getSheetFirstNameByExternSheet(int externSheetIndex) {
+ int sheetIndex = convertFromExternalSheetIndex(externSheetIndex);
+ return _uBook.getSheetName(sheetIndex);
+ }
public String getSheetLastNameByExternSheet(int externSheetIndex) {
// XSSF does multi-sheet references differently, so this is the same as the first
return getSheetFirstNameByExternSheet(externSheetIndex);
}
- public String getNameText(NamePtg namePtg) {
- return _uBook.getNameAt(namePtg.getIndex()).getNameName();
- }
- public EvaluationName getName(NamePtg namePtg) {
- int ix = namePtg.getIndex();
- return new Name(_uBook.getNameAt(ix), ix, this);
- }
- @Override
- public XSSFName createName() {
- return _uBook.createName();
- }
-
+ public String getNameText(NamePtg namePtg) {
+ return _uBook.getNameAt(namePtg.getIndex()).getNameName();
+ }
+ public EvaluationName getName(NamePtg namePtg) {
+ int ix = namePtg.getIndex();
+ return new Name(_uBook.getNameAt(ix), ix, this);
+ }
+ @Override
+ public XSSFName createName() {
+ return _uBook.createName();
+ }
+
+ private static String caseInsensitive(String s) {
+ return s.toUpperCase(Locale.ROOT);
+ }
+
+ /*
+ * TODO: data tables are stored at the workbook level in XSSF, but are bound to a single sheet.
+ * The current code structure has them hanging off XSSFSheet, but formulas reference them
+ * only by name (names are global, and case insensitive).
+ * This map stores names as lower case for case-insensitive lookups.
+ *
+ * FIXME: Caching tables by name here for fast formula lookup means the map is out of date if
+ * a table is renamed or added/removed to a sheet after the map is created.
+ *
+ * Perhaps tables can be managed similar to PivotTable references above?
+ */
+ private Map _tableCache = null;
+ private Map getTableCache() {
+ if ( _tableCache != null ) {
+ return _tableCache;
+ }
+ // FIXME: use org.apache.commons.collections.map.CaseInsensitiveMap
+ _tableCache = new HashMap();
+
+ for (Sheet sheet : _uBook) {
+ for (XSSFTable tbl : ((XSSFSheet)sheet).getTables()) {
+ String lname = caseInsensitive(tbl.getName());
+ _tableCache.put(lname, tbl);
+ }
+ }
+ return _tableCache;
+ }
+
+ /**
+ * Returns the data table with the given name (case insensitive).
+ * Tables are cached for performance (formula evaluation looks them up by name repeatedly).
+ * After the first table lookup, adding or removing a table from the document structure will cause trouble.
+ * This is meant to be used on documents whose structure is essentially static at the point formulas are evaluated.
+ *
+ * @param name the data table name (case-insensitive)
+ * @return The Data table in the workbook named name , or null if no table is named name .
+ * @since 3.15 beta 2
+ */
+ @Override
+ public XSSFTable getTable(String name) {
+ if (name == null) return null;
+ String lname = caseInsensitive(name);
+ return getTableCache().get(lname);
+ }
+
public UDFFinder getUDFFinder(){
return _uBook.getUDFFinder();
}
- private static final class Name implements EvaluationName {
+ private static final class Name implements EvaluationName {
- private final XSSFName _nameRecord;
- private final int _index;
- private final FormulaParsingWorkbook _fpBook;
+ private final XSSFName _nameRecord;
+ private final int _index;
+ private final FormulaParsingWorkbook _fpBook;
- public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {
- _nameRecord = name;
- _index = index;
- _fpBook = fpBook;
- }
+ public Name(XSSFName name, int index, FormulaParsingWorkbook fpBook) {
+ _nameRecord = name;
+ _index = index;
+ _fpBook = fpBook;
+ }
- public Ptg[] getNameDefinition() {
+ public Ptg[] getNameDefinition() {
- return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex());
- }
+ return FormulaParser.parse(_nameRecord.getRefersToFormula(), _fpBook, FormulaType.NAMEDRANGE, _nameRecord.getSheetIndex());
+ }
- public String getNameText() {
- return _nameRecord.getNameName();
- }
+ public String getNameText() {
+ return _nameRecord.getNameName();
+ }
- public boolean hasFormula() {
- // TODO - no idea if this is right
- CTDefinedName ctn = _nameRecord.getCTName();
- String strVal = ctn.getStringValue();
- return !ctn.getFunction() && strVal != null && strVal.length() > 0;
- }
+ public boolean hasFormula() {
+ // TODO - no idea if this is right
+ CTDefinedName ctn = _nameRecord.getCTName();
+ String strVal = ctn.getStringValue();
+ return !ctn.getFunction() && strVal != null && strVal.length() > 0;
+ }
- public boolean isFunctionName() {
- return _nameRecord.isFunctionName();
- }
+ public boolean isFunctionName() {
+ return _nameRecord.isFunctionName();
+ }
- public boolean isRange() {
- return hasFormula(); // TODO - is this right?
- }
- public NamePtg createPtg() {
- return new NamePtg(_index);
- }
- }
+ public boolean isRange() {
+ return hasFormula(); // TODO - is this right?
+ }
+ public NamePtg createPtg() {
+ return new NamePtg(_index);
+ }
+ }
- public SpreadsheetVersion getSpreadsheetVersion(){
- return SpreadsheetVersion.EXCEL2007;
- }
+ public SpreadsheetVersion getSpreadsheetVersion(){
+ return SpreadsheetVersion.EXCEL2007;
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
index 29aaa867e..ef2a7d94e 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFCell.java
@@ -406,9 +406,9 @@ public final class XSSFCell implements Cell {
if (cachedValueType != expectedTypeCode) {
throw typeMismatch(expectedTypeCode, cachedValueType, true);
}
- }
+ }
- /**
+ /**
* Set a string value for the cell.
*
* @param str value to set the cell to. For formulas we'll set the formula
@@ -506,7 +506,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(sheet.getWorkbook());
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL2007);
- Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(sharedFormula, fpb, FormulaType.CELL, sheetIndex, getRowIndex());
Ptg[] fmla = sf.convertSharedFormulas(ptgs,
getRowIndex() - ref.getFirstRow(), getColumnIndex() - ref.getFirstColumn());
return FormulaRenderer.toFormulaString(fpb, fmla);
@@ -550,7 +550,7 @@ public final class XSSFCell implements Cell {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
//validate through the FormulaParser
- FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()));
+ FormulaParser.parse(formula, fpb, formulaType, wb.getSheetIndex(getSheet()), getRowIndex());
CTCellFormula f = CTCellFormula.Factory.newInstance();
f.setStringValue(formula);
@@ -925,8 +925,8 @@ public final class XSSFCell implements Cell {
throw new IllegalArgumentException("Illegal cell type: " + cellType);
}
if (cellType != CELL_TYPE_FORMULA && _cell.isSetF()) {
- _cell.unsetF();
- }
+ _cell.unsetF();
+ }
}
/**
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
index 70fc72067..5fead42f1 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFEvaluationWorkbook.java
@@ -27,29 +27,32 @@ import org.apache.poi.ss.formula.ptg.Ptg;
* Internal POI use only
*/
public final class XSSFEvaluationWorkbook extends BaseXSSFEvaluationWorkbook {
- public static XSSFEvaluationWorkbook create(XSSFWorkbook book) {
- if (book == null) {
- return null;
- }
- return new XSSFEvaluationWorkbook(book);
- }
+ public static XSSFEvaluationWorkbook create(XSSFWorkbook book) {
+ if (book == null) {
+ return null;
+ }
+ return new XSSFEvaluationWorkbook(book);
+ }
- private XSSFEvaluationWorkbook(XSSFWorkbook book) {
- super(book);
- }
+ private XSSFEvaluationWorkbook(XSSFWorkbook book) {
+ super(book);
+ }
- public int getSheetIndex(EvaluationSheet evalSheet) {
- XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet();
- return _uBook.getSheetIndex(sheet);
- }
+ @Override
+ public int getSheetIndex(EvaluationSheet evalSheet) {
+ XSSFSheet sheet = ((XSSFEvaluationSheet)evalSheet).getXSSFSheet();
+ return _uBook.getSheetIndex(sheet);
+ }
- public EvaluationSheet getSheet(int sheetIndex) {
- return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
- }
-
+ @Override
+ public EvaluationSheet getSheet(int sheetIndex) {
+ return new XSSFEvaluationSheet(_uBook.getSheetAt(sheetIndex));
+ }
+
+ @Override
public Ptg[] getFormulaTokens(EvaluationCell evalCell) {
XSSFCell cell = ((XSSFEvaluationCell)evalCell).getXSSFCell();
XSSFEvaluationWorkbook frBook = XSSFEvaluationWorkbook.create(_uBook);
- return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()));
+ return FormulaParser.parse(cell.getCellFormula(), frBook, FormulaType.CELL, _uBook.getSheetIndex(cell.getSheet()), cell.getRowIndex());
}
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java
index ecbec23b5..be68c3050 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFName.java
@@ -192,7 +192,7 @@ public final class XSSFName implements Name {
public void setRefersToFormula(String formulaText) {
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
//validate through the FormulaParser
- FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
_ctName.setStringValue(formulaText);
}
@@ -203,7 +203,7 @@ public final class XSSFName implements Name {
return false;
}
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(_workbook);
- Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex());
+ Ptg[] ptgs = FormulaParser.parse(formulaText, fpb, FormulaType.NAMEDRANGE, getSheetIndex(), -1);
return Ptg.doesFormulaReferToDeletedCell(ptgs);
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java
index c8a932bb9..03e461422 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFTable.java
@@ -24,13 +24,17 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import org.apache.poi.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.ss.usermodel.Table;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.usermodel.helpers.XSSFXmlColumnPr;
+import org.apache.poi.util.StringUtil;
import org.apache.xmlbeans.XmlException;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTable;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTTableColumn;
@@ -48,10 +52,12 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.TableDocument;
*
* @author Roberto Manicardi
*/
-public class XSSFTable extends POIXMLDocumentPart {
+public class XSSFTable extends POIXMLDocumentPart implements Table {
private CTTable ctTable;
private List xmlColumnPr;
+ private CTTableColumn[] ctColumns;
+ private HashMap columnMap;
private CellReference startCellReference;
private CellReference endCellReference;
private String commonXPath;
@@ -107,7 +113,7 @@ public class XSSFTable extends POIXMLDocumentPart {
out.close();
}
- public CTTable getCTTable(){
+ public CTTable getCTTable() {
return ctTable;
}
@@ -117,32 +123,41 @@ public class XSSFTable extends POIXMLDocumentPart {
* @return true if the Table element contain mappings
*/
public boolean mapsTo(long id){
- boolean maps =false;
-
List pointers = getXmlColumnPrs();
for (XSSFXmlColumnPr pointer: pointers) {
if (pointer.getMapId()==id) {
- maps=true;
- break;
+ return true;
}
}
- return maps;
+ return false;
}
+ /**
+ * caches table columns for performance.
+ * Updated via updateHeaders
+ * @since 3.15 beta 2
+ */
+ private CTTableColumn[] getTableColumns() {
+ if (ctColumns == null) {
+ ctColumns = ctTable.getTableColumns().getTableColumnArray();
+ }
+ return ctColumns;
+ }
/**
*
* Calculates the xpath of the root element for the table. This will be the common part
* of all the mapping's xpaths
+ * Note: this function caches the result for performance. To flush the cache {@link #updateHeaders()} must be called.
*
* @return the xpath of the table's root element
*/
public String getCommonXpath() {
if (commonXPath == null) {
String[] commonTokens = {};
- for (CTTableColumn column :ctTable.getTableColumns().getTableColumnArray()) {
+ for (CTTableColumn column : getTableColumns()) {
if (column.getXmlColumnPr()!=null) {
String xpath = column.getXmlColumnPr().getXpath();
String[] tokens = xpath.split("/");
@@ -166,21 +181,24 @@ public class XSSFTable extends POIXMLDocumentPart {
}
}
- commonXPath = "";
- for (int i = 1 ; i< commonTokens.length;i++) {
- commonXPath +="/"+commonTokens[i];
- }
+ commonTokens[0] = "";
+ commonXPath = StringUtil.join(commonTokens, "/");
}
return commonXPath;
}
+ /**
+ * Note this list is static - once read, it does not notice later changes to the underlying column structures
+ * To clear the cache, call {@link #updateHeaders}
+ * @return List of XSSFXmlColumnPr
+ */
public List getXmlColumnPrs() {
if (xmlColumnPr==null) {
xmlColumnPr = new ArrayList();
- for (CTTableColumn column:ctTable.getTableColumns().getTableColumnArray()) {
+ for (CTTableColumn column: getTableColumns()) {
if (column.getXmlColumnPr()!=null) {
XSSFXmlColumnPr columnPr = new XSSFXmlColumnPr(this,column,column.getXmlColumnPr());
xmlColumnPr.add(columnPr);
@@ -283,7 +301,7 @@ public class XSSFTable extends POIXMLDocumentPart {
* Headers must be in sync, otherwise Excel will display a
* "Found unreadable content" message on startup.
*/
- public void updateHeaders(){
+ public void updateHeaders() {
XSSFSheet sheet = (XSSFSheet)getParent();
CellReference ref = getStartCellReference();
if(ref == null) return;
@@ -301,6 +319,83 @@ public class XSSFTable extends POIXMLDocumentPart {
}
cellnum++;
}
+ ctColumns = null;
+ columnMap = null;
+ xmlColumnPr = null;
+ commonXPath = null;
}
}
+
+ private static String caseInsensitive(String s) {
+ return s.toUpperCase(Locale.ROOT);
+ }
+
+ /**
+ * Gets the relative column index of a column in this table having the header name column
.
+ * The column index is relative to the left-most column in the table, 0-indexed.
+ * Returns -1
if column
is not a header name in table.
+ *
+ * Note: this function caches column names for performance. To flush the cache (because columns
+ * have been moved or column headers have been changed), {@link #updateHeaders()} must be called.
+ *
+ * @since 3.15 beta 2
+ */
+ public int findColumnIndex(String column) {
+ if (columnMap == null) {
+ // FIXME: replace with org.apache.commons.collections.map.CaseInsensitiveMap
+ int count = getTableColumns().length;
+ columnMap = new HashMap(count);
+
+ for (int i=0; i < count; i++) {
+ String columnName = getTableColumns()[i].getName();
+ columnMap.put(caseInsensitive(columnName), i);
+ }
+ }
+ // Table column names with special characters need a single quote escape
+ // but the escape is not present in the column definition
+ Integer idx = columnMap.get(caseInsensitive(column.replace("'", "")));
+ return idx == null ? -1 : idx.intValue();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public String getSheetName() {
+ return getXSSFSheet().getSheetName();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public boolean isHasTotalsRow() {
+ return ctTable.getTotalsRowShown();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public int getStartColIndex() {
+ return getStartCellReference().getCol();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public int getStartRowIndex() {
+ return getStartCellReference().getRow();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public int getEndColIndex() {
+ return getEndCellReference().getCol();
+ }
+
+ /**
+ * @since 3.15 beta 2
+ */
+ public int getEndRowIndex() {
+ return getEndCellReference().getRow();
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
index 100c2b95b..742720f78 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java
@@ -33,6 +33,7 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.regex.Pattern;
@@ -1761,7 +1762,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
* Get the document's embedded files.
*/
@Override
- public List getAllEmbedds() throws OpenXML4JException {
+ public List getAllEmbedds() throws OpenXML4JException {
List embedds = new LinkedList();
for(XSSFSheet sheet : sheets){
@@ -1928,7 +1929,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
*/
@Internal
public MapInfo getMapInfo(){
- return mapInfo;
+ return mapInfo;
}
/**
@@ -1945,92 +1946,92 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
throw new RuntimeException("Not Implemented - see bug #57184");
}
- /**
- * Specifies a boolean value that indicates whether structure of workbook is locked.
- * A value true indicates the structure of the workbook is locked. Worksheets in the workbook can't be moved,
- * deleted, hidden, unhidden, or renamed, and new worksheets can't be inserted.
- * A value of false indicates the structure of the workbook is not locked.
- *
- * @return true if structure of workbook is locked
- */
- public boolean isStructureLocked() {
- return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure();
- }
+ /**
+ * Specifies a boolean value that indicates whether structure of workbook is locked.
+ * A value true indicates the structure of the workbook is locked. Worksheets in the workbook can't be moved,
+ * deleted, hidden, unhidden, or renamed, and new worksheets can't be inserted.
+ * A value of false indicates the structure of the workbook is not locked.
+ *
+ * @return true if structure of workbook is locked
+ */
+ public boolean isStructureLocked() {
+ return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockStructure();
+ }
- /**
- * Specifies a boolean value that indicates whether the windows that comprise the workbook are locked.
- * A value of true indicates the workbook windows are locked. Windows are the same size and position each time the
- * workbook is opened.
- * A value of false indicates the workbook windows are not locked.
- *
- * @return true if windows that comprise the workbook are locked
- */
- public boolean isWindowsLocked() {
- return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows();
- }
+ /**
+ * Specifies a boolean value that indicates whether the windows that comprise the workbook are locked.
+ * A value of true indicates the workbook windows are locked. Windows are the same size and position each time the
+ * workbook is opened.
+ * A value of false indicates the workbook windows are not locked.
+ *
+ * @return true if windows that comprise the workbook are locked
+ */
+ public boolean isWindowsLocked() {
+ return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockWindows();
+ }
- /**
- * Specifies a boolean value that indicates whether the workbook is locked for revisions.
- *
- * @return true if the workbook is locked for revisions.
- */
- public boolean isRevisionLocked() {
- return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision();
- }
+ /**
+ * Specifies a boolean value that indicates whether the workbook is locked for revisions.
+ *
+ * @return true if the workbook is locked for revisions.
+ */
+ public boolean isRevisionLocked() {
+ return workbookProtectionPresent() && workbook.getWorkbookProtection().getLockRevision();
+ }
- /**
- * Locks the structure of workbook.
- */
- public void lockStructure() {
- safeGetWorkbookProtection().setLockStructure(true);
- }
+ /**
+ * Locks the structure of workbook.
+ */
+ public void lockStructure() {
+ safeGetWorkbookProtection().setLockStructure(true);
+ }
- /**
- * Unlocks the structure of workbook.
- */
- public void unLockStructure() {
- safeGetWorkbookProtection().setLockStructure(false);
- }
+ /**
+ * Unlocks the structure of workbook.
+ */
+ public void unLockStructure() {
+ safeGetWorkbookProtection().setLockStructure(false);
+ }
- /**
- * Locks the windows that comprise the workbook.
- */
- public void lockWindows() {
- safeGetWorkbookProtection().setLockWindows(true);
- }
+ /**
+ * Locks the windows that comprise the workbook.
+ */
+ public void lockWindows() {
+ safeGetWorkbookProtection().setLockWindows(true);
+ }
- /**
- * Unlocks the windows that comprise the workbook.
- */
- public void unLockWindows() {
- safeGetWorkbookProtection().setLockWindows(false);
- }
+ /**
+ * Unlocks the windows that comprise the workbook.
+ */
+ public void unLockWindows() {
+ safeGetWorkbookProtection().setLockWindows(false);
+ }
- /**
- * Locks the workbook for revisions.
- */
- public void lockRevision() {
- safeGetWorkbookProtection().setLockRevision(true);
- }
+ /**
+ * Locks the workbook for revisions.
+ */
+ public void lockRevision() {
+ safeGetWorkbookProtection().setLockRevision(true);
+ }
- /**
- * Unlocks the workbook for revisions.
- */
- public void unLockRevision() {
- safeGetWorkbookProtection().setLockRevision(false);
- }
+ /**
+ * Unlocks the workbook for revisions.
+ */
+ public void unLockRevision() {
+ safeGetWorkbookProtection().setLockRevision(false);
+ }
- /**
- * Sets the workbook password.
- *
- * @param password if null, the password will be removed
- * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier)
- * otherwise the given algorithm is used for calculating the hash password (Excel 2013)
- */
- public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) {
+ /**
+ * Sets the workbook password.
+ *
+ * @param password if null, the password will be removed
+ * @param hashAlgo if null, the password will be set as XOR password (Excel 2010 and earlier)
+ * otherwise the given algorithm is used for calculating the hash password (Excel 2013)
+ */
+ public void setWorkbookPassword(String password, HashAlgorithm hashAlgo) {
if (password == null && !workbookProtectionPresent()) return;
setPassword(safeGetWorkbookProtection(), password, hashAlgo, "workbook");
- }
+ }
/**
* Validate the password against the stored hash, the hashing method will be determined
@@ -2073,9 +2074,9 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
}
}
- private boolean workbookProtectionPresent() {
- return workbook.isSetWorkbookProtection();
- }
+ private boolean workbookProtectionPresent() {
+ return workbook.isSetWorkbookProtection();
+ }
private CTWorkbookProtection safeGetWorkbookProtection() {
if (!workbookProtectionPresent()){
@@ -2083,7 +2084,7 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
}
return workbook.getWorkbookProtection();
}
-
+
/**
*
* Returns the locator of user-defined functions.
@@ -2261,4 +2262,24 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook {
public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL2007;
}
+
+ /**
+ * Returns the data table with the given name (case insensitive).
+ *
+ * @param name the data table name (case-insensitive)
+ * @return The Data table in the workbook named name , or null if no table is named name .
+ * @since 3.15 beta 2
+ */
+ public XSSFTable getTable(String name) {
+ if (name != null && sheets != null) {
+ for (XSSFSheet sheet : sheets) {
+ for (XSSFTable tbl : sheet.getTables()) {
+ if (name.equalsIgnoreCase(tbl.getName())) {
+ return tbl;
+ }
+ }
+ }
+ }
+ return null;
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java
index a9d995fb7..8aa3de601 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFFormulaUtils.java
@@ -94,7 +94,7 @@ public final class XSSFFormulaUtils {
String formula = f.getStringValue();
if (formula != null && formula.length() > 0) {
int sheetIndex = _wb.getSheetIndex(cell.getSheet());
- Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.CELL, sheetIndex, cell.getRowIndex());
for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName);
}
@@ -113,7 +113,8 @@ public final class XSSFFormulaUtils {
String formula = name.getRefersToFormula();
if (formula != null) {
int sheetIndex = name.getSheetIndex();
- Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex);
+ int rowIndex = -1; //don't care
+ Ptg[] ptgs = FormulaParser.parse(formula, _fpwb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
for (Ptg ptg : ptgs) {
updatePtg(ptg, oldName, newName);
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java
index 80462ac4c..a1436cc64 100644
--- a/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java
+++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/helpers/XSSFRowShifter.java
@@ -134,8 +134,9 @@ public final class XSSFRowShifter {
XSSFName name = wb.getNameAt(i);
String formula = name.getRefersToFormula();
int sheetIndex = name.getSheetIndex();
+ final int rowIndex = -1; //don't care, named ranges are not allowed to include structured references
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.NAMEDRANGE, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
name.setRefersToFormula(shiftedFmla);
@@ -218,10 +219,11 @@ public final class XSSFRowShifter {
XSSFSheet sheet = row.getSheet();
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
+ final int rowIndex = row.getRowNum();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
try {
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
String shiftedFmla = null;
if (shifter.adjustFormula(ptgs, sheetIndex)) {
shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
@@ -238,6 +240,7 @@ public final class XSSFRowShifter {
public void updateConditionalFormatting(FormulaShifter shifter) {
XSSFWorkbook wb = sheet.getWorkbook();
int sheetIndex = wb.getSheetIndex(sheet);
+ final int rowIndex = -1; //don't care, structured references not allowed in conditional formatting
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
CTWorksheet ctWorksheet = sheet.getCTWorksheet();
@@ -283,7 +286,7 @@ public final class XSSFRowShifter {
String[] formulaArray = cfRule.getFormulaArray();
for (int i = 0; i < formulaArray.length; i++) {
String formula = formulaArray[i];
- Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex);
+ Ptg[] ptgs = FormulaParser.parse(formula, fpb, FormulaType.CELL, sheetIndex, rowIndex);
if (shifter.adjustFormula(ptgs, sheetIndex)) {
String shiftedFmla = FormulaRenderer.toFormulaString(fpb, ptgs);
cfRule.setFormulaArray(i, shiftedFmla);
diff --git a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java
index 9987dcf42..9f40ca363 100644
--- a/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java
+++ b/src/ooxml/testcases/org/apache/poi/ss/formula/TestFormulaParser.java
@@ -18,6 +18,11 @@
*/
package org.apache.poi.ss.formula;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.ptg.AbstractFunctionPtg;
@@ -28,7 +33,7 @@ import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFEvaluationWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
-import junit.framework.TestCase;
+import org.junit.Test;
/**
* Test {@link FormulaParser}'s handling of row numbers at the edge of the
@@ -36,8 +41,9 @@ import junit.framework.TestCase;
*
* @author David North
*/
-public class TestFormulaParser extends TestCase {
+public class TestFormulaParser {
+ @Test
public void testHSSFFailsForOver65536() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
try {
@@ -49,16 +55,19 @@ public class TestFormulaParser extends TestCase {
}
}
+ @Test
public void testHSSFPassCase() {
FormulaParsingWorkbook workbook = HSSFEvaluationWorkbook.create(new HSSFWorkbook());
FormulaParser.parse("Sheet1!1:65536", workbook, FormulaType.CELL, 0);
}
+ @Test
public void testXSSFWorksForOver65536() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
FormulaParser.parse("Sheet1!1:65537", workbook, FormulaType.CELL, 0);
}
+ @Test
public void testXSSFFailCase() {
FormulaParsingWorkbook workbook = XSSFEvaluationWorkbook.create(new XSSFWorkbook());
try {
@@ -71,6 +80,7 @@ public class TestFormulaParser extends TestCase {
}
// copied from org.apache.poi.hssf.model.TestFormulaParser
+ @Test
public void testMacroFunction() throws Exception {
// testNames.xlsm contains a VB function called 'myFunc'
final String testFile = "testNames.xlsm";
@@ -126,6 +136,7 @@ public class TestFormulaParser extends TestCase {
}
}
+ @Test
public void testParserErrors() throws Exception {
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("testNames.xlsm");
try {
diff --git a/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java b/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java
new file mode 100644
index 000000000..2fb7cb89f
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/ss/formula/TestStructuredReferences.java
@@ -0,0 +1,63 @@
+package org.apache.poi.ss.formula;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellValue;
+import org.apache.poi.ss.usermodel.FormulaEvaluator;
+import org.apache.poi.ss.usermodel.Table;
+import org.apache.poi.xssf.XSSFTestDataSamples;
+import org.apache.poi.xssf.usermodel.XSSFFormulaEvaluator;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.Test;
+
+/**
+ * Tests Excel Table expressions (structured references)
+ * @see
+ * Excel Structured Reference Syntax
+ *
+ */
+public class TestStructuredReferences {
+
+ /**
+ * Test the regular expression used in INDIRECT() evaluation to recognize structured references
+ */
+ @Test
+ public void testTableExpressionSyntax() {
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("abc[col1]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_abc[col1]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("_[col1]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[col1]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[#This Row]").matches());
+ assertTrue("Valid structured reference syntax didn't match expression", Table.isStructuredReference.matcher("\\[ [col1], [col2] ]").matches());
+
+ // can't have a space between the table name and open bracket
+ assertFalse("Invalid structured reference syntax didn't fail expression", Table.isStructuredReference.matcher("\\abc [ [col1], [col2] ]").matches());
+ }
+
+ @Test
+ public void testTableFormulas() throws Exception {
+ XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
+ try {
+
+ final FormulaEvaluator eval = new XSSFFormulaEvaluator(wb);
+ confirm(eval, wb.getSheet("Table").getRow(5).getCell(0), 49);
+ confirm(eval, wb.getSheet("Formulas").getRow(0).getCell(0), 209);
+ } finally {
+ wb.close();
+ }
+ }
+
+ private static void confirm(FormulaEvaluator fe, Cell cell, double expectedResult) {
+ fe.clearAllCachedResultValues();
+ CellValue cv = fe.evaluate(cell);
+ if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
+ fail("expected numeric cell type but got " + cv.formatAsString());
+ }
+ assertEquals(expectedResult, cv.getNumberValue(), 0.0);
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java
index 83dabacb9..08c4ed298 100644
--- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java
+++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFFormulaParser.java
@@ -22,6 +22,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import java.io.IOException;
+
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFEvaluationWorkbook;
@@ -41,12 +43,15 @@ import org.junit.Test;
import java.util.Arrays;
public final class TestXSSFFormulaParser {
- private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
- return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
- }
+ private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla) {
+ return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1);
+ }
+ private static Ptg[] parse(FormulaParsingWorkbook fpb, String fmla, int rowIndex) {
+ return FormulaParser.parse(fmla, fpb, FormulaType.CELL, -1, rowIndex);
+ }
- @Test
- public void basicParsing() {
+ @Test
+ public void basicParsing() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -118,10 +123,12 @@ public final class TestXSSFFormulaParser {
assertEquals(AttrPtg.class, ptgs[1].getClass());
assertEquals("Sheet1!A1:B3", ptgs[0].toFormulaString());
assertEquals("SUM", ptgs[1].toFormulaString());
+
+ wb.close();
}
- @Test
- public void builtInFormulas() {
+ @Test
+ public void builtInFormulas() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -134,10 +141,12 @@ public final class TestXSSFFormulaParser {
assertEquals(2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof IntPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof FuncPtg);
+
+ wb.close();
}
@Test
- public void formaulReferncesSameWorkbook() {
+ public void formulaReferencesSameWorkbook() throws IOException {
// Use a test file with "other workbook" style references
// to itself
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
@@ -153,10 +162,12 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[0]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
+
+ wb.close();
}
- @Test
- public void formulaReferencesOtherSheets() {
+ @Test
+ public void formulaReferencesOtherSheets() throws IOException {
// Use a test file with the named ranges in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@@ -193,10 +204,12 @@ public final class TestXSSFFormulaParser {
assertEquals(1, ptgs.length);
assertEquals(NamePtg.class, ptgs[0].getClass());
assertEquals("NR_Global_B2",((NamePtg)ptgs[0]).toFormulaString(fpb));
+
+ wb.close();
}
@Test
- public void formulaReferencesOtherWorkbook() {
+ public void formulaReferencesOtherWorkbook() throws IOException {
// Use a test file with the external linked table in place
XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("ref-56737.xlsx");
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
@@ -228,6 +241,8 @@ public final class TestXSSFFormulaParser {
assertEquals(null, ((NameXPxg)ptgs[0]).getSheetName());
assertEquals("NR_Global_B2",((NameXPxg)ptgs[0]).getNameName());
assertEquals("[1]!NR_Global_B2",((NameXPxg)ptgs[0]).toFormulaString());
+
+ wb.close();
}
/**
@@ -241,7 +256,7 @@ public final class TestXSSFFormulaParser {
* (but not evaluate - that's elsewhere in the test suite)
*/
@Test
- public void multiSheetReferencesHSSFandXSSF() throws Exception {
+ public void multiSheetReferencesHSSFandXSSF() throws IOException {
Workbook[] wbs = new Workbook[] {
HSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xls"),
XSSFTestDataSamples.openSampleWorkbook("55906-MultiSheetRefs.xlsx")
@@ -363,6 +378,8 @@ public final class TestXSSFFormulaParser {
newF = s1.getRow(0).createCell(11, Cell.CELL_TYPE_FORMULA);
newF.setCellFormula("MIN(Sheet1:Sheet2!A1:B2)");
assertEquals("MIN(Sheet1:Sheet2!A1:B2)", newF.getCellFormula());
+
+ wb.close();
}
}
@@ -374,7 +391,7 @@ public final class TestXSSFFormulaParser {
}
@Test
- public void test58648Single() {
+ public void test58648Single() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -384,10 +401,12 @@ public final class TestXSSFFormulaParser {
2, ptgs.length);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
+
+ wb.close();
}
@Test
- public void test58648Basic() {
+ public void test58648Basic() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -431,10 +450,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[0] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof ParenthesisPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof ParenthesisPtg);
+
+ wb.close();
}
@Test
- public void test58648FormulaParsing() {
+ public void test58648FormulaParsing() throws IOException {
Workbook wb = XSSFTestDataSamples.openSampleWorkbook("58648.xlsx");
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
@@ -460,10 +481,12 @@ public final class TestXSSFFormulaParser {
Cell cell = sheet.getRow(1).getCell(4);
assertEquals(5d, cell.getNumericCellValue(), 0d);
+
+ wb.close();
}
@Test
- public void testWhitespaceInFormula() {
+ public void testWhitespaceInFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -505,10 +528,12 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof IntersectionPtg);
+
+ wb.close();
}
@Test
- public void testWhitespaceInComplexFormula() {
+ public void testWhitespaceInComplexFormula() throws IOException {
XSSFWorkbook wb = new XSSFWorkbook();
XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
Ptg[] ptgs;
@@ -529,5 +554,172 @@ public final class TestXSSFFormulaParser {
assertTrue("Had " + Arrays.toString(ptgs), ptgs[1] instanceof RefPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[2] instanceof AreaPtg);
assertTrue("Had " + Arrays.toString(ptgs), ptgs[3] instanceof NameXPxg);
+
+ wb.close();
+ }
+
+ @Test
+ public void parseStructuredReferences() throws IOException {
+ XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("StructuredReferences.xlsx");
+
+ XSSFEvaluationWorkbook fpb = XSSFEvaluationWorkbook.create(wb);
+ Ptg[] ptgs;
+
+ /*
+ The following cases are tested (copied from FormulaParser.parseStructuredReference)
+ 1 Table1[col]
+ 2 Table1[[#Totals],[col]]
+ 3 Table1[#Totals]
+ 4 Table1[#All]
+ 5 Table1[#Data]
+ 6 Table1[#Headers]
+ 7 Table1[#Totals]
+ 8 Table1[#This Row]
+ 9 Table1[[#All],[col]]
+ 10 Table1[[#Headers],[col]]
+ 11 Table1[[#Totals],[col]]
+ 12 Table1[[#All],[col1]:[col2]]
+ 13 Table1[[#Data],[col1]:[col2]]
+ 14 Table1[[#Headers],[col1]:[col2]]
+ 15 Table1[[#Totals],[col1]:[col2]]
+ 16 Table1[[#Headers],[#Data],[col2]]
+ 17 Table1[[#This Row], [col1]]
+ 18 Table1[ [col1]:[col2] ]
+ */
+
+ final String tbl = "\\_Prime.1";
+ final String noTotalsRowReason = ": Tables without a Totals row should return #REF! on [#Totals]";
+
+ ////// Case 1: Evaluate Table1[col] with apostrophe-escaped #-signs ////////
+ ptgs = parse(fpb, "SUM("+tbl+"[calc='#*'#])");
+ assertEquals(2, ptgs.length);
+
+ // Area3DPxg [sheet=Table ! A2:A7]
+ assertTrue(ptgs[0] instanceof Area3DPxg);
+ Area3DPxg ptg0 = (Area3DPxg) ptgs[0];
+ assertEquals("Table", ptg0.getSheetName());
+ assertEquals("A2:A7", ptg0.format2DRefAsString());
+ // Note: structured references are evaluated and resolved to regular 3D area references.
+ assertEquals("Table!A2:A7", ptg0.toFormulaString());
+
+ // AttrPtg [sum ]
+ assertTrue(ptgs[1] instanceof AttrPtg);
+ AttrPtg ptg1 = (AttrPtg) ptgs[1];
+ assertTrue(ptg1.isSum());
+
+ ////// Case 1: Evaluate "Table1[col]" ////////
+ ptgs = parse(fpb, tbl+"[Name]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[col]", "Table!B2:B7", ptgs[0].toFormulaString());
+
+ ////// Case 2: Evaluate "Table1[[#Totals],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Totals],[col]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
+
+ ////// Case 3: Evaluate "Table1[#Totals]" ////////
+ ptgs = parse(fpb, tbl+"[#Totals]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
+
+ ////// Case 4: Evaluate "Table1[#All]" ////////
+ ptgs = parse(fpb, tbl+"[#All]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#All]", "Table!A1:C7", ptgs[0].toFormulaString());
+
+ ////// Case 5: Evaluate "Table1[#Data]" (excludes Header and Data rows) ////////
+ ptgs = parse(fpb, tbl+"[#Data]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#Data]", "Table!A2:C7", ptgs[0].toFormulaString());
+
+ ////// Case 6: Evaluate "Table1[#Headers]" ////////
+ ptgs = parse(fpb, tbl+"[#Headers]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#Headers]", "Table!A1:C1", ptgs[0].toFormulaString());
+
+ ////// Case 7: Evaluate "Table1[#Totals]" ////////
+ ptgs = parse(fpb, tbl+"[#Totals]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#Totals]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
+
+ ////// Case 8: Evaluate "Table1[#This Row]" ////////
+ ptgs = parse(fpb, tbl+"[#This Row]", 2);
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#This Row]", "Table!A3:C3", ptgs[0].toFormulaString());
+
+ ////// Evaluate "Table1[@]" (equivalent to "Table1[#This Row]") ////////
+ ptgs = parse(fpb, tbl+"[@]", 2);
+ assertEquals(1, ptgs.length);
+ assertEquals("Table!A3:C3", ptgs[0].toFormulaString());
+
+ ////// Evaluate "Table1[#This Row]" when rowIndex is outside Table ////////
+ ptgs = parse(fpb, tbl+"[#This Row]", 10);
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[#This Row]", ErrPtg.VALUE_INVALID, ptgs[0]);
+
+ ////// Evaluate "Table1[@]" when rowIndex is outside Table ////////
+ ptgs = parse(fpb, tbl+"[@]", 10);
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[@]", ErrPtg.VALUE_INVALID, ptgs[0]);
+
+ ////// Evaluate "Table1[[#Data],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Data], [Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Data],[col]]", "Table!C2:C7", ptgs[0].toFormulaString());
+
+
+ ////// Case 9: Evaluate "Table1[[#All],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#All], [Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#All],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
+
+ ////// Case 10: Evaluate "Table1[[#Headers],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Headers], [Number]]");
+ assertEquals(1, ptgs.length);
+ // also acceptable: Table1!B1
+ assertEquals("Table1[[#Headers],[col]]", "Table!C1:C1", ptgs[0].toFormulaString());
+
+ ////// Case 11: Evaluate "Table1[[#Totals],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Totals],[Name]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Totals],[col]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
+
+ ////// Case 12: Evaluate "Table1[[#All],[col1]:[col2]]" ////////
+ ptgs = parse(fpb, tbl+"[[#All], [Name]:[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#All],[col1]:[col2]]", "Table!B1:C7", ptgs[0].toFormulaString());
+
+ ////// Case 13: Evaluate "Table1[[#Data],[col]:[col2]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Data], [Name]:[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Data],[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
+
+ ////// Case 14: Evaluate "Table1[[#Headers],[col1]:[col2]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Headers], [Name]:[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Headers],[col1]:[col2]]", "Table!B1:C1", ptgs[0].toFormulaString());
+
+ ////// Case 15: Evaluate "Table1[[#Totals],[col]:[col2]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Totals], [Name]:[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Totals],[col]:[col2]]" + noTotalsRowReason, ErrPtg.REF_INVALID, ptgs[0]);
+
+ ////// Case 16: Evaluate "Table1[[#Headers],[#Data],[col]]" ////////
+ ptgs = parse(fpb, tbl+"[[#Headers],[#Data],[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[#Headers],[#Data],[col]]", "Table!C1:C7", ptgs[0].toFormulaString());
+
+ ////// Case 17: Evaluate "Table1[[#This Row], [col1]]" ////////
+ ptgs = parse(fpb, tbl+"[[#This Row], [Number]]", 2);
+ assertEquals(1, ptgs.length);
+ // also acceptable: Table!C3
+ assertEquals("Table1[[#This Row], [col1]]", "Table!C3:C3", ptgs[0].toFormulaString());
+
+ ////// Case 18: Evaluate "Table1[[col]:[col2]]" ////////
+ ptgs = parse(fpb, tbl+"[[Name]:[Number]]");
+ assertEquals(1, ptgs.length);
+ assertEquals("Table1[[col]:[col2]]", "Table!B2:C7", ptgs[0].toFormulaString());
+
+ wb.close();
}
}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
index 6b1eebe65..868557ae1 100644
--- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
+++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFSheet.java
@@ -1137,7 +1137,7 @@ public final class TestXSSFSheet extends BaseTestXSheet {
}
/**
- * See bug #50829
+ * See bug #50829 test data tables
*/
@Test
public void tables() throws IOException {
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java
index 6e642ccca..81eed92ca 100644
--- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java
+++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFWorkbook.java
@@ -79,432 +79,432 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.STCalcMode;
public final class TestXSSFWorkbook extends BaseTestXWorkbook {
- public TestXSSFWorkbook() {
- super(XSSFITestDataProvider.instance);
- }
+ public TestXSSFWorkbook() {
+ super(XSSFITestDataProvider.instance);
+ }
- /**
- * Tests that we can save, and then re-load a new document
- */
- @Test
- public void saveLoadNew() throws IOException, InvalidFormatException {
+ /**
+ * Tests that we can save, and then re-load a new document
+ */
+ @Test
+ public void saveLoadNew() throws IOException, InvalidFormatException {
XSSFWorkbook wb1 = new XSSFWorkbook();
- //check that the default date system is set to 1900
- CTWorkbookPr pr = wb1.getCTWorkbook().getWorkbookPr();
- assertNotNull(pr);
- assertTrue(pr.isSetDate1904());
- assertFalse("XSSF must use the 1900 date system", pr.getDate1904());
+ //check that the default date system is set to 1900
+ CTWorkbookPr pr = wb1.getCTWorkbook().getWorkbookPr();
+ assertNotNull(pr);
+ assertTrue(pr.isSetDate1904());
+ assertFalse("XSSF must use the 1900 date system", pr.getDate1904());
- Sheet sheet1 = wb1.createSheet("sheet1");
- Sheet sheet2 = wb1.createSheet("sheet2");
- wb1.createSheet("sheet3");
+ Sheet sheet1 = wb1.createSheet("sheet1");
+ Sheet sheet2 = wb1.createSheet("sheet2");
+ wb1.createSheet("sheet3");
- RichTextString rts = wb1.getCreationHelper().createRichTextString("hello world");
+ RichTextString rts = wb1.getCreationHelper().createRichTextString("hello world");
- sheet1.createRow(0).createCell((short)0).setCellValue(1.2);
- sheet1.createRow(1).createCell((short)0).setCellValue(rts);
- sheet2.createRow(0);
+ sheet1.createRow(0).createCell((short)0).setCellValue(1.2);
+ sheet1.createRow(1).createCell((short)0).setCellValue(rts);
+ sheet2.createRow(0);
- assertEquals(0, wb1.getSheetAt(0).getFirstRowNum());
- assertEquals(1, wb1.getSheetAt(0).getLastRowNum());
- assertEquals(0, wb1.getSheetAt(1).getFirstRowNum());
- assertEquals(0, wb1.getSheetAt(1).getLastRowNum());
- assertEquals(0, wb1.getSheetAt(2).getFirstRowNum());
- assertEquals(0, wb1.getSheetAt(2).getLastRowNum());
+ assertEquals(0, wb1.getSheetAt(0).getFirstRowNum());
+ assertEquals(1, wb1.getSheetAt(0).getLastRowNum());
+ assertEquals(0, wb1.getSheetAt(1).getFirstRowNum());
+ assertEquals(0, wb1.getSheetAt(1).getLastRowNum());
+ assertEquals(0, wb1.getSheetAt(2).getFirstRowNum());
+ assertEquals(0, wb1.getSheetAt(2).getLastRowNum());
- File file = TempFile.createTempFile("poi-", ".xlsx");
- OutputStream out = new FileOutputStream(file);
- wb1.write(out);
- out.close();
+ File file = TempFile.createTempFile("poi-", ".xlsx");
+ OutputStream out = new FileOutputStream(file);
+ wb1.write(out);
+ out.close();
- // Check the package contains what we'd expect it to
- OPCPackage pkg = OPCPackage.open(file.toString());
- PackagePart wbRelPart =
- pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels"));
- assertNotNull(wbRelPart);
- assertTrue(wbRelPart.isRelationshipPart());
- assertEquals(ContentTypes.RELATIONSHIPS_PART, wbRelPart.getContentType());
+ // Check the package contains what we'd expect it to
+ OPCPackage pkg = OPCPackage.open(file.toString());
+ PackagePart wbRelPart =
+ pkg.getPart(PackagingURIHelper.createPartName("/xl/_rels/workbook.xml.rels"));
+ assertNotNull(wbRelPart);
+ assertTrue(wbRelPart.isRelationshipPart());
+ assertEquals(ContentTypes.RELATIONSHIPS_PART, wbRelPart.getContentType());
- PackagePart wbPart =
- pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
- // Links to the three sheets, shared strings and styles
- assertTrue(wbPart.hasRelationships());
- assertEquals(5, wbPart.getRelationships().size());
- wb1.close();
+ PackagePart wbPart =
+ pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
+ // Links to the three sheets, shared strings and styles
+ assertTrue(wbPart.hasRelationships());
+ assertEquals(5, wbPart.getRelationships().size());
+ wb1.close();
- // Load back the XSSFWorkbook
- @SuppressWarnings("resource")
+ // Load back the XSSFWorkbook
+ @SuppressWarnings("resource")
XSSFWorkbook wb2 = new XSSFWorkbook(pkg);
- assertEquals(3, wb2.getNumberOfSheets());
- assertNotNull(wb2.getSheetAt(0));
- assertNotNull(wb2.getSheetAt(1));
- assertNotNull(wb2.getSheetAt(2));
+ assertEquals(3, wb2.getNumberOfSheets());
+ assertNotNull(wb2.getSheetAt(0));
+ assertNotNull(wb2.getSheetAt(1));
+ assertNotNull(wb2.getSheetAt(2));
- assertNotNull(wb2.getSharedStringSource());
- assertNotNull(wb2.getStylesSource());
+ assertNotNull(wb2.getSharedStringSource());
+ assertNotNull(wb2.getStylesSource());
- assertEquals(0, wb2.getSheetAt(0).getFirstRowNum());
- assertEquals(1, wb2.getSheetAt(0).getLastRowNum());
- assertEquals(0, wb2.getSheetAt(1).getFirstRowNum());
- assertEquals(0, wb2.getSheetAt(1).getLastRowNum());
- assertEquals(0, wb2.getSheetAt(2).getFirstRowNum());
- assertEquals(0, wb2.getSheetAt(2).getLastRowNum());
+ assertEquals(0, wb2.getSheetAt(0).getFirstRowNum());
+ assertEquals(1, wb2.getSheetAt(0).getLastRowNum());
+ assertEquals(0, wb2.getSheetAt(1).getFirstRowNum());
+ assertEquals(0, wb2.getSheetAt(1).getLastRowNum());
+ assertEquals(0, wb2.getSheetAt(2).getFirstRowNum());
+ assertEquals(0, wb2.getSheetAt(2).getLastRowNum());
- sheet1 = wb2.getSheetAt(0);
- assertEquals(1.2, sheet1.getRow(0).getCell(0).getNumericCellValue(), 0.0001);
- assertEquals("hello world", sheet1.getRow(1).getCell(0).getRichStringCellValue().getString());
+ sheet1 = wb2.getSheetAt(0);
+ assertEquals(1.2, sheet1.getRow(0).getCell(0).getNumericCellValue(), 0.0001);
+ assertEquals("hello world", sheet1.getRow(1).getCell(0).getRichStringCellValue().getString());
- pkg.close();
- }
+ pkg.close();
+ }
@Test
- public void existing() throws Exception {
+ public void existing() throws Exception {
- XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
- assertNotNull(workbook.getSharedStringSource());
- assertNotNull(workbook.getStylesSource());
+ XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
+ assertNotNull(workbook.getSharedStringSource());
+ assertNotNull(workbook.getStylesSource());
- // And check a few low level bits too
- OPCPackage pkg = OPCPackage.open(HSSFTestDataSamples.openSampleFileStream("Formatting.xlsx"));
- PackagePart wbPart =
- pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
+ // And check a few low level bits too
+ OPCPackage pkg = OPCPackage.open(HSSFTestDataSamples.openSampleFileStream("Formatting.xlsx"));
+ PackagePart wbPart =
+ pkg.getPart(PackagingURIHelper.createPartName("/xl/workbook.xml"));
- // Links to the three sheets, shared, styles and themes
- assertTrue(wbPart.hasRelationships());
- assertEquals(6, wbPart.getRelationships().size());
+ // Links to the three sheets, shared, styles and themes
+ assertTrue(wbPart.hasRelationships());
+ assertEquals(6, wbPart.getRelationships().size());
- pkg.close();
- workbook.close();
- }
+ pkg.close();
+ workbook.close();
+ }
@Test
- public void getCellStyleAt() throws IOException{
- XSSFWorkbook workbook = new XSSFWorkbook();
- try {
- short i = 0;
- //get default style
- CellStyle cellStyleAt = workbook.getCellStyleAt(i);
- assertNotNull(cellStyleAt);
+ public void getCellStyleAt() throws IOException{
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ try {
+ short i = 0;
+ //get default style
+ CellStyle cellStyleAt = workbook.getCellStyleAt(i);
+ assertNotNull(cellStyleAt);
- //get custom style
- StylesTable styleSource = workbook.getStylesSource();
- XSSFCellStyle customStyle = new XSSFCellStyle(styleSource);
- XSSFFont font = new XSSFFont();
- font.setFontName("Verdana");
- customStyle.setFont(font);
- int x = styleSource.putStyle(customStyle);
- cellStyleAt = workbook.getCellStyleAt((short)x);
- assertNotNull(cellStyleAt);
- } finally {
- workbook.close();
- }
- }
+ //get custom style
+ StylesTable styleSource = workbook.getStylesSource();
+ XSSFCellStyle customStyle = new XSSFCellStyle(styleSource);
+ XSSFFont font = new XSSFFont();
+ font.setFontName("Verdana");
+ customStyle.setFont(font);
+ int x = styleSource.putStyle(customStyle);
+ cellStyleAt = workbook.getCellStyleAt((short)x);
+ assertNotNull(cellStyleAt);
+ } finally {
+ workbook.close();
+ }
+ }
@Test
- public void getFontAt() throws IOException{
- XSSFWorkbook workbook = new XSSFWorkbook();
- try {
- StylesTable styleSource = workbook.getStylesSource();
- short i = 0;
- //get default font
- Font fontAt = workbook.getFontAt(i);
- assertNotNull(fontAt);
+ public void getFontAt() throws IOException{
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ try {
+ StylesTable styleSource = workbook.getStylesSource();
+ short i = 0;
+ //get default font
+ Font fontAt = workbook.getFontAt(i);
+ assertNotNull(fontAt);
- //get customized font
- XSSFFont customFont = new XSSFFont();
- customFont.setItalic(true);
- int x = styleSource.putFont(customFont);
- fontAt = workbook.getFontAt((short)x);
- assertNotNull(fontAt);
- } finally {
- workbook.close();
- }
- }
+ //get customized font
+ XSSFFont customFont = new XSSFFont();
+ customFont.setItalic(true);
+ int x = styleSource.putFont(customFont);
+ fontAt = workbook.getFontAt((short)x);
+ assertNotNull(fontAt);
+ } finally {
+ workbook.close();
+ }
+ }
@Test
- public void getNumCellStyles() throws IOException{
- XSSFWorkbook workbook = new XSSFWorkbook();
- try {
- //get default cellStyles
- assertEquals(1, workbook.getNumCellStyles());
- } finally {
- workbook.close();
- }
- }
+ public void getNumCellStyles() throws IOException{
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ try {
+ //get default cellStyles
+ assertEquals(1, workbook.getNumCellStyles());
+ } finally {
+ workbook.close();
+ }
+ }
@Test
- public void loadSave() throws IOException {
- XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
- assertEquals(3, workbook.getNumberOfSheets());
- assertEquals("dd/mm/yyyy", workbook.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString());
- assertNotNull(workbook.getSharedStringSource());
- assertNotNull(workbook.getStylesSource());
+ public void loadSave() throws IOException {
+ XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
+ assertEquals(3, workbook.getNumberOfSheets());
+ assertEquals("dd/mm/yyyy", workbook.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString());
+ assertNotNull(workbook.getSharedStringSource());
+ assertNotNull(workbook.getStylesSource());
- // Write out, and check
- // Load up again, check all still there
- XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertEquals(3, wb2.getNumberOfSheets());
- assertNotNull(wb2.getSheetAt(0));
- assertNotNull(wb2.getSheetAt(1));
- assertNotNull(wb2.getSheetAt(2));
+ // Write out, and check
+ // Load up again, check all still there
+ XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertEquals(3, wb2.getNumberOfSheets());
+ assertNotNull(wb2.getSheetAt(0));
+ assertNotNull(wb2.getSheetAt(1));
+ assertNotNull(wb2.getSheetAt(2));
- assertEquals("dd/mm/yyyy", wb2.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString());
- assertEquals("yyyy/mm/dd", wb2.getSheetAt(0).getRow(2).getCell(0).getRichStringCellValue().getString());
- assertEquals("yyyy-mm-dd", wb2.getSheetAt(0).getRow(3).getCell(0).getRichStringCellValue().getString());
- assertEquals("yy/mm/dd", wb2.getSheetAt(0).getRow(4).getCell(0).getRichStringCellValue().getString());
- assertNotNull(wb2.getSharedStringSource());
- assertNotNull(wb2.getStylesSource());
+ assertEquals("dd/mm/yyyy", wb2.getSheetAt(0).getRow(1).getCell(0).getRichStringCellValue().getString());
+ assertEquals("yyyy/mm/dd", wb2.getSheetAt(0).getRow(2).getCell(0).getRichStringCellValue().getString());
+ assertEquals("yyyy-mm-dd", wb2.getSheetAt(0).getRow(3).getCell(0).getRichStringCellValue().getString());
+ assertEquals("yy/mm/dd", wb2.getSheetAt(0).getRow(4).getCell(0).getRichStringCellValue().getString());
+ assertNotNull(wb2.getSharedStringSource());
+ assertNotNull(wb2.getStylesSource());
- workbook.close();
- wb2.close();
- }
+ workbook.close();
+ wb2.close();
+ }
@Test
- public void styles() throws IOException {
- XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
+ public void styles() throws IOException {
+ XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("Formatting.xlsx");
- StylesTable ss = wb1.getStylesSource();
- assertNotNull(ss);
- StylesTable st = ss;
+ StylesTable ss = wb1.getStylesSource();
+ assertNotNull(ss);
+ StylesTable st = ss;
- // Has 8 number formats
- assertEquals(8, st.getNumDataFormats());
- // Has 2 fonts
- assertEquals(2, st.getFonts().size());
- // Has 2 fills
- assertEquals(2, st.getFills().size());
- // Has 1 border
- assertEquals(1, st.getBorders().size());
+ // Has 8 number formats
+ assertEquals(8, st.getNumDataFormats());
+ // Has 2 fonts
+ assertEquals(2, st.getFonts().size());
+ // Has 2 fills
+ assertEquals(2, st.getFills().size());
+ // Has 1 border
+ assertEquals(1, st.getBorders().size());
- // Add two more styles
- assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
- st.putNumberFormat("testFORMAT"));
- assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
- st.putNumberFormat("testFORMAT"));
- assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 9,
- st.putNumberFormat("testFORMAT2"));
- assertEquals(10, st.getNumDataFormats());
+ // Add two more styles
+ assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
+ st.putNumberFormat("testFORMAT"));
+ assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 8,
+ st.putNumberFormat("testFORMAT"));
+ assertEquals(StylesTable.FIRST_CUSTOM_STYLE_ID + 9,
+ st.putNumberFormat("testFORMAT2"));
+ assertEquals(10, st.getNumDataFormats());
- // Save, load back in again, and check
- XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1);
- wb1.close();
+ // Save, load back in again, and check
+ XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1);
+ wb1.close();
- ss = wb2.getStylesSource();
- assertNotNull(ss);
+ ss = wb2.getStylesSource();
+ assertNotNull(ss);
- assertEquals(10, st.getNumDataFormats());
- assertEquals(2, st.getFonts().size());
- assertEquals(2, st.getFills().size());
- assertEquals(1, st.getBorders().size());
- wb2.close();
- }
+ assertEquals(10, st.getNumDataFormats());
+ assertEquals(2, st.getFonts().size());
+ assertEquals(2, st.getFills().size());
+ assertEquals(1, st.getBorders().size());
+ wb2.close();
+ }
@Test
- public void incrementSheetId() throws IOException {
- XSSFWorkbook wb = new XSSFWorkbook();
- try {
- int sheetId = (int)wb.createSheet().sheet.getSheetId();
- assertEquals(1, sheetId);
- sheetId = (int)wb.createSheet().sheet.getSheetId();
- assertEquals(2, sheetId);
+ public void incrementSheetId() throws IOException {
+ XSSFWorkbook wb = new XSSFWorkbook();
+ try {
+ int sheetId = (int)wb.createSheet().sheet.getSheetId();
+ assertEquals(1, sheetId);
+ sheetId = (int)wb.createSheet().sheet.getSheetId();
+ assertEquals(2, sheetId);
- //test file with gaps in the sheetId sequence
- XSSFWorkbook wbBack = XSSFTestDataSamples.openSampleWorkbook("47089.xlsm");
- try {
- int lastSheetId = (int)wbBack.getSheetAt(wbBack.getNumberOfSheets() - 1).sheet.getSheetId();
- sheetId = (int)wbBack.createSheet().sheet.getSheetId();
- assertEquals(lastSheetId+1, sheetId);
- } finally {
- wbBack.close();
- }
- } finally {
- wb.close();
- }
- }
+ //test file with gaps in the sheetId sequence
+ XSSFWorkbook wbBack = XSSFTestDataSamples.openSampleWorkbook("47089.xlsm");
+ try {
+ int lastSheetId = (int)wbBack.getSheetAt(wbBack.getNumberOfSheets() - 1).sheet.getSheetId();
+ sheetId = (int)wbBack.createSheet().sheet.getSheetId();
+ assertEquals(lastSheetId+1, sheetId);
+ } finally {
+ wbBack.close();
+ }
+ } finally {
+ wb.close();
+ }
+ }
- /**
- * Test setting of core properties such as Title and Author
- * @throws IOException
- */
+ /**
+ * Test setting of core properties such as Title and Author
+ * @throws IOException
+ */
@Test
- public void workbookProperties() throws IOException {
- XSSFWorkbook workbook = new XSSFWorkbook();
- try {
- POIXMLProperties props = workbook.getProperties();
- assertNotNull(props);
- //the Application property must be set for new workbooks, see Bugzilla #47559
- assertEquals("Apache POI", props.getExtendedProperties().getUnderlyingProperties().getApplication());
+ public void workbookProperties() throws IOException {
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ try {
+ POIXMLProperties props = workbook.getProperties();
+ assertNotNull(props);
+ //the Application property must be set for new workbooks, see Bugzilla #47559
+ assertEquals("Apache POI", props.getExtendedProperties().getUnderlyingProperties().getApplication());
- PackagePropertiesPart opcProps = props.getCoreProperties().getUnderlyingProperties();
- assertNotNull(opcProps);
+ PackagePropertiesPart opcProps = props.getCoreProperties().getUnderlyingProperties();
+ assertNotNull(opcProps);
- opcProps.setTitleProperty("Testing Bugzilla #47460");
- assertEquals("Apache POI", opcProps.getCreatorProperty().getValue());
- opcProps.setCreatorProperty("poi-dev@poi.apache.org");
+ opcProps.setTitleProperty("Testing Bugzilla #47460");
+ assertEquals("Apache POI", opcProps.getCreatorProperty().getValue());
+ opcProps.setCreatorProperty("poi-dev@poi.apache.org");
- XSSFWorkbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertEquals("Apache POI", wbBack.getProperties().getExtendedProperties().getUnderlyingProperties().getApplication());
- opcProps = wbBack.getProperties().getCoreProperties().getUnderlyingProperties();
- assertEquals("Testing Bugzilla #47460", opcProps.getTitleProperty().getValue());
- assertEquals("poi-dev@poi.apache.org", opcProps.getCreatorProperty().getValue());
- wbBack.close();
- } finally {
- workbook.close();
- }
- }
+ XSSFWorkbook wbBack = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertEquals("Apache POI", wbBack.getProperties().getExtendedProperties().getUnderlyingProperties().getApplication());
+ opcProps = wbBack.getProperties().getCoreProperties().getUnderlyingProperties();
+ assertEquals("Testing Bugzilla #47460", opcProps.getTitleProperty().getValue());
+ assertEquals("poi-dev@poi.apache.org", opcProps.getCreatorProperty().getValue());
+ wbBack.close();
+ } finally {
+ workbook.close();
+ }
+ }
- /**
- * Verify that the attached test data was not modified. If this test method
- * fails, the test data is not working properly.
- */
+ /**
+ * Verify that the attached test data was not modified. If this test method
+ * fails, the test data is not working properly.
+ */
@Test
- public void bug47668() throws Exception {
- XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("47668.xlsx");
- List allPictures = workbook.getAllPictures();
- assertEquals(1, allPictures.size());
+ public void bug47668() throws Exception {
+ XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook("47668.xlsx");
+ List allPictures = workbook.getAllPictures();
+ assertEquals(1, allPictures.size());
- PackagePartName imagePartName = PackagingURIHelper
- .createPartName("/xl/media/image1.jpeg");
- PackagePart imagePart = workbook.getPackage().getPart(imagePartName);
- assertNotNull(imagePart);
+ PackagePartName imagePartName = PackagingURIHelper
+ .createPartName("/xl/media/image1.jpeg");
+ PackagePart imagePart = workbook.getPackage().getPart(imagePartName);
+ assertNotNull(imagePart);
- for (XSSFPictureData pictureData : allPictures) {
- PackagePart picturePart = pictureData.getPackagePart();
- assertSame(imagePart, picturePart);
- }
+ for (XSSFPictureData pictureData : allPictures) {
+ PackagePart picturePart = pictureData.getPackagePart();
+ assertSame(imagePart, picturePart);
+ }
- XSSFSheet sheet0 = workbook.getSheetAt(0);
- XSSFDrawing drawing0 = sheet0.createDrawingPatriarch();
- XSSFPictureData pictureData0 = (XSSFPictureData) drawing0.getRelations().get(0);
- byte[] data0 = pictureData0.getData();
- CRC32 crc0 = new CRC32();
- crc0.update(data0);
+ XSSFSheet sheet0 = workbook.getSheetAt(0);
+ XSSFDrawing drawing0 = sheet0.createDrawingPatriarch();
+ XSSFPictureData pictureData0 = (XSSFPictureData) drawing0.getRelations().get(0);
+ byte[] data0 = pictureData0.getData();
+ CRC32 crc0 = new CRC32();
+ crc0.update(data0);
- XSSFSheet sheet1 = workbook.getSheetAt(1);
- XSSFDrawing drawing1 = sheet1.createDrawingPatriarch();
- XSSFPictureData pictureData1 = (XSSFPictureData) drawing1.getRelations().get(0);
- byte[] data1 = pictureData1.getData();
- CRC32 crc1 = new CRC32();
- crc1.update(data1);
+ XSSFSheet sheet1 = workbook.getSheetAt(1);
+ XSSFDrawing drawing1 = sheet1.createDrawingPatriarch();
+ XSSFPictureData pictureData1 = (XSSFPictureData) drawing1.getRelations().get(0);
+ byte[] data1 = pictureData1.getData();
+ CRC32 crc1 = new CRC32();
+ crc1.update(data1);
- assertEquals(crc0.getValue(), crc1.getValue());
- workbook.close();
- }
+ assertEquals(crc0.getValue(), crc1.getValue());
+ workbook.close();
+ }
- /**
- * When deleting a sheet make sure that we adjust sheet indices of named ranges
- */
+ /**
+ * When deleting a sheet make sure that we adjust sheet indices of named ranges
+ */
@Test
- public void bug47737() throws IOException {
- XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("47737.xlsx");
- assertEquals(2, wb.getNumberOfNames());
- assertNotNull(wb.getCalculationChain());
+ public void bug47737() throws IOException {
+ XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("47737.xlsx");
+ assertEquals(2, wb.getNumberOfNames());
+ assertNotNull(wb.getCalculationChain());
- XSSFName nm0 = wb.getNameAt(0);
- assertTrue(nm0.getCTName().isSetLocalSheetId());
- assertEquals(0, nm0.getCTName().getLocalSheetId());
+ XSSFName nm0 = wb.getNameAt(0);
+ assertTrue(nm0.getCTName().isSetLocalSheetId());
+ assertEquals(0, nm0.getCTName().getLocalSheetId());
- XSSFName nm1 = wb.getNameAt(1);
- assertTrue(nm1.getCTName().isSetLocalSheetId());
- assertEquals(1, nm1.getCTName().getLocalSheetId());
+ XSSFName nm1 = wb.getNameAt(1);
+ assertTrue(nm1.getCTName().isSetLocalSheetId());
+ assertEquals(1, nm1.getCTName().getLocalSheetId());
- wb.removeSheetAt(0);
- assertEquals(1, wb.getNumberOfNames());
- XSSFName nm2 = wb.getNameAt(0);
- assertTrue(nm2.getCTName().isSetLocalSheetId());
- assertEquals(0, nm2.getCTName().getLocalSheetId());
- //calculation chain is removed as well
- assertNull(wb.getCalculationChain());
- wb.close();
+ wb.removeSheetAt(0);
+ assertEquals(1, wb.getNumberOfNames());
+ XSSFName nm2 = wb.getNameAt(0);
+ assertTrue(nm2.getCTName().isSetLocalSheetId());
+ assertEquals(0, nm2.getCTName().getLocalSheetId());
+ //calculation chain is removed as well
+ assertNull(wb.getCalculationChain());
+ wb.close();
- }
+ }
- /**
- * Problems with XSSFWorkbook.removeSheetAt when workbook contains charts
- */
+ /**
+ * Problems with XSSFWorkbook.removeSheetAt when workbook contains charts
+ */
@Test
- public void bug47813() throws IOException {
- XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("47813.xlsx");
- assertEquals(3, wb1.getNumberOfSheets());
- assertNotNull(wb1.getCalculationChain());
+ public void bug47813() throws IOException {
+ XSSFWorkbook wb1 = XSSFTestDataSamples.openSampleWorkbook("47813.xlsx");
+ assertEquals(3, wb1.getNumberOfSheets());
+ assertNotNull(wb1.getCalculationChain());
- assertEquals("Numbers", wb1.getSheetName(0));
- //the second sheet is of type 'chartsheet'
- assertEquals("Chart", wb1.getSheetName(1));
- assertTrue(wb1.getSheetAt(1) instanceof XSSFChartSheet);
- assertEquals("SomeJunk", wb1.getSheetName(2));
+ assertEquals("Numbers", wb1.getSheetName(0));
+ //the second sheet is of type 'chartsheet'
+ assertEquals("Chart", wb1.getSheetName(1));
+ assertTrue(wb1.getSheetAt(1) instanceof XSSFChartSheet);
+ assertEquals("SomeJunk", wb1.getSheetName(2));
- wb1.removeSheetAt(2);
- assertEquals(2, wb1.getNumberOfSheets());
- assertNull(wb1.getCalculationChain());
+ wb1.removeSheetAt(2);
+ assertEquals(2, wb1.getNumberOfSheets());
+ assertNull(wb1.getCalculationChain());
- XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1);
- assertEquals(2, wb2.getNumberOfSheets());
- assertNull(wb2.getCalculationChain());
+ XSSFWorkbook wb2 = XSSFTestDataSamples.writeOutAndReadBack(wb1);
+ assertEquals(2, wb2.getNumberOfSheets());
+ assertNull(wb2.getCalculationChain());
- assertEquals("Numbers", wb2.getSheetName(0));
- assertEquals("Chart", wb2.getSheetName(1));
- wb2.close();
- wb1.close();
- }
+ assertEquals("Numbers", wb2.getSheetName(0));
+ assertEquals("Chart", wb2.getSheetName(1));
+ wb2.close();
+ wb1.close();
+ }
- /**
- * Problems with the count of the number of styles
- * coming out wrong
- */
+ /**
+ * Problems with the count of the number of styles
+ * coming out wrong
+ */
@Test
- public void bug49702() throws IOException {
- // First try with a new file
- XSSFWorkbook wb1 = new XSSFWorkbook();
+ public void bug49702() throws IOException {
+ // First try with a new file
+ XSSFWorkbook wb1 = new XSSFWorkbook();
- // Should have one style
- assertEquals(1, wb1.getNumCellStyles());
- wb1.getCellStyleAt((short)0);
- try {
- wb1.getCellStyleAt((short)1);
- fail("Shouldn't be able to get style at 1 that doesn't exist");
- } catch(IndexOutOfBoundsException e) {}
+ // Should have one style
+ assertEquals(1, wb1.getNumCellStyles());
+ wb1.getCellStyleAt((short)0);
+ try {
+ wb1.getCellStyleAt((short)1);
+ fail("Shouldn't be able to get style at 1 that doesn't exist");
+ } catch(IndexOutOfBoundsException e) {}
- // Add another one
- CellStyle cs = wb1.createCellStyle();
- cs.setDataFormat((short)11);
+ // Add another one
+ CellStyle cs = wb1.createCellStyle();
+ cs.setDataFormat((short)11);
- // Re-check
- assertEquals(2, wb1.getNumCellStyles());
- wb1.getCellStyleAt((short)0);
- wb1.getCellStyleAt((short)1);
- try {
- wb1.getCellStyleAt((short)2);
- fail("Shouldn't be able to get style at 2 that doesn't exist");
- } catch(IndexOutOfBoundsException e) {}
+ // Re-check
+ assertEquals(2, wb1.getNumCellStyles());
+ wb1.getCellStyleAt((short)0);
+ wb1.getCellStyleAt((short)1);
+ try {
+ wb1.getCellStyleAt((short)2);
+ fail("Shouldn't be able to get style at 2 that doesn't exist");
+ } catch(IndexOutOfBoundsException e) {}
- // Save and reload
- XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb1);
- assertEquals(2, nwb.getNumCellStyles());
- nwb.getCellStyleAt((short)0);
- nwb.getCellStyleAt((short)1);
- try {
- nwb.getCellStyleAt((short)2);
- fail("Shouldn't be able to get style at 2 that doesn't exist");
- } catch(IndexOutOfBoundsException e) {}
+ // Save and reload
+ XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb1);
+ assertEquals(2, nwb.getNumCellStyles());
+ nwb.getCellStyleAt((short)0);
+ nwb.getCellStyleAt((short)1);
+ try {
+ nwb.getCellStyleAt((short)2);
+ fail("Shouldn't be able to get style at 2 that doesn't exist");
+ } catch(IndexOutOfBoundsException e) {}
- // Now with an existing file
- XSSFWorkbook wb2 = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx");
- assertEquals(3, wb2.getNumCellStyles());
- wb2.getCellStyleAt((short)0);
- wb2.getCellStyleAt((short)1);
- wb2.getCellStyleAt((short)2);
- try {
- wb2.getCellStyleAt((short)3);
- fail("Shouldn't be able to get style at 3 that doesn't exist");
- } catch(IndexOutOfBoundsException e) {}
+ // Now with an existing file
+ XSSFWorkbook wb2 = XSSFTestDataSamples.openSampleWorkbook("sample.xlsx");
+ assertEquals(3, wb2.getNumCellStyles());
+ wb2.getCellStyleAt((short)0);
+ wb2.getCellStyleAt((short)1);
+ wb2.getCellStyleAt((short)2);
+ try {
+ wb2.getCellStyleAt((short)3);
+ fail("Shouldn't be able to get style at 3 that doesn't exist");
+ } catch(IndexOutOfBoundsException e) {}
- wb2.close();
- wb1.close();
- nwb.close();
- }
+ wb2.close();
+ wb1.close();
+ nwb.close();
+ }
@Test
public void recalcId() throws IOException {
@@ -542,14 +542,14 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
}
@Test
- public void columnWidthPOI52233() throws Exception {
- XSSFWorkbook workbook = new XSSFWorkbook();
- XSSFSheet sheet = workbook.createSheet();
- XSSFRow row = sheet.createRow(0);
- XSSFCell cell = row.createCell(0);
- cell.setCellValue("hello world");
+ public void columnWidthPOI52233() throws Exception {
+ XSSFWorkbook workbook = new XSSFWorkbook();
+ XSSFSheet sheet = workbook.createSheet();
+ XSSFRow row = sheet.createRow(0);
+ XSSFCell cell = row.createCell(0);
+ cell.setCellValue("hello world");
- sheet = workbook.createSheet();
+ sheet = workbook.createSheet();
sheet.setColumnWidth(4, 5000);
sheet.setColumnWidth(5, 5000);
@@ -557,114 +557,114 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
accessWorkbook(workbook);
- ByteArrayOutputStream stream = new ByteArrayOutputStream();
- try {
- workbook.write(stream);
- } finally {
- stream.close();
- }
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ try {
+ workbook.write(stream);
+ } finally {
+ stream.close();
+ }
- accessWorkbook(workbook);
- workbook.close();
- }
+ accessWorkbook(workbook);
+ workbook.close();
+ }
- private void accessWorkbook(XSSFWorkbook workbook) {
- workbook.getSheetAt(1).setColumnGroupCollapsed(4, true);
- workbook.getSheetAt(1).setColumnGroupCollapsed(4, false);
+ private void accessWorkbook(XSSFWorkbook workbook) {
+ workbook.getSheetAt(1).setColumnGroupCollapsed(4, true);
+ workbook.getSheetAt(1).setColumnGroupCollapsed(4, false);
- assertEquals("hello world", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue());
- assertEquals(2048, workbook.getSheetAt(0).getColumnWidth(0)); // <-works
- }
+ assertEquals("hello world", workbook.getSheetAt(0).getRow(0).getCell(0).getStringCellValue());
+ assertEquals(2048, workbook.getSheetAt(0).getColumnWidth(0)); // <-works
+ }
@Test
- public void bug48495() throws IOException {
- Workbook wb = XSSFTestDataSamples.openSampleWorkbook("48495.xlsx");
+ public void bug48495() throws IOException {
+ Workbook wb = XSSFTestDataSamples.openSampleWorkbook("48495.xlsx");
- assertSheetOrder(wb, "Sheet1");
+ assertSheetOrder(wb, "Sheet1");
- Sheet sheet = wb.getSheetAt(0);
- sheet.shiftRows(2, sheet.getLastRowNum(), 1, true, false);
- Row newRow = sheet.getRow(2);
- if (newRow == null) newRow = sheet.createRow(2);
- newRow.createCell(0).setCellValue(" Another Header");
- wb.cloneSheet(0);
+ Sheet sheet = wb.getSheetAt(0);
+ sheet.shiftRows(2, sheet.getLastRowNum(), 1, true, false);
+ Row newRow = sheet.getRow(2);
+ if (newRow == null) newRow = sheet.createRow(2);
+ newRow.createCell(0).setCellValue(" Another Header");
+ wb.cloneSheet(0);
- assertSheetOrder(wb, "Sheet1", "Sheet1 (2)");
+ assertSheetOrder(wb, "Sheet1", "Sheet1 (2)");
- // FileOutputStream fileOut = new FileOutputStream("/tmp/bug48495.xlsx");
-// try {
-// wb.write(fileOut);
-// } finally {
-// fileOut.close();
-// }
+ // FileOutputStream fileOut = new FileOutputStream("/tmp/bug48495.xlsx");
+// try {
+// wb.write(fileOut);
+// } finally {
+// fileOut.close();
+// }
- Workbook read = XSSFTestDataSamples.writeOutAndReadBack(wb);
- assertNotNull(read);
- assertSheetOrder(read, "Sheet1", "Sheet1 (2)");
- read.close();
- wb.close();
- }
+ Workbook read = XSSFTestDataSamples.writeOutAndReadBack(wb);
+ assertNotNull(read);
+ assertSheetOrder(read, "Sheet1", "Sheet1 (2)");
+ read.close();
+ wb.close();
+ }
@Test
- public void bug47090a() throws IOException {
- Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
- assertSheetOrder(workbook, "Sheet1", "Sheet2");
- workbook.removeSheetAt(0);
- assertSheetOrder(workbook, "Sheet2");
- workbook.createSheet();
- assertSheetOrder(workbook, "Sheet2", "Sheet1");
- Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertSheetOrder(read, "Sheet2", "Sheet1");
- read.close();
- workbook.close();
- }
+ public void bug47090a() throws IOException {
+ Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
+ assertSheetOrder(workbook, "Sheet1", "Sheet2");
+ workbook.removeSheetAt(0);
+ assertSheetOrder(workbook, "Sheet2");
+ workbook.createSheet();
+ assertSheetOrder(workbook, "Sheet2", "Sheet1");
+ Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertSheetOrder(read, "Sheet2", "Sheet1");
+ read.close();
+ workbook.close();
+ }
@Test
- public void bug47090b() throws IOException {
- Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
- assertSheetOrder(workbook, "Sheet1", "Sheet2");
- workbook.removeSheetAt(1);
- assertSheetOrder(workbook, "Sheet1");
- workbook.createSheet();
- assertSheetOrder(workbook, "Sheet1", "Sheet0"); // Sheet0 because it uses "Sheet" + sheets.size() as starting point!
- Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertSheetOrder(read, "Sheet1", "Sheet0");
- read.close();
- workbook.close();
- }
+ public void bug47090b() throws IOException {
+ Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
+ assertSheetOrder(workbook, "Sheet1", "Sheet2");
+ workbook.removeSheetAt(1);
+ assertSheetOrder(workbook, "Sheet1");
+ workbook.createSheet();
+ assertSheetOrder(workbook, "Sheet1", "Sheet0"); // Sheet0 because it uses "Sheet" + sheets.size() as starting point!
+ Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertSheetOrder(read, "Sheet1", "Sheet0");
+ read.close();
+ workbook.close();
+ }
@Test
- public void bug47090c() throws IOException {
- Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
- assertSheetOrder(workbook, "Sheet1", "Sheet2");
- workbook.removeSheetAt(0);
- assertSheetOrder(workbook, "Sheet2");
- workbook.cloneSheet(0);
- assertSheetOrder(workbook, "Sheet2", "Sheet2 (2)");
- Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertSheetOrder(read, "Sheet2", "Sheet2 (2)");
- read.close();
- workbook.close();
- }
+ public void bug47090c() throws IOException {
+ Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
+ assertSheetOrder(workbook, "Sheet1", "Sheet2");
+ workbook.removeSheetAt(0);
+ assertSheetOrder(workbook, "Sheet2");
+ workbook.cloneSheet(0);
+ assertSheetOrder(workbook, "Sheet2", "Sheet2 (2)");
+ Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertSheetOrder(read, "Sheet2", "Sheet2 (2)");
+ read.close();
+ workbook.close();
+ }
@Test
- public void bug47090d() throws IOException {
- Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
- assertSheetOrder(workbook, "Sheet1", "Sheet2");
- workbook.createSheet();
- assertSheetOrder(workbook, "Sheet1", "Sheet2", "Sheet0");
- workbook.removeSheetAt(0);
- assertSheetOrder(workbook, "Sheet2", "Sheet0");
- workbook.createSheet();
- assertSheetOrder(workbook, "Sheet2", "Sheet0", "Sheet1");
- Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
- assertSheetOrder(read, "Sheet2", "Sheet0", "Sheet1");
- read.close();
- workbook.close();
- }
+ public void bug47090d() throws IOException {
+ Workbook workbook = XSSFTestDataSamples.openSampleWorkbook("47090.xlsx");
+ assertSheetOrder(workbook, "Sheet1", "Sheet2");
+ workbook.createSheet();
+ assertSheetOrder(workbook, "Sheet1", "Sheet2", "Sheet0");
+ workbook.removeSheetAt(0);
+ assertSheetOrder(workbook, "Sheet2", "Sheet0");
+ workbook.createSheet();
+ assertSheetOrder(workbook, "Sheet2", "Sheet0", "Sheet1");
+ Workbook read = XSSFTestDataSamples.writeOutAndReadBack(workbook);
+ assertSheetOrder(read, "Sheet2", "Sheet0", "Sheet1");
+ read.close();
+ workbook.close();
+ }
@Test
- public void bug51158() throws IOException {
+ public void bug51158() throws IOException {
// create a workbook
final XSSFWorkbook wb1 = new XSSFWorkbook();
XSSFSheet sheet = wb1.createSheet("Test Sheet");
@@ -706,10 +706,10 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
wb3.close();
wb2.close();
wb1.close();
- }
+ }
@Test
- public void bug51158a() throws IOException {
+ public void bug51158a() throws IOException {
// create a workbook
final XSSFWorkbook workbook = new XSSFWorkbook();
try {
@@ -1091,20 +1091,52 @@ public final class TestXSSFWorkbook extends BaseTestXWorkbook {
//assertCloseDoesNotModifyFile(filename, wb);
}
- @Test
- public void testCloseBeforeWrite() throws IOException {
- Workbook wb = new XSSFWorkbook();
- wb.createSheet("somesheet");
+ @Test
+ public void testCloseBeforeWrite() throws IOException {
+ Workbook wb = new XSSFWorkbook();
+ wb.createSheet("somesheet");
- // test what happens if we close the Workbook before we write it out
- wb.close();
+ // test what happens if we close the Workbook before we write it out
+ wb.close();
- try {
- XSSFTestDataSamples.writeOutAndReadBack(wb);
- fail("Expecting IOException here");
- } catch (RuntimeException e) {
- // expected here
- assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException);
- }
- }
+ try {
+ XSSFTestDataSamples.writeOutAndReadBack(wb);
+ fail("Expecting IOException here");
+ } catch (RuntimeException e) {
+ // expected here
+ assertTrue("Had: " + e.getCause(), e.getCause() instanceof IOException);
+ }
+ }
+
+ /**
+ * See bug #57840 test data tables
+ */
+ @Test
+ public void getTable() throws IOException {
+ XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("WithTable.xlsx");
+ XSSFTable table1 = wb.getTable("Tabella1");
+ assertNotNull("Tabella1 was not found in workbook", table1);
+ assertEquals("Table name", "Tabella1", table1.getName());
+ assertEquals("Sheet name", "Foglio1", table1.getSheetName());
+
+ // Table lookup should be case-insensitive
+ assertSame("Case insensitive table name lookup", table1, wb.getTable("TABELLA1"));
+
+ // If workbook does not contain any data tables matching the provided name, getTable should return null
+ assertNull("Null table name should not throw NPE", wb.getTable(null));
+ assertNull("Should not be able to find non-existent table", wb.getTable("Foglio1"));
+
+ // If a table is added after getTable is called it should still be reachable by XSSFWorkbook.getTable
+ // This test makes sure that if any caching is done that getTable never uses a stale cache
+ XSSFTable table2 = wb.getSheet("Foglio2").createTable();
+ table2.setName("Table2");
+ assertSame("Did not find Table2", table2, wb.getTable("Table2"));
+
+ // If table name is modified after getTable is called, the table can only be found by its new name
+ // This test makes sure that if any caching is done that getTable never uses a stale cache
+ table1.setName("Table1");
+ assertSame("Did not find Tabella1 renamed to Table1", table1, wb.getTable("TABLE1"));
+
+ wb.close();
+ }
}
diff --git a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
index 0d140ab7d..bca29c74f 100644
--- a/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
+++ b/src/testcases/org/apache/poi/hssf/model/AllModelTests.java
@@ -27,7 +27,7 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
TestDrawingManager.class,
TestDrawingManager2.class,
- TestFormulaParser.class,
+ //TestFormulaParser.class, //converted to junit4
TestFormulaParserEval.class,
TestFormulaParserIf.class,
TestLinkTable.class,
diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
index f39d07b94..82153fe09 100644
--- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
+++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java
@@ -87,271 +87,271 @@ import org.junit.Test;
*/
public final class TestFormulaParser {
- /**
- * @return parsed token array already confirmed not null
- */
- /* package */ static Ptg[] parseFormula(String formula) {
- Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null);
- assertNotNull("Ptg array should not be null", result);
- return result;
- }
- private static String toFormulaString(Ptg[] ptgs) {
- return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs);
- }
+ /**
+ * @return parsed token array already confirmed not null
+ */
+ /* package */ static Ptg[] parseFormula(String formula) {
+ Ptg[] result = HSSFFormulaParser.parse(formula, (HSSFWorkbook)null);
+ assertNotNull("Ptg array should not be null", result);
+ return result;
+ }
+ private static String toFormulaString(Ptg[] ptgs) {
+ return HSSFFormulaParser.toFormulaString((HSSFWorkbook)null, ptgs);
+ }
- @Test
- public void testSimpleFormula() {
- confirmTokenClasses("2+2",IntPtg.class, IntPtg.class, AddPtg.class);
- }
+ @Test
+ public void testSimpleFormula() {
+ confirmTokenClasses("2+2",IntPtg.class, IntPtg.class, AddPtg.class);
+ }
- @Test
- public void testFormulaWithSpace1() {
- confirmTokenClasses(" 2 + 2 ",IntPtg.class, IntPtg.class, AddPtg.class);
- }
+ @Test
+ public void testFormulaWithSpace1() {
+ confirmTokenClasses(" 2 + 2 ",IntPtg.class, IntPtg.class, AddPtg.class);
+ }
- @Test
- public void testFormulaWithSpace2() {
- Ptg[] ptgs = parseFormula("2+ sum( 3 , 4) ");
- assertEquals(5, ptgs.length);
- }
+ @Test
+ public void testFormulaWithSpace2() {
+ Ptg[] ptgs = parseFormula("2+ sum( 3 , 4) ");
+ assertEquals(5, ptgs.length);
+ }
- @Test
- public void testFormulaWithSpaceNRef() {
- Ptg[] ptgs = parseFormula("sum( A2:A3 )");
- assertEquals(2, ptgs.length);
- }
+ @Test
+ public void testFormulaWithSpaceNRef() {
+ Ptg[] ptgs = parseFormula("sum( A2:A3 )");
+ assertEquals(2, ptgs.length);
+ }
- @Test
- public void testFormulaWithString() {
- Ptg[] ptgs = parseFormula("\"hello\" & \"world\" ");
- assertEquals(3, ptgs.length);
- }
+ @Test
+ public void testFormulaWithString() {
+ Ptg[] ptgs = parseFormula("\"hello\" & \"world\" ");
+ assertEquals(3, ptgs.length);
+ }
- @Test
- public void testTRUE() {
- Ptg[] ptgs = parseFormula("TRUE");
- assertEquals(1, ptgs.length);
- BoolPtg flag = (BoolPtg) ptgs[0];
- assertEquals(true, flag.getValue());
- }
+ @Test
+ public void testTRUE() {
+ Ptg[] ptgs = parseFormula("TRUE");
+ assertEquals(1, ptgs.length);
+ BoolPtg flag = (BoolPtg) ptgs[0];
+ assertEquals(true, flag.getValue());
+ }
- @Test
- public void testSumIf() {
- Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)");
- assertEquals(4, ptgs.length);
- }
+ @Test
+ public void testSumIf() {
+ Ptg[] ptgs = parseFormula("SUMIF(A1:A5,\">4000\",B1:B5)");
+ assertEquals(4, ptgs.length);
+ }
- /**
- * Bug Reported by xt-jens.riis@nokia.com (Jens Riis)
- * Refers to Bug #17582
- *
- */
- @Test
- public void testNonAlphaFormula() {
- Ptg[] ptgs = parseFormula("\"TOTAL[\"&F3&\"]\"");
- confirmTokenClasses(ptgs, StringPtg.class, RefPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class);
- assertEquals("TOTAL[", ((StringPtg)ptgs[0]).getValue());
- }
+ /**
+ * Bug Reported by xt-jens.riis@nokia.com (Jens Riis)
+ * Refers to Bug #17582
+ *
+ */
+ @Test
+ public void testNonAlphaFormula() {
+ Ptg[] ptgs = parseFormula("\"TOTAL[\"&F3&\"]\"");
+ confirmTokenClasses(ptgs, StringPtg.class, RefPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class);
+ assertEquals("TOTAL[", ((StringPtg)ptgs[0]).getValue());
+ }
- @Test
- public void testMacroFunction() throws IOException {
- // testNames.xls contains a VB function called 'myFunc'
- final String testFile = "testNames.xls";
- HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(testFile);
- try {
- HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(wb);
+ @Test
+ public void testMacroFunction() throws IOException {
+ // testNames.xls contains a VB function called 'myFunc'
+ final String testFile = "testNames.xls";
+ HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook(testFile);
+ try {
+ HSSFEvaluationWorkbook book = HSSFEvaluationWorkbook.create(wb);
- //Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands go here...), FunctionPtg(myFunc)]
- Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", book, FormulaType.CELL, -1);
- assertEquals(3, ptg.length);
+ //Expected ptg stack: [NamePtg(myFunc), StringPtg(arg), (additional operands go here...), FunctionPtg(myFunc)]
+ Ptg[] ptg = FormulaParser.parse("myFunc(\"arg\")", book, FormulaType.CELL, -1);
+ assertEquals(3, ptg.length);
- // the name gets encoded as the first operand on the stack
- NamePtg tname = (NamePtg) ptg[0];
- assertEquals("myFunc", tname.toFormulaString(book));
+ // the name gets encoded as the first operand on the stack
+ NamePtg tname = (NamePtg) ptg[0];
+ assertEquals("myFunc", tname.toFormulaString(book));
- // the function's arguments are pushed onto the stack from left-to-right as OperandPtgs
- StringPtg arg = (StringPtg) ptg[1];
- assertEquals("arg", arg.getValue());
+ // the function's arguments are pushed onto the stack from left-to-right as OperandPtgs
+ StringPtg arg = (StringPtg) ptg[1];
+ assertEquals("arg", arg.getValue());
- // The external FunctionPtg is the last Ptg added to the stack
- // During formula evaluation, this Ptg pops off the the appropriate number of
- // arguments (getNumberOfOperands()) and pushes the result on the stack
- AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2]; //FuncVarPtg
- assertTrue(tfunc.isExternalFunction());
+ // The external FunctionPtg is the last Ptg added to the stack
+ // During formula evaluation, this Ptg pops off the the appropriate number of
+ // arguments (getNumberOfOperands()) and pushes the result on the stack
+ AbstractFunctionPtg tfunc = (AbstractFunctionPtg) ptg[2]; //FuncVarPtg
+ assertTrue(tfunc.isExternalFunction());
- // confirm formula parsing is case-insensitive
- FormulaParser.parse("mYfUnC(\"arg\")", book, FormulaType.CELL, -1);
+ // confirm formula parsing is case-insensitive
+ FormulaParser.parse("mYfUnC(\"arg\")", book, FormulaType.CELL, -1);
- // confirm formula parsing doesn't care about argument count or type
- // this should only throw an error when evaluating the formula.
- FormulaParser.parse("myFunc()", book, FormulaType.CELL, -1);
- FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", book, FormulaType.CELL, -1);
+ // confirm formula parsing doesn't care about argument count or type
+ // this should only throw an error when evaluating the formula.
+ FormulaParser.parse("myFunc()", book, FormulaType.CELL, -1);
+ FormulaParser.parse("myFunc(\"arg\", 0, TRUE)", book, FormulaType.CELL, -1);
- // A completely unknown formula name (not saved in workbook) should still be parseable and renderable
- // but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated.
- FormulaParser.parse("yourFunc(\"arg\")", book, FormulaType.CELL, -1);
+ // A completely unknown formula name (not saved in workbook) should still be parseable and renderable
+ // but will throw an NotImplementedFunctionException or return a #NAME? error value if evaluated.
+ FormulaParser.parse("yourFunc(\"arg\")", book, FormulaType.CELL, -1);
- // Verify that myFunc and yourFunc were successfully added to Workbook names
- HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb);
- try {
- // HSSFWorkbook/EXCEL97-specific side-effects user-defined function names must be added to Workbook's defined names in order to be saved.
- assertNotNull(wb2.getName("myFunc"));
- assertEqualsIgnoreCase("myFunc", wb2.getName("myFunc").getNameName());
- assertNotNull(wb2.getName("yourFunc"));
- assertEqualsIgnoreCase("yourFunc", wb2.getName("yourFunc").getNameName());
+ // Verify that myFunc and yourFunc were successfully added to Workbook names
+ HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ try {
+ // HSSFWorkbook/EXCEL97-specific side-effects user-defined function names must be added to Workbook's defined names in order to be saved.
+ assertNotNull(wb2.getName("myFunc"));
+ assertEqualsIgnoreCase("myFunc", wb2.getName("myFunc").getNameName());
+ assertNotNull(wb2.getName("yourFunc"));
+ assertEqualsIgnoreCase("yourFunc", wb2.getName("yourFunc").getNameName());
- // Manually check to make sure file isn't corrupted
- // TODO: develop a process for occasionally manually reviewing workbooks
- // to verify workbooks are not corrupted
- /*
- final File fileIn = HSSFTestDataSamples.getSampleFile(testFile);
- final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls"));
- FileOutputStream fos = new FileOutputStream(reSavedFile);
- wb2.write(fos);
- fos.close();
- */
- } finally {
- wb2.close();
- }
- } finally {
- wb.close();
- }
- }
-
- private final static void assertEqualsIgnoreCase(String expected, String actual) {
- assertEquals(expected.toLowerCase(Locale.ROOT), actual.toLowerCase(Locale.ROOT));
- }
+ // Manually check to make sure file isn't corrupted
+ // TODO: develop a process for occasionally manually reviewing workbooks
+ // to verify workbooks are not corrupted
+ /*
+ final File fileIn = HSSFTestDataSamples.getSampleFile(testFile);
+ final File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls"));
+ FileOutputStream fos = new FileOutputStream(reSavedFile);
+ wb2.write(fos);
+ fos.close();
+ */
+ } finally {
+ wb2.close();
+ }
+ } finally {
+ wb.close();
+ }
+ }
+
+ private final static void assertEqualsIgnoreCase(String expected, String actual) {
+ assertEquals(expected.toLowerCase(Locale.ROOT), actual.toLowerCase(Locale.ROOT));
+ }
- @Test
- public void testEmbeddedSlash() {
- confirmTokenClasses("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",
- StringPtg.class, StringPtg.class, FuncVarPtg.class);
- }
+ @Test
+ public void testEmbeddedSlash() {
+ confirmTokenClasses("HYPERLINK(\"http://www.jakarta.org\",\"Jakarta\")",
+ StringPtg.class, StringPtg.class, FuncVarPtg.class);
+ }
- @Test
- public void testConcatenate() {
- confirmTokenClasses("CONCATENATE(\"first\",\"second\")",
- StringPtg.class, StringPtg.class, FuncVarPtg.class);
- }
+ @Test
+ public void testConcatenate() {
+ confirmTokenClasses("CONCATENATE(\"first\",\"second\")",
+ StringPtg.class, StringPtg.class, FuncVarPtg.class);
+ }
- @Test
- public void testWorksheetReferences() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ @Test
+ public void testWorksheetReferences() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("NoQuotesNeeded");
- wb.createSheet("Quotes Needed Here $@");
+ wb.createSheet("NoQuotesNeeded");
+ wb.createSheet("Quotes Needed Here $@");
- HSSFSheet sheet = wb.createSheet("Test");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell;
+ HSSFSheet sheet = wb.createSheet("Test");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell;
- cell = row.createCell(0);
- cell.setCellFormula("NoQuotesNeeded!A1");
+ cell = row.createCell(0);
+ cell.setCellFormula("NoQuotesNeeded!A1");
- cell = row.createCell(1);
- cell.setCellFormula("'Quotes Needed Here $@'!A1");
-
- wb.close();
- }
+ cell = row.createCell(1);
+ cell.setCellFormula("'Quotes Needed Here $@'!A1");
+
+ wb.close();
+ }
- @Test
- public void testUnaryMinus() {
- confirmTokenClasses("-A1", RefPtg.class, UnaryMinusPtg.class);
- }
+ @Test
+ public void testUnaryMinus() {
+ confirmTokenClasses("-A1", RefPtg.class, UnaryMinusPtg.class);
+ }
- @Test
- public void testUnaryPlus() {
- confirmTokenClasses("+A1", RefPtg.class, UnaryPlusPtg.class);
- }
+ @Test
+ public void testUnaryPlus() {
+ confirmTokenClasses("+A1", RefPtg.class, UnaryPlusPtg.class);
+ }
- /**
- * There may be multiple ways to encode an expression involving {@link UnaryPlusPtg}
- * or {@link UnaryMinusPtg}. These may be perfectly equivalent from a formula
- * evaluation perspective, or formula rendering. However, differences in the way
- * POI encodes formulas may cause unnecessary confusion. These non-critical tests
- * check that POI follows the same encoding rules as Excel.
- */
- @Test
- public void testExactEncodingOfUnaryPlusAndMinus() {
- // as tested in Excel:
- confirmUnary("-3", -3, NumberPtg.class);
- confirmUnary("--4", -4, NumberPtg.class, UnaryMinusPtg.class);
- confirmUnary("+++5", 5, IntPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class);
- confirmUnary("++-6", -6, NumberPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class);
+ /**
+ * There may be multiple ways to encode an expression involving {@link UnaryPlusPtg}
+ * or {@link UnaryMinusPtg}. These may be perfectly equivalent from a formula
+ * evaluation perspective, or formula rendering. However, differences in the way
+ * POI encodes formulas may cause unnecessary confusion. These non-critical tests
+ * check that POI follows the same encoding rules as Excel.
+ */
+ @Test
+ public void testExactEncodingOfUnaryPlusAndMinus() {
+ // as tested in Excel:
+ confirmUnary("-3", -3, NumberPtg.class);
+ confirmUnary("--4", -4, NumberPtg.class, UnaryMinusPtg.class);
+ confirmUnary("+++5", 5, IntPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class);
+ confirmUnary("++-6", -6, NumberPtg.class, UnaryPlusPtg.class, UnaryPlusPtg.class);
- // Spaces muck things up a bit. It would be clearer why the following cases are
- // reasonable if POI encoded tAttrSpace in the right places.
- // Otherwise these differences look capricious.
- confirmUnary("+ 12", 12, IntPtg.class, UnaryPlusPtg.class);
- confirmUnary("- 13", 13, IntPtg.class, UnaryMinusPtg.class);
- }
+ // Spaces muck things up a bit. It would be clearer why the following cases are
+ // reasonable if POI encoded tAttrSpace in the right places.
+ // Otherwise these differences look capricious.
+ confirmUnary("+ 12", 12, IntPtg.class, UnaryPlusPtg.class);
+ confirmUnary("- 13", 13, IntPtg.class, UnaryMinusPtg.class);
+ }
- private static void confirmUnary(String formulaText, double val, Class>...expectedTokenTypes) {
- Ptg[] ptgs = parseFormula(formulaText);
- confirmTokenClasses(ptgs, expectedTokenTypes);
- Ptg ptg0 = ptgs[0];
- if (ptg0 instanceof IntPtg) {
- IntPtg intPtg = (IntPtg) ptg0;
- assertEquals((int)val, intPtg.getValue());
- } else if (ptg0 instanceof NumberPtg) {
- NumberPtg numberPtg = (NumberPtg) ptg0;
- assertEquals(val, numberPtg.getValue(), 0.0);
- } else {
- fail("bad ptg0 " + ptg0);
- }
- }
+ private static void confirmUnary(String formulaText, double val, Class>...expectedTokenTypes) {
+ Ptg[] ptgs = parseFormula(formulaText);
+ confirmTokenClasses(ptgs, expectedTokenTypes);
+ Ptg ptg0 = ptgs[0];
+ if (ptg0 instanceof IntPtg) {
+ IntPtg intPtg = (IntPtg) ptg0;
+ assertEquals((int)val, intPtg.getValue());
+ } else if (ptg0 instanceof NumberPtg) {
+ NumberPtg numberPtg = (NumberPtg) ptg0;
+ assertEquals(val, numberPtg.getValue(), 0.0);
+ } else {
+ fail("bad ptg0 " + ptg0);
+ }
+ }
- @Test
- public void testLeadingSpaceInString() {
- String value = " hi ";
- Ptg[] ptgs = parseFormula("\"" + value + "\"");
- confirmTokenClasses(ptgs, StringPtg.class);
- assertTrue("ptg0 contains exact value", ((StringPtg)ptgs[0]).getValue().equals(value));
- }
+ @Test
+ public void testLeadingSpaceInString() {
+ String value = " hi ";
+ Ptg[] ptgs = parseFormula("\"" + value + "\"");
+ confirmTokenClasses(ptgs, StringPtg.class);
+ assertTrue("ptg0 contains exact value", ((StringPtg)ptgs[0]).getValue().equals(value));
+ }
- @Test
- public void testLookupAndMatchFunctionArgs() {
- Ptg[] ptgs = parseFormula("lookup(A1, A3:A52, B3:B52)");
- confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, AreaPtg.class, FuncVarPtg.class);
- assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE);
+ @Test
+ public void testLookupAndMatchFunctionArgs() {
+ Ptg[] ptgs = parseFormula("lookup(A1, A3:A52, B3:B52)");
+ confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, AreaPtg.class, FuncVarPtg.class);
+ assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE);
- ptgs = parseFormula("match(A1, A3:A52)");
- confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, FuncVarPtg.class);
- assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE);
- }
+ ptgs = parseFormula("match(A1, A3:A52)");
+ confirmTokenClasses(ptgs, RefPtg.class, AreaPtg.class, FuncVarPtg.class);
+ assertTrue("ptg0 has Value class", ptgs[0].getPtgClass() == Ptg.CLASS_VALUE);
+ }
- /** bug 33160*/
- @Test
- public void testLargeInt() {
- confirmTokenClasses("40", IntPtg.class);
- confirmTokenClasses("40000", IntPtg.class);
- }
+ /** bug 33160*/
+ @Test
+ public void testLargeInt() {
+ confirmTokenClasses("40", IntPtg.class);
+ confirmTokenClasses("40000", IntPtg.class);
+ }
- /** bug 33160 */
- @Test
- public void testSimpleLongFormula() {
- confirmTokenClasses("40000/2", IntPtg.class, IntPtg.class, DividePtg.class);
- }
+ /** bug 33160 */
+ @Test
+ public void testSimpleLongFormula() {
+ confirmTokenClasses("40000/2", IntPtg.class, IntPtg.class, DividePtg.class);
+ }
- /** bug 35027, underscore in sheet name */
- @Test
- public void testUnderscore() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ /** bug 35027, underscore in sheet name */
+ @Test
+ public void testUnderscore() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");
+ wb.createSheet("Cash_Flow");
- HSSFSheet sheet = wb.createSheet("Test");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell;
+ HSSFSheet sheet = wb.createSheet("Test");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell;
- cell = row.createCell(0);
- cell.setCellFormula("Cash_Flow!A1");
-
- wb.close();
- }
+ cell = row.createCell(0);
+ cell.setCellFormula("Cash_Flow!A1");
+
+ wb.close();
+ }
/** bug 49725, defined names with underscore */
- @Test
+ @Test
public void testNamesWithUnderscore() throws IOException {
HSSFWorkbook wb = new HSSFWorkbook(); //or new XSSFWorkbook();
HSSFSheet sheet = wb.createSheet("NamesWithUnderscore");
@@ -398,162 +398,162 @@ public final class TestFormulaParser {
}
// bug 38396 : Formula with exponential numbers not parsed correctly.
- @Test
- public void testExponentialParsing() {
- confirmTokenClasses("1.3E21/2", NumberPtg.class, IntPtg.class, DividePtg.class);
- confirmTokenClasses("1322E21/2", NumberPtg.class, IntPtg.class, DividePtg.class);
- confirmTokenClasses("1.3E1/2", NumberPtg.class, IntPtg.class, DividePtg.class);
- }
+ @Test
+ public void testExponentialParsing() {
+ confirmTokenClasses("1.3E21/2", NumberPtg.class, IntPtg.class, DividePtg.class);
+ confirmTokenClasses("1322E21/2", NumberPtg.class, IntPtg.class, DividePtg.class);
+ confirmTokenClasses("1.3E1/2", NumberPtg.class, IntPtg.class, DividePtg.class);
+ }
- @Test
- public void testExponentialInSheet() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ @Test
+ public void testExponentialInSheet() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");
+ wb.createSheet("Cash_Flow");
- HSSFSheet sheet = wb.createSheet("Test");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell = row.createCell(0);
- String formula = null;
+ HSSFSheet sheet = wb.createSheet("Test");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ String formula = null;
- cell.setCellFormula("1.3E21/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "1.3E+21/3", formula);
+ cell.setCellFormula("1.3E21/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "1.3E+21/3", formula);
- cell.setCellFormula("-1.3E21/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-1.3E+21/3", formula);
+ cell.setCellFormula("-1.3E21/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-1.3E+21/3", formula);
- cell.setCellFormula("1322E21/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "1.322E+24/3", formula);
+ cell.setCellFormula("1322E21/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "1.322E+24/3", formula);
- cell.setCellFormula("-1322E21/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-1.322E+24/3", formula);
+ cell.setCellFormula("-1322E21/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-1.322E+24/3", formula);
- cell.setCellFormula("1.3E1/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "13/3", formula);
+ cell.setCellFormula("1.3E1/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "13/3", formula);
- cell.setCellFormula("-1.3E1/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-13/3", formula);
+ cell.setCellFormula("-1.3E1/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-13/3", formula);
- cell.setCellFormula("1.3E-4/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "0.00013/3", formula);
+ cell.setCellFormula("1.3E-4/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "0.00013/3", formula);
- cell.setCellFormula("-1.3E-4/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-0.00013/3", formula);
+ cell.setCellFormula("-1.3E-4/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-0.00013/3", formula);
- cell.setCellFormula("13E-15/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "0.000000000000013/3", formula);
+ cell.setCellFormula("13E-15/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "0.000000000000013/3", formula);
- cell.setCellFormula("-13E-15/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-0.000000000000013/3", formula);
+ cell.setCellFormula("-13E-15/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-0.000000000000013/3", formula);
- cell.setCellFormula("1.3E3/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "1300/3", formula);
+ cell.setCellFormula("1.3E3/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "1300/3", formula);
- cell.setCellFormula("-1.3E3/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-1300/3", formula);
+ cell.setCellFormula("-1.3E3/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-1300/3", formula);
- cell.setCellFormula("1300000000000000/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "1300000000000000/3", formula);
+ cell.setCellFormula("1300000000000000/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "1300000000000000/3", formula);
- cell.setCellFormula("-1300000000000000/3");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-1300000000000000/3", formula);
+ cell.setCellFormula("-1300000000000000/3");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-1300000000000000/3", formula);
- cell.setCellFormula("-10E-1/3.1E2*4E3/3E4");
- formula = cell.getCellFormula();
- assertEquals("Exponential formula string", "-1/310*4000/30000", formula);
-
- wb.close();
- }
+ cell.setCellFormula("-10E-1/3.1E2*4E3/3E4");
+ formula = cell.getCellFormula();
+ assertEquals("Exponential formula string", "-1/310*4000/30000", formula);
+
+ wb.close();
+ }
- @Test
- public void testNumbers() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ @Test
+ public void testNumbers() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");
+ wb.createSheet("Cash_Flow");
- HSSFSheet sheet = wb.createSheet("Test");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell = row.createCell(0);
- String formula = null;
+ HSSFSheet sheet = wb.createSheet("Test");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ String formula = null;
- // starts from decimal point
+ // starts from decimal point
- cell.setCellFormula(".1");
- formula = cell.getCellFormula();
- assertEquals("0.1", formula);
+ cell.setCellFormula(".1");
+ formula = cell.getCellFormula();
+ assertEquals("0.1", formula);
- cell.setCellFormula("+.1");
- formula = cell.getCellFormula();
- assertEquals("0.1", formula);
+ cell.setCellFormula("+.1");
+ formula = cell.getCellFormula();
+ assertEquals("0.1", formula);
- cell.setCellFormula("-.1");
- formula = cell.getCellFormula();
- assertEquals("-0.1", formula);
+ cell.setCellFormula("-.1");
+ formula = cell.getCellFormula();
+ assertEquals("-0.1", formula);
- // has exponent
+ // has exponent
- cell.setCellFormula("10E1");
- formula = cell.getCellFormula();
- assertEquals("100", formula);
+ cell.setCellFormula("10E1");
+ formula = cell.getCellFormula();
+ assertEquals("100", formula);
- cell.setCellFormula("10E+1");
- formula = cell.getCellFormula();
- assertEquals("100", formula);
+ cell.setCellFormula("10E+1");
+ formula = cell.getCellFormula();
+ assertEquals("100", formula);
- cell.setCellFormula("10E-1");
- formula = cell.getCellFormula();
- assertEquals("1", formula);
-
- wb.close();
- }
+ cell.setCellFormula("10E-1");
+ formula = cell.getCellFormula();
+ assertEquals("1", formula);
+
+ wb.close();
+ }
- @Test
- public void testRanges() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ @Test
+ public void testRanges() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Cash_Flow");
+ wb.createSheet("Cash_Flow");
- HSSFSheet sheet = wb.createSheet("Test");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell = row.createCell(0);
- String formula = null;
+ HSSFSheet sheet = wb.createSheet("Test");
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ String formula = null;
- cell.setCellFormula("A1.A2");
- formula = cell.getCellFormula();
- assertEquals("A1:A2", formula);
+ cell.setCellFormula("A1.A2");
+ formula = cell.getCellFormula();
+ assertEquals("A1:A2", formula);
- cell.setCellFormula("A1..A2");
- formula = cell.getCellFormula();
- assertEquals("A1:A2", formula);
+ cell.setCellFormula("A1..A2");
+ formula = cell.getCellFormula();
+ assertEquals("A1:A2", formula);
- cell.setCellFormula("A1...A2");
- formula = cell.getCellFormula();
- assertEquals("A1:A2", formula);
-
- wb.close();
- }
+ cell.setCellFormula("A1...A2");
+ formula = cell.getCellFormula();
+ assertEquals("A1:A2", formula);
+
+ wb.close();
+ }
- @Test
- public void testMultiSheetReference() throws IOException {
+ @Test
+ public void testMultiSheetReference() throws IOException {
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Cash_Flow");
wb.createSheet("Test Sheet");
-
+
HSSFSheet sheet = wb.createSheet("Test");
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell(0);
@@ -596,632 +596,632 @@ public final class TestFormulaParser {
assertEquals("Cash_Flow:\'Test Sheet\'!A1:B2", formula);
wb.close();
- }
-
- /**
- * Test for bug observable at svn revision 618865 (5-Feb-2008)
- * a formula consisting of a single no-arg function got rendered without the function braces
- */
- @Test
- public void testToFormulaStringZeroArgFunction() throws IOException {
- HSSFWorkbook book = new HSSFWorkbook();
-
- Ptg[] ptgs = {
- FuncPtg.create(10),
- };
- assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs));
-
- book.close();
- }
-
- @Test
- public void testPercent() {
-
- confirmTokenClasses("5%", IntPtg.class, PercentPtg.class);
- // spaces OK
- confirmTokenClasses(" 250 % ", IntPtg.class, PercentPtg.class);
- // double percent OK
- confirmTokenClasses("12345.678%%", NumberPtg.class, PercentPtg.class, PercentPtg.class);
-
- // percent of a bracketed expression
- confirmTokenClasses("(A1+35)%*B1%", RefPtg.class, IntPtg.class, AddPtg.class, ParenthesisPtg.class,
- PercentPtg.class, RefPtg.class, PercentPtg.class, MultiplyPtg.class);
-
- // percent of a text quantity
- confirmTokenClasses("\"8.75\"%", StringPtg.class, PercentPtg.class);
-
- // percent to the power of
- confirmTokenClasses("50%^3", IntPtg.class, PercentPtg.class, IntPtg.class, PowerPtg.class);
-
- // things that parse OK but would *evaluate* to an error
- confirmTokenClasses("\"abc\"%", StringPtg.class, PercentPtg.class);
- confirmTokenClasses("#N/A%", ErrPtg.class, PercentPtg.class);
- }
-
- /**
- * Tests combinations of various operators in the absence of brackets
- */
- @Test
- public void testPrecedenceAndAssociativity() {
-
- // TRUE=TRUE=2=2 evaluates to FALSE
- confirmTokenClasses("TRUE=TRUE=2=2", BoolPtg.class, BoolPtg.class, EqualPtg.class,
- IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class);
-
- // 2^3^2 evaluates to 64 not 512
- confirmTokenClasses("2^3^2", IntPtg.class, IntPtg.class, PowerPtg.class,
- IntPtg.class, PowerPtg.class);
-
- // "abc" & 2 + 3 & "def" evaluates to "abc5def"
- confirmTokenClasses("\"abc\"&2+3&\"def\"", StringPtg.class, IntPtg.class, IntPtg.class,
- AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class);
-
- // (1 / 2) - (3 * 4)
- confirmTokenClasses("1/2-3*4", IntPtg.class, IntPtg.class, DividePtg.class,
- IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class);
-
- // 2 * (2^2)
- // NOT: (2 *2) ^ 2 -> int int multiply int power
- confirmTokenClasses("2*2^2", IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class);
-
- // 2^200% -> 2 not 1.6E58
- confirmTokenClasses("2^200%", IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class);
- }
-
- /* package */ static Ptg[] confirmTokenClasses(String formula, Class>...expectedClasses) {
- Ptg[] ptgs = parseFormula(formula);
- confirmTokenClasses(ptgs, expectedClasses);
- return ptgs;
- }
-
- private static void confirmTokenClasses(Ptg[] ptgs, Class>...expectedClasses) {
- assertEquals(expectedClasses.length, ptgs.length);
- for (int i = 0; i < expectedClasses.length; i++) {
- if(expectedClasses[i] != ptgs[i].getClass()) {
- fail("difference at token[" + i + "]: expected ("
- + expectedClasses[i].getName() + ") but got ("
- + ptgs[i].getClass().getName() + ")");
- }
- }
- }
-
- @Test
- public void testPower() {
- confirmTokenClasses("2^5", IntPtg.class, IntPtg.class, PowerPtg.class);
- }
-
- private static Ptg parseSingleToken(String formula, Class extends Ptg> ptgClass) {
- Ptg[] ptgs = parseFormula(formula);
- assertEquals(1, ptgs.length);
- Ptg result = ptgs[0];
- assertEquals(ptgClass, result.getClass());
- return result;
- }
-
- @Test
- public void testParseNumber() {
- IntPtg ip;
-
- // bug 33160
- ip = (IntPtg) parseSingleToken("40", IntPtg.class);
- assertEquals(40, ip.getValue());
- ip = (IntPtg) parseSingleToken("40000", IntPtg.class);
- assertEquals(40000, ip.getValue());
-
- // check the upper edge of the IntPtg range:
- ip = (IntPtg) parseSingleToken("65535", IntPtg.class);
- assertEquals(65535, ip.getValue());
- NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class);
- assertEquals(65536, np.getValue(), 0);
-
- np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class);
- assertEquals(65534.6, np.getValue(), 0);
- }
-
- public void testMissingArgs() {
-
- confirmTokenClasses("if(A1, ,C1)",
- RefPtg.class,
- AttrPtg.class, // tAttrIf
- MissingArgPtg.class,
- AttrPtg.class, // tAttrSkip
- RefPtg.class,
- AttrPtg.class, // tAttrSkip
- FuncVarPtg.class
- );
-
- confirmTokenClasses("counta( , A1:B2, )", MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class,
- FuncVarPtg.class);
- }
-
- @Test
- public void testParseErrorLiterals() {
-
- confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!");
- confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!");
- confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!");
- confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!");
- confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?");
- confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!");
- confirmParseErrorLiteral(ErrPtg.N_A, "#N/A");
- parseFormula("HLOOKUP(F7,#REF!,G7,#REF!)");
- }
-
- private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) {
- assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class));
- }
-
- /**
- * To aid readability the parameters have been encoded with single quotes instead of double
- * quotes. This method converts single quotes to double quotes before performing the parse
- * and result check.
- */
- private static void confirmStringParse(String singleQuotedValue) {
- // formula: internal quotes become double double, surround with double quotes
- String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"';
- String expectedValue = singleQuotedValue.replace('\'', '"');
-
- StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class);
- assertEquals(expectedValue, sp.getValue());
- }
-
- @Test
- public void testParseStringLiterals_bug28754() throws IOException {
-
- StringPtg sp;
- try {
- sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class);
- } catch (RuntimeException e) {
- if(e.getMessage().startsWith("Cannot Parse")) {
- fail("Identified bug 28754a");
- }
- throw e;
- }
- assertEquals("test\"ing", sp.getValue());
-
- HSSFWorkbook wb = new HSSFWorkbook();
- try {
- HSSFSheet sheet = wb.createSheet();
- wb.setSheetName(0, "Sheet1");
+ }
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell = row.createCell(0);
- cell.setCellFormula("right(\"test\"\"ing\", 3)");
- String actualCellFormula = cell.getCellFormula();
- if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) {
- fail("Identified bug 28754b");
- }
- assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula);
- } finally {
- wb.close();
- }
- }
+ /**
+ * Test for bug observable at svn revision 618865 (5-Feb-2008)
+ * a formula consisting of a single no-arg function got rendered without the function braces
+ */
+ @Test
+ public void testToFormulaStringZeroArgFunction() throws IOException {
+ HSSFWorkbook book = new HSSFWorkbook();
- @Test
- public void testParseStringLiterals() {
- confirmStringParse("goto considered harmful");
+ Ptg[] ptgs = {
+ FuncPtg.create(10),
+ };
+ assertEquals("NA()", HSSFFormulaParser.toFormulaString(book, ptgs));
+
+ book.close();
+ }
- confirmStringParse("goto 'considered' harmful");
+ @Test
+ public void testPercent() {
- confirmStringParse("");
- confirmStringParse("'");
- confirmStringParse("''");
- confirmStringParse("' '");
- confirmStringParse(" ' ");
- }
+ confirmTokenClasses("5%", IntPtg.class, PercentPtg.class);
+ // spaces OK
+ confirmTokenClasses(" 250 % ", IntPtg.class, PercentPtg.class);
+ // double percent OK
+ confirmTokenClasses("12345.678%%", NumberPtg.class, PercentPtg.class, PercentPtg.class);
- @Test
- public void testParseSumIfSum() {
- String formulaString;
- Ptg[] ptgs;
- ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))");
- formulaString = toFormulaString(ptgs);
- assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString);
+ // percent of a bracketed expression
+ confirmTokenClasses("(A1+35)%*B1%", RefPtg.class, IntPtg.class, AddPtg.class, ParenthesisPtg.class,
+ PercentPtg.class, RefPtg.class, PercentPtg.class, MultiplyPtg.class);
- ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)");
- formulaString = toFormulaString(ptgs);
- assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString);
- }
-
- @Test
- public void testParserErrors() {
- parseExpectedException(" 12 . 345 ");
- parseExpectedException("1 .23 ");
+ // percent of a text quantity
+ confirmTokenClasses("\"8.75\"%", StringPtg.class, PercentPtg.class);
- parseExpectedException("sum(#NAME)");
- parseExpectedException("1 + #N / A * 2");
- parseExpectedException("#value?");
- parseExpectedException("#DIV/ 0+2");
+ // percent to the power of
+ confirmTokenClasses("50%^3", IntPtg.class, PercentPtg.class, IntPtg.class, PowerPtg.class);
+
+ // things that parse OK but would *evaluate* to an error
+ confirmTokenClasses("\"abc\"%", StringPtg.class, PercentPtg.class);
+ confirmTokenClasses("#N/A%", ErrPtg.class, PercentPtg.class);
+ }
+
+ /**
+ * Tests combinations of various operators in the absence of brackets
+ */
+ @Test
+ public void testPrecedenceAndAssociativity() {
+
+ // TRUE=TRUE=2=2 evaluates to FALSE
+ confirmTokenClasses("TRUE=TRUE=2=2", BoolPtg.class, BoolPtg.class, EqualPtg.class,
+ IntPtg.class, EqualPtg.class, IntPtg.class, EqualPtg.class);
+
+ // 2^3^2 evaluates to 64 not 512
+ confirmTokenClasses("2^3^2", IntPtg.class, IntPtg.class, PowerPtg.class,
+ IntPtg.class, PowerPtg.class);
+
+ // "abc" & 2 + 3 & "def" evaluates to "abc5def"
+ confirmTokenClasses("\"abc\"&2+3&\"def\"", StringPtg.class, IntPtg.class, IntPtg.class,
+ AddPtg.class, ConcatPtg.class, StringPtg.class, ConcatPtg.class);
+
+ // (1 / 2) - (3 * 4)
+ confirmTokenClasses("1/2-3*4", IntPtg.class, IntPtg.class, DividePtg.class,
+ IntPtg.class, IntPtg.class, MultiplyPtg.class, SubtractPtg.class);
+
+ // 2 * (2^2)
+ // NOT: (2 *2) ^ 2 -> int int multiply int power
+ confirmTokenClasses("2*2^2", IntPtg.class, IntPtg.class, IntPtg.class, PowerPtg.class, MultiplyPtg.class);
+
+ // 2^200% -> 2 not 1.6E58
+ confirmTokenClasses("2^200%", IntPtg.class, IntPtg.class, PercentPtg.class, PowerPtg.class);
+ }
+
+ /* package */ static Ptg[] confirmTokenClasses(String formula, Class>...expectedClasses) {
+ Ptg[] ptgs = parseFormula(formula);
+ confirmTokenClasses(ptgs, expectedClasses);
+ return ptgs;
+ }
+
+ private static void confirmTokenClasses(Ptg[] ptgs, Class>...expectedClasses) {
+ assertEquals(expectedClasses.length, ptgs.length);
+ for (int i = 0; i < expectedClasses.length; i++) {
+ if(expectedClasses[i] != ptgs[i].getClass()) {
+ fail("difference at token[" + i + "]: expected ("
+ + expectedClasses[i].getName() + ") but got ("
+ + ptgs[i].getClass().getName() + ")");
+ }
+ }
+ }
+
+ @Test
+ public void testPower() {
+ confirmTokenClasses("2^5", IntPtg.class, IntPtg.class, PowerPtg.class);
+ }
+
+ private static Ptg parseSingleToken(String formula, Class extends Ptg> ptgClass) {
+ Ptg[] ptgs = parseFormula(formula);
+ assertEquals(1, ptgs.length);
+ Ptg result = ptgs[0];
+ assertEquals(ptgClass, result.getClass());
+ return result;
+ }
+
+ @Test
+ public void testParseNumber() {
+ IntPtg ip;
+
+ // bug 33160
+ ip = (IntPtg) parseSingleToken("40", IntPtg.class);
+ assertEquals(40, ip.getValue());
+ ip = (IntPtg) parseSingleToken("40000", IntPtg.class);
+ assertEquals(40000, ip.getValue());
+
+ // check the upper edge of the IntPtg range:
+ ip = (IntPtg) parseSingleToken("65535", IntPtg.class);
+ assertEquals(65535, ip.getValue());
+ NumberPtg np = (NumberPtg) parseSingleToken("65536", NumberPtg.class);
+ assertEquals(65536, np.getValue(), 0);
+
+ np = (NumberPtg) parseSingleToken("65534.6", NumberPtg.class);
+ assertEquals(65534.6, np.getValue(), 0);
+ }
+
+ public void testMissingArgs() {
+
+ confirmTokenClasses("if(A1, ,C1)",
+ RefPtg.class,
+ AttrPtg.class, // tAttrIf
+ MissingArgPtg.class,
+ AttrPtg.class, // tAttrSkip
+ RefPtg.class,
+ AttrPtg.class, // tAttrSkip
+ FuncVarPtg.class
+ );
+
+ confirmTokenClasses("counta( , A1:B2, )", MissingArgPtg.class, AreaPtg.class, MissingArgPtg.class,
+ FuncVarPtg.class);
+ }
+
+ @Test
+ public void testParseErrorLiterals() {
+
+ confirmParseErrorLiteral(ErrPtg.NULL_INTERSECTION, "#NULL!");
+ confirmParseErrorLiteral(ErrPtg.DIV_ZERO, "#DIV/0!");
+ confirmParseErrorLiteral(ErrPtg.VALUE_INVALID, "#VALUE!");
+ confirmParseErrorLiteral(ErrPtg.REF_INVALID, "#REF!");
+ confirmParseErrorLiteral(ErrPtg.NAME_INVALID, "#NAME?");
+ confirmParseErrorLiteral(ErrPtg.NUM_ERROR, "#NUM!");
+ confirmParseErrorLiteral(ErrPtg.N_A, "#N/A");
+ parseFormula("HLOOKUP(F7,#REF!,G7,#REF!)");
+ }
+
+ private static void confirmParseErrorLiteral(ErrPtg expectedToken, String formula) {
+ assertEquals(expectedToken, parseSingleToken(formula, ErrPtg.class));
+ }
+
+ /**
+ * To aid readability the parameters have been encoded with single quotes instead of double
+ * quotes. This method converts single quotes to double quotes before performing the parse
+ * and result check.
+ */
+ private static void confirmStringParse(String singleQuotedValue) {
+ // formula: internal quotes become double double, surround with double quotes
+ String formula = '"' + singleQuotedValue.replaceAll("'", "\"\"") + '"';
+ String expectedValue = singleQuotedValue.replace('\'', '"');
+
+ StringPtg sp = (StringPtg) parseSingleToken(formula, StringPtg.class);
+ assertEquals(expectedValue, sp.getValue());
+ }
+
+ @Test
+ public void testParseStringLiterals_bug28754() throws IOException {
+
+ StringPtg sp;
+ try {
+ sp = (StringPtg) parseSingleToken("\"test\"\"ing\"", StringPtg.class);
+ } catch (RuntimeException e) {
+ if(e.getMessage().startsWith("Cannot Parse")) {
+ fail("Identified bug 28754a");
+ }
+ throw e;
+ }
+ assertEquals("test\"ing", sp.getValue());
+
+ HSSFWorkbook wb = new HSSFWorkbook();
+ try {
+ HSSFSheet sheet = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ cell.setCellFormula("right(\"test\"\"ing\", 3)");
+ String actualCellFormula = cell.getCellFormula();
+ if("RIGHT(\"test\"ing\",3)".equals(actualCellFormula)) {
+ fail("Identified bug 28754b");
+ }
+ assertEquals("RIGHT(\"test\"\"ing\",3)", actualCellFormula);
+ } finally {
+ wb.close();
+ }
+ }
+
+ @Test
+ public void testParseStringLiterals() {
+ confirmStringParse("goto considered harmful");
+
+ confirmStringParse("goto 'considered' harmful");
+
+ confirmStringParse("");
+ confirmStringParse("'");
+ confirmStringParse("''");
+ confirmStringParse("' '");
+ confirmStringParse(" ' ");
+ }
+
+ @Test
+ public void testParseSumIfSum() {
+ String formulaString;
+ Ptg[] ptgs;
+ ptgs = parseFormula("sum(5, 2, if(3>2, sum(A1:A2), 6))");
+ formulaString = toFormulaString(ptgs);
+ assertEquals("SUM(5,2,IF(3>2,SUM(A1:A2),6))", formulaString);
+
+ ptgs = parseFormula("if(1<2,sum(5, 2, if(3>2, sum(A1:A2), 6)),4)");
+ formulaString = toFormulaString(ptgs);
+ assertEquals("IF(1<2,SUM(5,2,IF(3>2,SUM(A1:A2),6)),4)", formulaString);
+ }
+
+ @Test
+ public void testParserErrors() {
+ parseExpectedException(" 12 . 345 ");
+ parseExpectedException("1 .23 ");
+
+ parseExpectedException("sum(#NAME)");
+ parseExpectedException("1 + #N / A * 2");
+ parseExpectedException("#value?");
+ parseExpectedException("#DIV/ 0+2");
- parseExpectedException("IF(TRUE)");
- parseExpectedException("countif(A1:B5, C1, D1)");
-
- parseExpectedException("(");
- parseExpectedException(")");
- parseExpectedException("+");
- parseExpectedException("42+");
-
- parseExpectedException("IF(");
- }
+ parseExpectedException("IF(TRUE)");
+ parseExpectedException("countif(A1:B5, C1, D1)");
+
+ parseExpectedException("(");
+ parseExpectedException(")");
+ parseExpectedException("+");
+ parseExpectedException("42+");
+
+ parseExpectedException("IF(");
+ }
- private static void parseExpectedException(String formula) {
- try {
- parseFormula(formula);
- fail("Expected FormulaParseException: " + formula);
- } catch (FormulaParseException e) {
- // expected during successful test
- assertNotNull(e.getMessage());
- }
- }
+ private static void parseExpectedException(String formula) {
+ try {
+ parseFormula(formula);
+ fail("Expected FormulaParseException: " + formula);
+ } catch (FormulaParseException e) {
+ // expected during successful test
+ assertNotNull(e.getMessage());
+ }
+ }
- @Test
- public void testSetFormulaWithRowBeyond32768_Bug44539() throws IOException {
+ @Test
+ public void testSetFormulaWithRowBeyond32768_Bug44539() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet = wb.createSheet();
- wb.setSheetName(0, "Sheet1");
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet();
+ wb.setSheetName(0, "Sheet1");
- HSSFRow row = sheet.createRow(0);
- HSSFCell cell = row.createCell(0);
- cell.setCellFormula("SUM(A32769:A32770)");
- if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) {
- fail("Identified bug 44539");
- }
- assertEquals("SUM(A32769:A32770)", cell.getCellFormula());
-
- wb.close();
- }
+ HSSFRow row = sheet.createRow(0);
+ HSSFCell cell = row.createCell(0);
+ cell.setCellFormula("SUM(A32769:A32770)");
+ if("SUM(A-32767:A-32766)".equals(cell.getCellFormula())) {
+ fail("Identified bug 44539");
+ }
+ assertEquals("SUM(A32769:A32770)", cell.getCellFormula());
+
+ wb.close();
+ }
- @Test
- public void testSpaceAtStartOfFormula() {
- // Simulating cell formula of "= 4" (note space)
- // The same Ptg array can be observed if an excel file is saved with that exact formula
+ @Test
+ public void testSpaceAtStartOfFormula() {
+ // Simulating cell formula of "= 4" (note space)
+ // The same Ptg array can be observed if an excel file is saved with that exact formula
- AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1);
- Ptg[] ptgs = { spacePtg, new IntPtg(4), };
- String formulaString;
- try {
- formulaString = toFormulaString(ptgs);
- } catch (IllegalStateException e) {
- if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) {
- fail("Identified bug 44609");
- }
- // else some unexpected error
- throw e;
- }
- // FormulaParser strips spaces anyway
- assertEquals("4", formulaString);
+ AttrPtg spacePtg = AttrPtg.createSpace(AttrPtg.SpaceType.SPACE_BEFORE, 1);
+ Ptg[] ptgs = { spacePtg, new IntPtg(4), };
+ String formulaString;
+ try {
+ formulaString = toFormulaString(ptgs);
+ } catch (IllegalStateException e) {
+ if(e.getMessage().equalsIgnoreCase("too much stuff left on the stack")) {
+ fail("Identified bug 44609");
+ }
+ // else some unexpected error
+ throw e;
+ }
+ // FormulaParser strips spaces anyway
+ assertEquals("4", formulaString);
- ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, };
- formulaString = toFormulaString(ptgs);
- assertEquals("3+4", formulaString);
- }
+ ptgs = new Ptg[] { new IntPtg(3), spacePtg, new IntPtg(4), spacePtg, AddPtg.instance, };
+ formulaString = toFormulaString(ptgs);
+ assertEquals("3+4", formulaString);
+ }
- /**
- * Checks some internal error detecting logic ('stack underflow error' in toFormulaString)
- */
- @Test
- public void testTooFewOperandArgs() {
- // Simulating badly encoded cell formula of "=/1"
- // Not sure if Excel could ever produce this
- Ptg[] ptgs = {
- // Excel would probably have put tMissArg here
- new IntPtg(1),
- DividePtg.instance,
- };
- try {
- toFormulaString(ptgs);
- fail("Expected exception was not thrown");
- } catch (IllegalStateException e) {
- // expected during successful test
- assertTrue(e.getMessage().startsWith("Too few arguments supplied to operation"));
- }
- }
- /**
- * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable
- * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.
- *
- * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases
- * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
- * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
- */
- @Test
- public void testFuncPtgSelection() {
+ /**
+ * Checks some internal error detecting logic ('stack underflow error' in toFormulaString)
+ */
+ @Test
+ public void testTooFewOperandArgs() {
+ // Simulating badly encoded cell formula of "=/1"
+ // Not sure if Excel could ever produce this
+ Ptg[] ptgs = {
+ // Excel would probably have put tMissArg here
+ new IntPtg(1),
+ DividePtg.instance,
+ };
+ try {
+ toFormulaString(ptgs);
+ fail("Expected exception was not thrown");
+ } catch (IllegalStateException e) {
+ // expected during successful test
+ assertTrue(e.getMessage().startsWith("Too few arguments supplied to operation"));
+ }
+ }
+ /**
+ * Make sure that POI uses the right Func Ptg when encoding formulas. Functions with variable
+ * number of args should get FuncVarPtg, functions with fixed args should get FuncPtg.
+ *
+ * Prior to the fix for bug 44675 POI would encode FuncVarPtg for all functions. In many cases
+ * Excel tolerates the wrong Ptg and evaluates the formula OK (e.g. SIN), but in some cases
+ * (e.g. COUNTIF) Excel fails to evaluate the formula, giving '#VALUE!' instead.
+ */
+ @Test
+ public void testFuncPtgSelection() {
- Ptg[] ptgs = parseFormula("countif(A1:A2, 1)");
- assertEquals(3, ptgs.length);
- if(ptgs[2] instanceof FuncVarPtg) {
- fail("Identified bug 44675");
- }
- confirmTokenClasses(ptgs, AreaPtg.class, IntPtg.class, FuncPtg.class);
+ Ptg[] ptgs = parseFormula("countif(A1:A2, 1)");
+ assertEquals(3, ptgs.length);
+ if(ptgs[2] instanceof FuncVarPtg) {
+ fail("Identified bug 44675");
+ }
+ confirmTokenClasses(ptgs, AreaPtg.class, IntPtg.class, FuncPtg.class);
- confirmTokenClasses("sin(1)", IntPtg.class, FuncPtg.class);
- }
+ confirmTokenClasses("sin(1)", IntPtg.class, FuncPtg.class);
+ }
- @Test
- public void testWrongNumberOfFunctionArgs() throws IOException {
- confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0.");
- confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4.");
- confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6.");
- confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2.");
- }
+ @Test
+ public void testWrongNumberOfFunctionArgs() throws IOException {
+ confirmArgCountMsg("sin()", "Too few arguments to function 'SIN'. Expected 1 but got 0.");
+ confirmArgCountMsg("countif(1, 2, 3, 4)", "Too many arguments to function 'COUNTIF'. Expected 2 but got 4.");
+ confirmArgCountMsg("index(1, 2, 3, 4, 5, 6)", "Too many arguments to function 'INDEX'. At most 4 were expected but got 6.");
+ confirmArgCountMsg("vlookup(1, 2)", "Too few arguments to function 'VLOOKUP'. At least 3 were expected but got 2.");
+ }
- private static void confirmArgCountMsg(String formula, String expectedMessage) throws IOException {
- HSSFWorkbook book = new HSSFWorkbook();
- try {
- HSSFFormulaParser.parse(formula, book);
- fail("Didn't get parse exception as expected");
- } catch (FormulaParseException e) {
- confirmParseException(e, expectedMessage);
- }
- book.close();
- }
+ private static void confirmArgCountMsg(String formula, String expectedMessage) throws IOException {
+ HSSFWorkbook book = new HSSFWorkbook();
+ try {
+ HSSFFormulaParser.parse(formula, book);
+ fail("Didn't get parse exception as expected");
+ } catch (FormulaParseException e) {
+ confirmParseException(e, expectedMessage);
+ }
+ book.close();
+ }
- @Test
- public void testParseErrorExpectedMsg() {
+ @Test
+ public void testParseErrorExpectedMsg() {
- try {
- parseFormula("round(3.14;2)");
- fail("Didn't get parse exception as expected");
- } catch (FormulaParseException e) {
- confirmParseException(e,
- "Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'");
- }
+ try {
+ parseFormula("round(3.14;2)");
+ fail("Didn't get parse exception as expected");
+ } catch (FormulaParseException e) {
+ confirmParseException(e,
+ "Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'");
+ }
- try {
- parseFormula(" =2+2");
- fail("Didn't get parse exception as expected");
- } catch (FormulaParseException e) {
- confirmParseException(e,
- "The specified formula ' =2+2' starts with an equals sign which is not allowed.");
- }
- }
+ try {
+ parseFormula(" =2+2");
+ fail("Didn't get parse exception as expected");
+ } catch (FormulaParseException e) {
+ confirmParseException(e,
+ "The specified formula ' =2+2' starts with an equals sign which is not allowed.");
+ }
+ }
- /**
- * this function name has a dot in it.
- */
- @Test
- public void testParseErrorTypeFunction() {
+ /**
+ * this function name has a dot in it.
+ */
+ @Test
+ public void testParseErrorTypeFunction() {
- Ptg[] ptgs;
- try {
- ptgs = parseFormula("error.type(A1)");
- } catch (IllegalArgumentException e) {
- if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
- fail("Identified bug 45334");
- }
- throw e;
- }
- confirmTokenClasses(ptgs, RefPtg.class, FuncPtg.class);
- assertEquals("ERROR.TYPE", ((FuncPtg) ptgs[1]).getName());
- }
+ Ptg[] ptgs;
+ try {
+ ptgs = parseFormula("error.type(A1)");
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) {
+ fail("Identified bug 45334");
+ }
+ throw e;
+ }
+ confirmTokenClasses(ptgs, RefPtg.class, FuncPtg.class);
+ assertEquals("ERROR.TYPE", ((FuncPtg) ptgs[1]).getName());
+ }
- @Test
- public void testNamedRangeThatLooksLikeCell() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet = wb.createSheet("Sheet1");
- HSSFName name = wb.createName();
- name.setRefersToFormula("Sheet1!B1");
- name.setNameName("pfy1");
+ @Test
+ public void testNamedRangeThatLooksLikeCell() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Sheet1");
+ HSSFName name = wb.createName();
+ name.setRefersToFormula("Sheet1!B1");
+ name.setNameName("pfy1");
- Ptg[] ptgs;
- try {
- ptgs = HSSFFormulaParser.parse("count(pfy1)", wb);
- } catch (IllegalArgumentException e) {
- if (e.getMessage().equals("Specified colIx (1012) is out of range")) {
- fail("Identified bug 45354");
- }
- wb.close();
- throw e;
- }
- confirmTokenClasses(ptgs, NamePtg.class, FuncVarPtg.class);
+ Ptg[] ptgs;
+ try {
+ ptgs = HSSFFormulaParser.parse("count(pfy1)", wb);
+ } catch (IllegalArgumentException e) {
+ if (e.getMessage().equals("Specified colIx (1012) is out of range")) {
+ fail("Identified bug 45354");
+ }
+ wb.close();
+ throw e;
+ }
+ confirmTokenClasses(ptgs, NamePtg.class, FuncVarPtg.class);
- HSSFCell cell = sheet.createRow(0).createCell(0);
- cell.setCellFormula("count(pfy1)");
- assertEquals("COUNT(pfy1)", cell.getCellFormula());
- try {
- cell.setCellFormula("count(pf1)");
- fail("Expected formula parse execption");
- } catch (FormulaParseException e) {
- confirmParseException(e,
- "Specified named range 'pf1' does not exist in the current workbook.");
- }
- cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
- wb.close();
- }
+ HSSFCell cell = sheet.createRow(0).createCell(0);
+ cell.setCellFormula("count(pfy1)");
+ assertEquals("COUNT(pfy1)", cell.getCellFormula());
+ try {
+ cell.setCellFormula("count(pf1)");
+ fail("Expected formula parse execption");
+ } catch (FormulaParseException e) {
+ confirmParseException(e,
+ "Specified named range 'pf1' does not exist in the current workbook.");
+ }
+ cell.setCellFormula("count(fp1)"); // plain cell ref, col is in range
+ wb.close();
+ }
- @Test
- public void testParseAreaRefHighRow_bug45358() throws IOException {
- Ptg[] ptgs;
- AreaI aptg;
+ @Test
+ public void testParseAreaRefHighRow_bug45358() throws IOException {
+ Ptg[] ptgs;
+ AreaI aptg;
- HSSFWorkbook book = new HSSFWorkbook();
- book.createSheet("Sheet1");
+ HSSFWorkbook book = new HSSFWorkbook();
+ book.createSheet("Sheet1");
- ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
- aptg = (AreaI) ptgs[0];
- if (aptg.getLastRow() == -25537) {
- fail("Identified bug 45358");
- }
- assertEquals(39999, aptg.getLastRow());
+ ptgs = HSSFFormulaParser.parse("Sheet1!A10:A40000", book);
+ aptg = (AreaI) ptgs[0];
+ if (aptg.getLastRow() == -25537) {
+ fail("Identified bug 45358");
+ }
+ assertEquals(39999, aptg.getLastRow());
- ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
- aptg = (AreaI) ptgs[0];
- assertEquals(65535, aptg.getLastRow());
+ ptgs = HSSFFormulaParser.parse("Sheet1!A10:A65536", book);
+ aptg = (AreaI) ptgs[0];
+ assertEquals(65535, aptg.getLastRow());
- // plain area refs should be ok too
- ptgs = parseFormula("A10:A65536");
- aptg = (AreaI) ptgs[0];
- assertEquals(65535, aptg.getLastRow());
+ // plain area refs should be ok too
+ ptgs = parseFormula("A10:A65536");
+ aptg = (AreaI) ptgs[0];
+ assertEquals(65535, aptg.getLastRow());
- book.close();
- }
-
- @Test
- public void testParseArray() {
- Ptg[] ptgs;
- ptgs = parseFormula("mode({1,2,2,#REF!;FALSE,3,3,2})");
- confirmTokenClasses(ptgs, ArrayPtg.class, FuncVarPtg.class);
- assertEquals("{1,2,2,#REF!;FALSE,3,3,2}", ptgs[0].toFormulaString());
+ book.close();
+ }
+
+ @Test
+ public void testParseArray() {
+ Ptg[] ptgs;
+ ptgs = parseFormula("mode({1,2,2,#REF!;FALSE,3,3,2})");
+ confirmTokenClasses(ptgs, ArrayPtg.class, FuncVarPtg.class);
+ assertEquals("{1,2,2,#REF!;FALSE,3,3,2}", ptgs[0].toFormulaString());
- ArrayPtg aptg = (ArrayPtg) ptgs[0];
- Object[][] values = aptg.getTokenArrayValues();
- assertEquals(ErrorConstant.valueOf(FormulaError.REF.getCode()), values[0][3]);
- assertEquals(Boolean.FALSE, values[1][0]);
- }
+ ArrayPtg aptg = (ArrayPtg) ptgs[0];
+ Object[][] values = aptg.getTokenArrayValues();
+ assertEquals(ErrorConstant.valueOf(FormulaError.REF.getCode()), values[0][3]);
+ assertEquals(Boolean.FALSE, values[1][0]);
+ }
- @Test
- public void testParseStringElementInArray() {
- Ptg[] ptgs;
- ptgs = parseFormula("MAX({\"5\"},3)");
- confirmTokenClasses(ptgs, ArrayPtg.class, IntPtg.class, FuncVarPtg.class);
- Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
- if (element instanceof UnicodeString) {
- // this would cause ClassCastException below
- fail("Wrong encoding of array element value");
- }
- assertEquals(String.class, element.getClass());
+ @Test
+ public void testParseStringElementInArray() {
+ Ptg[] ptgs;
+ ptgs = parseFormula("MAX({\"5\"},3)");
+ confirmTokenClasses(ptgs, ArrayPtg.class, IntPtg.class, FuncVarPtg.class);
+ Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
+ if (element instanceof UnicodeString) {
+ // this would cause ClassCastException below
+ fail("Wrong encoding of array element value");
+ }
+ assertEquals(String.class, element.getClass());
- // make sure the formula encodes OK
- int encSize = Ptg.getEncodedSize(ptgs);
- byte[] data = new byte[encSize];
- Ptg.serializePtgs(ptgs, data, 0);
- byte[] expData = HexRead.readFromString(
- "20 00 00 00 00 00 00 00 " // tArray
- + "1E 03 00 " // tInt(3)
- + "42 02 07 00 " // tFuncVar(MAX) 2-arg
- + "00 00 00 " // Array data: 1 col, 1 row
- + "02 01 00 00 35" // elem (type=string, len=1, "5")
- );
- assertArrayEquals(expData, data);
- int initSize = Ptg.getEncodedSizeWithoutArrayData(ptgs);
- Ptg[] ptgs2 = Ptg.readTokens(initSize, new LittleEndianByteArrayInputStream(data));
- confirmTokenClasses(ptgs2, ArrayPtg.class, IntPtg.class, FuncVarPtg.class);
- }
+ // make sure the formula encodes OK
+ int encSize = Ptg.getEncodedSize(ptgs);
+ byte[] data = new byte[encSize];
+ Ptg.serializePtgs(ptgs, data, 0);
+ byte[] expData = HexRead.readFromString(
+ "20 00 00 00 00 00 00 00 " // tArray
+ + "1E 03 00 " // tInt(3)
+ + "42 02 07 00 " // tFuncVar(MAX) 2-arg
+ + "00 00 00 " // Array data: 1 col, 1 row
+ + "02 01 00 00 35" // elem (type=string, len=1, "5")
+ );
+ assertArrayEquals(expData, data);
+ int initSize = Ptg.getEncodedSizeWithoutArrayData(ptgs);
+ Ptg[] ptgs2 = Ptg.readTokens(initSize, new LittleEndianByteArrayInputStream(data));
+ confirmTokenClasses(ptgs2, ArrayPtg.class, IntPtg.class, FuncVarPtg.class);
+ }
- @Test
- public void testParseArrayNegativeElement() {
- Ptg[] ptgs;
- try {
- ptgs = parseFormula("{-42}");
- } catch (FormulaParseException e) {
- if (e.getMessage().equals("Parse error near char 1 '-' in specified formula '{-42}'. Expected Integer")) {
- fail("Identified bug - failed to parse negative array element.");
- }
- throw e;
- }
- confirmTokenClasses(ptgs, ArrayPtg.class);
- Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
+ @Test
+ public void testParseArrayNegativeElement() {
+ Ptg[] ptgs;
+ try {
+ ptgs = parseFormula("{-42}");
+ } catch (FormulaParseException e) {
+ if (e.getMessage().equals("Parse error near char 1 '-' in specified formula '{-42}'. Expected Integer")) {
+ fail("Identified bug - failed to parse negative array element.");
+ }
+ throw e;
+ }
+ confirmTokenClasses(ptgs, ArrayPtg.class);
+ Object element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
- assertEquals(-42.0, ((Double)element).doubleValue(), 0.0);
+ assertEquals(-42.0, ((Double)element).doubleValue(), 0.0);
- // Should be able to handle whitespace between unary minus and digits (Excel
- // accepts this formula after presenting the user with a confirmation dialog).
- ptgs = parseFormula("{- 5}");
- element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
- assertEquals(-5.0, ((Double)element).doubleValue(), 0.0);
- }
+ // Should be able to handle whitespace between unary minus and digits (Excel
+ // accepts this formula after presenting the user with a confirmation dialog).
+ ptgs = parseFormula("{- 5}");
+ element = ((ArrayPtg)ptgs[0]).getTokenArrayValues()[0][0];
+ assertEquals(-5.0, ((Double)element).doubleValue(), 0.0);
+ }
- @Test
- public void testRangeOperator() throws IOException {
+ @Test
+ public void testRangeOperator() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet = wb.createSheet();
- HSSFCell cell = sheet.createRow(0).createCell(0);
+ 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());
+ 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!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());
+ 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());
-
- wb.close();
- }
+ // with funny sheet name
+ wb.setSheetName(0, "A1...A2");
+ cell.setCellFormula("A1...A2!B1");
+ assertEquals("A1...A2!B1", cell.getCellFormula());
+
+ wb.close();
+ }
- @Test
- public void testBooleanNamedSheet() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet = wb.createSheet("true");
- HSSFCell cell = sheet.createRow(0).createCell(0);
- cell.setCellFormula("'true'!B2");
+ @Test
+ public void testBooleanNamedSheet() throws IOException {
+ 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());
-
- wb.close();
- }
+ assertEquals("'true'!B2", cell.getCellFormula());
+
+ wb.close();
+ }
- @Test
- public void testParseExternalWorkbookReference() throws IOException {
- HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
- HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
+ @Test
+ public void testParseExternalWorkbookReference() throws IOException {
+ HSSFWorkbook wbA = HSSFTestDataSamples.openSampleWorkbook("multibookFormulaA.xls");
+ HSSFCell cell = wbA.getSheetAt(0).getRow(0).getCell(0);
- // make sure formula in sample is as expected
- assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
- Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
- confirmSingle3DRef(expectedPtgs, 1);
+ // make sure formula in sample is as expected
+ assertEquals("[multibookFormulaB.xls]BSheet1!B1", cell.getCellFormula());
+ Ptg[] expectedPtgs = FormulaExtractor.getPtgs(cell);
+ confirmSingle3DRef(expectedPtgs, 1);
- // now try (re-)parsing the formula
- Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
- confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
+ // now try (re-)parsing the formula
+ Ptg[] actualPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]BSheet1!B1", wbA);
+ confirmSingle3DRef(actualPtgs, 1); // externalSheetIndex 1 -> BSheet1
- // try parsing a formula pointing to a different external sheet
- Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
- confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
+ // try parsing a formula pointing to a different external sheet
+ Ptg[] otherPtgs = HSSFFormulaParser.parse("[multibookFormulaB.xls]AnotherSheet!B1", wbA);
+ confirmSingle3DRef(otherPtgs, 0); // externalSheetIndex 0 -> AnotherSheet
- // try setting the same formula in a cell
- cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
- assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
-
- wbA.close();
- }
-
- private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
- assertEquals(1, ptgs.length);
- Ptg ptg0 = ptgs[0];
- assertTrue(ptg0 instanceof Ref3DPtg);
- assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
- }
+ // try setting the same formula in a cell
+ cell.setCellFormula("[multibookFormulaB.xls]AnotherSheet!B1");
+ assertEquals("[multibookFormulaB.xls]AnotherSheet!B1", cell.getCellFormula());
+
+ wbA.close();
+ }
+
+ private static void confirmSingle3DRef(Ptg[] ptgs, int expectedExternSheetIndex) {
+ assertEquals(1, ptgs.length);
+ Ptg ptg0 = ptgs[0];
+ assertTrue(ptg0 instanceof Ref3DPtg);
+ assertEquals(expectedExternSheetIndex, ((Ref3DPtg)ptg0).getExternSheetIndex());
+ }
- @Test
- public void testUnion() throws IOException {
- String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6";
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
- Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
+ @Test
+ public void testUnion() throws IOException {
+ String formula = "Sheet1!$B$2:$C$3,OFFSET(Sheet1!$E$2:$E$4,1,Sheet1!$A$1),Sheet1!$D$6";
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
+ Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
- confirmTokenClasses(ptgs,
- // TODO - AttrPtg.class, // Excel prepends this
- MemFuncPtg.class,
- Area3DPtg.class,
- Area3DPtg.class,
- IntPtg.class,
- Ref3DPtg.class,
- FuncVarPtg.class,
- UnionPtg.class,
- Ref3DPtg.class,
- UnionPtg.class
- );
- MemFuncPtg mf = (MemFuncPtg)ptgs[0];
- assertEquals(45, mf.getLenRefSubexpression());
+ confirmTokenClasses(ptgs,
+ // TODO - AttrPtg.class, // Excel prepends this
+ MemFuncPtg.class,
+ Area3DPtg.class,
+ Area3DPtg.class,
+ IntPtg.class,
+ Ref3DPtg.class,
+ FuncVarPtg.class,
+ UnionPtg.class,
+ Ref3DPtg.class,
+ UnionPtg.class
+ );
+ MemFuncPtg mf = (MemFuncPtg)ptgs[0];
+ assertEquals(45, mf.getLenRefSubexpression());
// We don't check the type of the operands.
confirmTokenClasses("1,2", MemAreaPtg.class, IntPtg.class, IntPtg.class, UnionPtg.class);
wb.close();
- }
+ }
- @Test
- public void testIntersection() throws IOException {
+ @Test
+ public void testIntersection() throws IOException {
String formula = "Sheet1!$B$2:$C$3 OFFSET(Sheet1!$E$2:$E$4, 1,Sheet1!$A$1) Sheet1!$D$6";
HSSFWorkbook wb = new HSSFWorkbook();
wb.createSheet("Sheet1");
@@ -1246,30 +1246,30 @@ public final class TestFormulaParser {
confirmTokenClasses("1 2", MemAreaPtg.class, IntPtg.class, IntPtg.class, IntersectionPtg.class);
wb.close();
- }
-
- @Test
- public void testComparisonInParen() {
- confirmTokenClasses("(A1 > B2)",
+ }
+
+ @Test
+ public void testComparisonInParen() {
+ confirmTokenClasses("(A1 > B2)",
RefPtg.class,
RefPtg.class,
GreaterThanPtg.class,
ParenthesisPtg.class
);
- }
-
- @Test
- public void testUnionInParen() {
- confirmTokenClasses("(A1:B2,B2:C3)",
+ }
+
+ @Test
+ public void testUnionInParen() {
+ confirmTokenClasses("(A1:B2,B2:C3)",
MemAreaPtg.class,
AreaPtg.class,
AreaPtg.class,
UnionPtg.class,
ParenthesisPtg.class
);
- }
+ }
- @Test
+ @Test
public void testIntersectionInParen() {
confirmTokenClasses("(A1:B2 B2:C3)",
MemAreaPtg.class,
@@ -1280,316 +1280,316 @@ public final class TestFormulaParser {
);
}
- @Test
- public void testRange_bug46643() throws IOException {
- String formula = "Sheet1!A1:Sheet1!B3";
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
- Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1);
+ @Test
+ public void testRange_bug46643() throws IOException {
+ String formula = "Sheet1!A1:Sheet1!B3";
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
+ Ptg[] ptgs = FormulaParser.parse(formula, HSSFEvaluationWorkbook.create(wb), FormulaType.CELL, -1, -1);
- if (ptgs.length == 3) {
- confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class);
- fail("Identified bug 46643");
- }
+ if (ptgs.length == 3) {
+ confirmTokenClasses(ptgs, Ref3DPtg.class, Ref3DPtg.class, RangePtg.class);
+ fail("Identified bug 46643");
+ }
- confirmTokenClasses(ptgs,
- MemFuncPtg.class,
- Ref3DPtg.class,
- Ref3DPtg.class,
- RangePtg.class
- );
- MemFuncPtg mf = (MemFuncPtg)ptgs[0];
- assertEquals(15, mf.getLenRefSubexpression());
- wb.close();
- }
+ confirmTokenClasses(ptgs,
+ MemFuncPtg.class,
+ Ref3DPtg.class,
+ Ref3DPtg.class,
+ RangePtg.class
+ );
+ MemFuncPtg mf = (MemFuncPtg)ptgs[0];
+ assertEquals(15, mf.getLenRefSubexpression());
+ wb.close();
+ }
- /** Named ranges with backslashes, e.g. 'POI\\2009' */
- @Test
- public void testBackSlashInNames() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
+ /** Named ranges with backslashes, e.g. 'POI\\2009' */
+ @Test
+ public void testBackSlashInNames() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
- HSSFName name = wb.createName();
- name.setNameName("POI\\2009");
- name.setRefersToFormula("Sheet1!$A$1");
+ HSSFName name = wb.createName();
+ name.setNameName("POI\\2009");
+ name.setRefersToFormula("Sheet1!$A$1");
- HSSFSheet sheet = wb.createSheet();
- HSSFRow row = sheet.createRow(0);
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
- HSSFCell cell_C1 = row.createCell(2);
- cell_C1.setCellFormula("POI\\2009");
- assertEquals("POI\\2009", cell_C1.getCellFormula());
+ HSSFCell cell_C1 = row.createCell(2);
+ cell_C1.setCellFormula("POI\\2009");
+ assertEquals("POI\\2009", cell_C1.getCellFormula());
- HSSFCell cell_D1 = row.createCell(2);
- cell_D1.setCellFormula("NOT(POI\\2009=\"3.5-final\")");
- assertEquals("NOT(POI\\2009=\"3.5-final\")", cell_D1.getCellFormula());
-
- wb.close();
- }
+ HSSFCell cell_D1 = row.createCell(2);
+ cell_D1.setCellFormula("NOT(POI\\2009=\"3.5-final\")");
+ assertEquals("NOT(POI\\2009=\"3.5-final\")", cell_D1.getCellFormula());
+
+ wb.close();
+ }
- /**
- * TODO - delete equiv test:
- * {@link BaseTestBugzillaIssues#test42448()}
- */
- @Test
- public void testParseAbnormalSheetNamesAndRanges_bug42448() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("A");
- try {
- HSSFFormulaParser.parse("SUM(A!C7:A!C67)", wb);
- } catch (StringIndexOutOfBoundsException e) {
- fail("Identified bug 42448");
- }
- // the exact example from the bugzilla description:
- HSSFFormulaParser.parse("SUMPRODUCT(A!C7:A!C67, B8:B68) / B69", wb);
-
- wb.close();
- }
+ /**
+ * TODO - delete equiv test:
+ * {@link BaseTestBugzillaIssues#test42448()}
+ */
+ @Test
+ public void testParseAbnormalSheetNamesAndRanges_bug42448() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("A");
+ try {
+ HSSFFormulaParser.parse("SUM(A!C7:A!C67)", wb);
+ } catch (StringIndexOutOfBoundsException e) {
+ fail("Identified bug 42448");
+ }
+ // the exact example from the bugzilla description:
+ HSSFFormulaParser.parse("SUMPRODUCT(A!C7:A!C67, B8:B68) / B69", wb);
+
+ wb.close();
+ }
- @Test
- public void testRangeFuncOperand_bug46951() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- Ptg[] ptgs;
- try {
- ptgs = HSSFFormulaParser.parse("SUM(C1:OFFSET(C1,0,B1))", wb);
- } catch (RuntimeException e) {
- if (e.getMessage().equals("Specified named range 'OFFSET' does not exist in the current workbook.")) {
- fail("Identified bug 46951");
- }
- wb.close();
- throw e;
- }
- confirmTokenClasses(ptgs,
- MemFuncPtg.class, // [len=23]
- RefPtg.class, // [C1]
- RefPtg.class, // [C1]
- IntPtg.class, // [0]
- RefPtg.class, // [B1]
- FuncVarPtg.class, // [OFFSET nArgs=3]
- RangePtg.class, //
- AttrPtg.class // [sum ]
- );
- wb.close();
- }
+ @Test
+ public void testRangeFuncOperand_bug46951() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ Ptg[] ptgs;
+ try {
+ ptgs = HSSFFormulaParser.parse("SUM(C1:OFFSET(C1,0,B1))", wb);
+ } catch (RuntimeException e) {
+ if (e.getMessage().equals("Specified named range 'OFFSET' does not exist in the current workbook.")) {
+ fail("Identified bug 46951");
+ }
+ wb.close();
+ throw e;
+ }
+ confirmTokenClasses(ptgs,
+ MemFuncPtg.class, // [len=23]
+ RefPtg.class, // [C1]
+ RefPtg.class, // [C1]
+ IntPtg.class, // [0]
+ RefPtg.class, // [B1]
+ FuncVarPtg.class, // [OFFSET nArgs=3]
+ RangePtg.class, //
+ AttrPtg.class // [sum ]
+ );
+ wb.close();
+ }
- @Test
- public void testUnionOfFullCollFullRowRef() throws IOException {
- Ptg[] ptgs;
- ptgs = parseFormula("3:4");
- ptgs = parseFormula("$Z:$AC");
- confirmTokenClasses(ptgs, AreaPtg.class);
- ptgs = parseFormula("B:B");
+ @Test
+ public void testUnionOfFullCollFullRowRef() throws IOException {
+ Ptg[] ptgs;
+ ptgs = parseFormula("3:4");
+ ptgs = parseFormula("$Z:$AC");
+ confirmTokenClasses(ptgs, AreaPtg.class);
+ ptgs = parseFormula("B:B");
- ptgs = parseFormula("$11:$13");
- confirmTokenClasses(ptgs, AreaPtg.class);
+ ptgs = parseFormula("$11:$13");
+ confirmTokenClasses(ptgs, AreaPtg.class);
- ptgs = parseFormula("$A:$A,$1:$4");
- confirmTokenClasses(ptgs, MemAreaPtg.class,
- AreaPtg.class,
- AreaPtg.class,
- UnionPtg.class
- );
+ ptgs = parseFormula("$A:$A,$1:$4");
+ confirmTokenClasses(ptgs, MemAreaPtg.class,
+ AreaPtg.class,
+ AreaPtg.class,
+ UnionPtg.class
+ );
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
- ptgs = HSSFFormulaParser.parse("Sheet1!$A:$A,Sheet1!$1:$4", wb);
- confirmTokenClasses(ptgs, MemFuncPtg.class,
- Area3DPtg.class,
- Area3DPtg.class,
- UnionPtg.class
- );
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
+ ptgs = HSSFFormulaParser.parse("Sheet1!$A:$A,Sheet1!$1:$4", wb);
+ confirmTokenClasses(ptgs, MemFuncPtg.class,
+ Area3DPtg.class,
+ Area3DPtg.class,
+ UnionPtg.class
+ );
- ptgs = HSSFFormulaParser.parse("'Sheet1'!$A:$A,'Sheet1'!$1:$4", wb);
- confirmTokenClasses(ptgs,
- MemFuncPtg.class,
- Area3DPtg.class,
- Area3DPtg.class,
- UnionPtg.class
- );
-
- wb.close();
- }
+ ptgs = HSSFFormulaParser.parse("'Sheet1'!$A:$A,'Sheet1'!$1:$4", wb);
+ confirmTokenClasses(ptgs,
+ MemFuncPtg.class,
+ Area3DPtg.class,
+ Area3DPtg.class,
+ UnionPtg.class
+ );
+
+ wb.close();
+ }
- @Test
- public void testExplicitRangeWithTwoSheetNames() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
- Ptg[] ptgs = HSSFFormulaParser.parse("Sheet1!F1:Sheet1!G2", wb);
- confirmTokenClasses(ptgs,
- MemFuncPtg.class,
- Ref3DPtg.class,
- Ref3DPtg.class,
- RangePtg.class
- );
- MemFuncPtg mf;
- mf = (MemFuncPtg)ptgs[0];
- assertEquals(15, mf.getLenRefSubexpression());
- wb.close();
- }
+ @Test
+ public void testExplicitRangeWithTwoSheetNames() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
+ Ptg[] ptgs = HSSFFormulaParser.parse("Sheet1!F1:Sheet1!G2", wb);
+ confirmTokenClasses(ptgs,
+ MemFuncPtg.class,
+ Ref3DPtg.class,
+ Ref3DPtg.class,
+ RangePtg.class
+ );
+ MemFuncPtg mf;
+ mf = (MemFuncPtg)ptgs[0];
+ assertEquals(15, mf.getLenRefSubexpression());
+ wb.close();
+ }
- /**
- * Checks that the area-ref and explicit range operators get the right associativity
- * and that the {@link MemFuncPtg} / {@link MemAreaPtg} is added correctly
- */
- @Test
- public void testComplexExplicitRangeEncodings() {
+ /**
+ * Checks that the area-ref and explicit range operators get the right associativity
+ * and that the {@link MemFuncPtg} / {@link MemAreaPtg} is added correctly
+ */
+ @Test
+ public void testComplexExplicitRangeEncodings() {
- Ptg[] ptgs;
- ptgs = parseFormula("SUM(OFFSET(A1,0,0):B2:C3:D4:E5:OFFSET(F6,1,1):G7)");
- confirmTokenClasses(ptgs,
- // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009)
- MemFuncPtg.class, // len 57
- RefPtg.class, // [A1]
- IntPtg.class, // [0]
- IntPtg.class, // [0]
- FuncVarPtg.class, // [OFFSET nArgs=3]
- AreaPtg.class, // [B2:C3]
- RangePtg.class,
- AreaPtg.class, // [D4:E5]
- RangePtg.class,
- RefPtg.class, // [F6]
- IntPtg.class, // [1]
- IntPtg.class, // [1]
- FuncVarPtg.class, // [OFFSET nArgs=3]
- RangePtg.class,
- RefPtg.class, // [G7]
- RangePtg.class,
- AttrPtg.class // [sum ]
- );
+ Ptg[] ptgs;
+ ptgs = parseFormula("SUM(OFFSET(A1,0,0):B2:C3:D4:E5:OFFSET(F6,1,1):G7)");
+ confirmTokenClasses(ptgs,
+ // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009)
+ MemFuncPtg.class, // len 57
+ RefPtg.class, // [A1]
+ IntPtg.class, // [0]
+ IntPtg.class, // [0]
+ FuncVarPtg.class, // [OFFSET nArgs=3]
+ AreaPtg.class, // [B2:C3]
+ RangePtg.class,
+ AreaPtg.class, // [D4:E5]
+ RangePtg.class,
+ RefPtg.class, // [F6]
+ IntPtg.class, // [1]
+ IntPtg.class, // [1]
+ FuncVarPtg.class, // [OFFSET nArgs=3]
+ RangePtg.class,
+ RefPtg.class, // [G7]
+ RangePtg.class,
+ AttrPtg.class // [sum ]
+ );
- MemFuncPtg mf = (MemFuncPtg)ptgs[0];
- assertEquals(57, mf.getLenRefSubexpression());
- assertEquals("D4:E5", ((AreaPtgBase)ptgs[7]).toFormulaString());
- assertTrue(((AttrPtg)ptgs[16]).isSum());
+ MemFuncPtg mf = (MemFuncPtg)ptgs[0];
+ assertEquals(57, mf.getLenRefSubexpression());
+ assertEquals("D4:E5", ((AreaPtgBase)ptgs[7]).toFormulaString());
+ assertTrue(((AttrPtg)ptgs[16]).isSum());
- ptgs = parseFormula("SUM(A1:B2:C3:D4)");
- confirmTokenClasses(ptgs,
- // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009)
- MemAreaPtg.class, // len 19
- AreaPtg.class, // [A1:B2]
- AreaPtg.class, // [C3:D4]
- RangePtg.class,
- AttrPtg.class // [sum ]
- );
- MemAreaPtg ma = (MemAreaPtg)ptgs[0];
- assertEquals(19, ma.getLenRefSubexpression());
- }
+ ptgs = parseFormula("SUM(A1:B2:C3:D4)");
+ confirmTokenClasses(ptgs,
+ // AttrPtg.class, // [volatile ] // POI doesn't do this yet (Apr 2009)
+ MemAreaPtg.class, // len 19
+ AreaPtg.class, // [A1:B2]
+ AreaPtg.class, // [C3:D4]
+ RangePtg.class,
+ AttrPtg.class // [sum ]
+ );
+ MemAreaPtg ma = (MemAreaPtg)ptgs[0];
+ assertEquals(19, ma.getLenRefSubexpression());
+ }
- /**
- * Mostly confirming that erroneous conditions are detected. Actual error message wording is not critical.
- *
- */
- @Test
- public void testEdgeCaseParserErrors() throws IOException {
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
+ /**
+ * Mostly confirming that erroneous conditions are detected. Actual error message wording is not critical.
+ *
+ */
+ @Test
+ public void testEdgeCaseParserErrors() throws IOException {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
- confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference.");
+ confirmParseError(wb, "A1:ROUND(B1,1)", "The RHS of the range operator ':' at position 3 is not a proper reference.");
- confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, or defined name");
- confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, or defined name");
- confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found");
- confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference.");
- confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11.");
- confirmParseError(wb, "Sheet1!A .. B", "Dotted range (full row or column) expression 'A .. B' must not contain whitespace.");
- confirmParseError(wb, "Sheet1!A...B", "Dotted range (full row or column) expression 'A...B' must have exactly 2 dots.");
- confirmParseError(wb, "Sheet1!A foobar", "Second part of cell reference expected after sheet name at index 10.");
+ confirmParseError(wb, "Sheet1!!!", "Parse error near char 7 '!' in specified formula 'Sheet1!!!'. Expected number, string, defined name, or data table");
+ confirmParseError(wb, "Sheet1!.Name", "Parse error near char 7 '.' in specified formula 'Sheet1!.Name'. Expected number, string, defined name, or data table");
+ confirmParseError(wb, "Sheet1!Sheet1", "Specified name 'Sheet1' for sheet Sheet1 not found");
+ confirmParseError(wb, "Sheet1!F:Sheet1!G", "'Sheet1!F' is not a proper reference.");
+ confirmParseError(wb, "Sheet1!F..foobar", "Complete area reference expected after sheet name at index 11.");
+ confirmParseError(wb, "Sheet1!A .. B", "Dotted range (full row or column) expression 'A .. B' must not contain whitespace.");
+ confirmParseError(wb, "Sheet1!A...B", "Dotted range (full row or column) expression 'A...B' must have exactly 2 dots.");
+ confirmParseError(wb, "Sheet1!A foobar", "Second part of cell reference expected after sheet name at index 10.");
- confirmParseError(wb, "foobar", "Specified named range 'foobar' does not exist in the current workbook.");
- confirmParseError(wb, "A1:1", "The RHS of the range operator ':' at position 3 is not a proper reference.");
- wb.close();
- }
+ confirmParseError(wb, "foobar", "Specified named range 'foobar' does not exist in the current workbook.");
+ confirmParseError(wb, "A1:1", "The RHS of the range operator ':' at position 3 is not a proper reference.");
+ wb.close();
+ }
- private static void confirmParseError(HSSFWorkbook wb, String formula, String expectedMessage) {
+ private static void confirmParseError(HSSFWorkbook wb, String formula, String expectedMessage) {
- try {
- HSSFFormulaParser.parse(formula, wb);
- fail("Expected formula parse execption");
- } catch (FormulaParseException e) {
- confirmParseException(e, expectedMessage);
- }
- }
+ try {
+ HSSFFormulaParser.parse(formula, wb);
+ fail("Expected formula parse execption");
+ } catch (FormulaParseException e) {
+ confirmParseException(e, expectedMessage);
+ }
+ }
- /**
- * In bug 47078, POI had trouble evaluating a defined name flagged as 'complex'.
- * POI should also be able to parse such defined names.
- */
- @Test
- public void testParseComplexName() throws IOException {
+ /**
+ * In bug 47078, POI had trouble evaluating a defined name flagged as 'complex'.
+ * POI should also be able to parse such defined names.
+ */
+ @Test
+ public void testParseComplexName() throws IOException {
- // Mock up a spreadsheet to match the critical details of the sample
- HSSFWorkbook wb = new HSSFWorkbook();
- wb.createSheet("Sheet1");
- HSSFName definedName = wb.createName();
- definedName.setNameName("foo");
- definedName.setRefersToFormula("Sheet1!B2");
+ // Mock up a spreadsheet to match the critical details of the sample
+ HSSFWorkbook wb = new HSSFWorkbook();
+ wb.createSheet("Sheet1");
+ HSSFName definedName = wb.createName();
+ definedName.setNameName("foo");
+ definedName.setRefersToFormula("Sheet1!B2");
- // Set the complex flag - POI doesn't usually manipulate this flag
- NameRecord nameRec = TestHSSFName.getNameRecord(definedName);
- nameRec.setOptionFlag((short)0x10); // 0x10 -> complex
+ // Set the complex flag - POI doesn't usually manipulate this flag
+ NameRecord nameRec = TestHSSFName.getNameRecord(definedName);
+ nameRec.setOptionFlag((short)0x10); // 0x10 -> complex
- Ptg[] result;
- try {
- result = HSSFFormulaParser.parse("1+foo", wb);
- } catch (FormulaParseException e) {
- if (e.getMessage().equals("Specified name 'foo' is not a range as expected.")) {
- fail("Identified bug 47078c");
- }
- wb.close();
- throw e;
- }
- confirmTokenClasses(result, IntPtg.class, NamePtg.class, AddPtg.class);
-
- wb.close();
- }
+ Ptg[] result;
+ try {
+ result = HSSFFormulaParser.parse("1+foo", wb);
+ } catch (FormulaParseException e) {
+ if (e.getMessage().equals("Specified name 'foo' is not a range as expected.")) {
+ fail("Identified bug 47078c");
+ }
+ wb.close();
+ throw e;
+ }
+ confirmTokenClasses(result, IntPtg.class, NamePtg.class, AddPtg.class);
+
+ wb.close();
+ }
- /**
- * Zero is not a valid row number so cell references like 'A0' are not valid.
- * Actually, they should be treated like defined names.
- *
- * In addition, leading zeros (on the row component) should be removed from cell
- * references during parsing.
- */
- @Test
- public void testZeroRowRefs() throws IOException {
- String badCellRef = "B0"; // bad because zero is not a valid row number
- String leadingZeroCellRef = "B000001"; // this should get parsed as "B1"
- HSSFWorkbook wb = new HSSFWorkbook();
+ /**
+ * Zero is not a valid row number so cell references like 'A0' are not valid.
+ * Actually, they should be treated like defined names.
+ *
+ * In addition, leading zeros (on the row component) should be removed from cell
+ * references during parsing.
+ */
+ @Test
+ public void testZeroRowRefs() throws IOException {
+ String badCellRef = "B0"; // bad because zero is not a valid row number
+ String leadingZeroCellRef = "B000001"; // this should get parsed as "B1"
+ HSSFWorkbook wb = new HSSFWorkbook();
- try {
- HSSFFormulaParser.parse(badCellRef, wb);
- fail("Identified bug 47312b - Shouldn't be able to parse cell ref '"
- + badCellRef + "'.");
- } catch (FormulaParseException e) {
- // expected during successful test
- confirmParseException(e, "Specified named range '"
- + badCellRef + "' does not exist in the current workbook.");
- }
+ try {
+ HSSFFormulaParser.parse(badCellRef, wb);
+ fail("Identified bug 47312b - Shouldn't be able to parse cell ref '"
+ + badCellRef + "'.");
+ } catch (FormulaParseException e) {
+ // expected during successful test
+ confirmParseException(e, "Specified named range '"
+ + badCellRef + "' does not exist in the current workbook.");
+ }
- Ptg[] ptgs;
- try {
- ptgs = HSSFFormulaParser.parse(leadingZeroCellRef, wb);
- assertEquals("B1", ((RefPtg) ptgs[0]).toFormulaString());
- } catch (FormulaParseException e) {
- confirmParseException(e, "Specified named range '"
- + leadingZeroCellRef + "' does not exist in the current workbook.");
- // close but no cigar
- fail("Identified bug 47312c - '" + leadingZeroCellRef + "' should parse as 'B1'.");
- }
+ Ptg[] ptgs;
+ try {
+ ptgs = HSSFFormulaParser.parse(leadingZeroCellRef, wb);
+ assertEquals("B1", ((RefPtg) ptgs[0]).toFormulaString());
+ } catch (FormulaParseException e) {
+ confirmParseException(e, "Specified named range '"
+ + leadingZeroCellRef + "' does not exist in the current workbook.");
+ // close but no cigar
+ fail("Identified bug 47312c - '" + leadingZeroCellRef + "' should parse as 'B1'.");
+ }
- // create a defined name called 'B0' and try again
- Name n = wb.createName();
- n.setNameName("B0");
- n.setRefersToFormula("1+1");
- ptgs = HSSFFormulaParser.parse("B0", wb);
- confirmTokenClasses(ptgs, NamePtg.class);
-
- wb.close();
- }
+ // create a defined name called 'B0' and try again
+ Name n = wb.createName();
+ n.setNameName("B0");
+ n.setRefersToFormula("1+1");
+ ptgs = HSSFFormulaParser.parse("B0", wb);
+ confirmTokenClasses(ptgs, NamePtg.class);
+
+ wb.close();
+ }
- private static void confirmParseException(FormulaParseException e, String expMsg) {
- assertEquals(expMsg, e.getMessage());
- }
+ private static void confirmParseException(FormulaParseException e, String expMsg) {
+ assertEquals(expMsg, e.getMessage());
+ }
@Test
public void test57196_Formula() throws IOException {
diff --git a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java
index 3a5a43493..14de4bcda 100644
--- a/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java
+++ b/src/testcases/org/apache/poi/hssf/record/TestSharedFormulaRecord.java
@@ -38,67 +38,67 @@ import org.apache.poi.util.LittleEndianInput;
*/
public final class TestSharedFormulaRecord extends TestCase {
- /**
- * A sample spreadsheet known to have one sheet with 4 shared formula ranges
- */
- private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls";
- /**
- * Binary data for an encoded formula. Taken from attachment 22062 (bugzilla 45123/45421).
- * The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))"
- * This data is found at offset 0x1A4A (within the shared formula record).
- * The critical thing about this formula is that it contains shared formula tokens (tRefN*,
- * tAreaN*) with operand class 'array'.
- */
- private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = {
- 0x1A, 0x00,
- 0x63, 0x02, 0x00, 0x00, 0x00,
- 0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA
- 0x0B,
- 0x15,
- 0x13,
- 0x13,
- 0x63, 0x03, 0x00, 0x00, 0x00,
- 0x15,
- 0x13,
- 0x13,
- 0x42, 0x02, (byte)0xE4, 0x00,
- };
+ /**
+ * A sample spreadsheet known to have one sheet with 4 shared formula ranges
+ */
+ private static final String SHARED_FORMULA_TEST_XLS = "SharedFormulaTest.xls";
+ /**
+ * Binary data for an encoded formula. Taken from attachment 22062 (bugzilla 45123/45421).
+ * The shared formula is in Sheet1!C6:C21, with text "SUMPRODUCT(--(End_Acct=$C6),--(End_Bal))"
+ * This data is found at offset 0x1A4A (within the shared formula record).
+ * The critical thing about this formula is that it contains shared formula tokens (tRefN*,
+ * tAreaN*) with operand class 'array'.
+ */
+ private static final byte[] SHARED_FORMULA_WITH_REF_ARRAYS_DATA = {
+ 0x1A, 0x00,
+ 0x63, 0x02, 0x00, 0x00, 0x00,
+ 0x6C, 0x00, 0x00, 0x02, (byte)0x80, // tRefNA
+ 0x0B,
+ 0x15,
+ 0x13,
+ 0x13,
+ 0x63, 0x03, 0x00, 0x00, 0x00,
+ 0x15,
+ 0x13,
+ 0x13,
+ 0x42, 0x02, (byte)0xE4, 0x00,
+ };
- /**
- * The method SharedFormulaRecord.convertSharedFormulas() converts formulas from
- * 'shared formula' to 'single cell formula' format. It is important that token operand
- * classes are preserved during this transformation, because Excel may not tolerate the
- * incorrect encoding. The formula here is one such example (Excel displays #VALUE!).
- */
- public void testConvertSharedFormulasOperandClasses_bug45123() {
+ /**
+ * The method SharedFormulaRecord.convertSharedFormulas() converts formulas from
+ * 'shared formula' to 'single cell formula' format. It is important that token operand
+ * classes are preserved during this transformation, because Excel may not tolerate the
+ * incorrect encoding. The formula here is one such example (Excel displays #VALUE!).
+ */
+ public void testConvertSharedFormulasOperandClasses_bug45123() {
- LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA);
- int encodedLen = in.readUShort();
- Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in);
+ LittleEndianInput in = TestcaseRecordInputStream.createLittleEndian(SHARED_FORMULA_WITH_REF_ARRAYS_DATA);
+ int encodedLen = in.readUShort();
+ Ptg[] sharedFormula = Ptg.readTokens(encodedLen, in);
SharedFormula sf = new SharedFormula(SpreadsheetVersion.EXCEL97);
- Ptg[] convertedFormula = sf.convertSharedFormulas(sharedFormula, 100, 200);
+ Ptg[] convertedFormula = sf.convertSharedFormulas(sharedFormula, 100, 200);
- RefPtg refPtg = (RefPtg) convertedFormula[1];
- assertEquals("$C101", refPtg.toFormulaString());
- if (refPtg.getPtgClass() == Ptg.CLASS_REF) {
- throw new AssertionFailedError("Identified bug 45123");
- }
+ RefPtg refPtg = (RefPtg) convertedFormula[1];
+ assertEquals("$C101", refPtg.toFormulaString());
+ if (refPtg.getPtgClass() == Ptg.CLASS_REF) {
+ throw new AssertionFailedError("Identified bug 45123");
+ }
- confirmOperandClasses(sharedFormula, convertedFormula);
- }
+ confirmOperandClasses(sharedFormula, convertedFormula);
+ }
- private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) {
- assertEquals(originalPtgs.length, convertedPtgs.length);
- for (int i = 0; i < convertedPtgs.length; i++) {
- Ptg originalPtg = originalPtgs[i];
- Ptg convertedPtg = convertedPtgs[i];
- if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) {
- throw new ComparisonFailure("Different operand class for token[" + i + "]",
- String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass()));
- }
- }
- }
+ private static void confirmOperandClasses(Ptg[] originalPtgs, Ptg[] convertedPtgs) {
+ assertEquals(originalPtgs.length, convertedPtgs.length);
+ for (int i = 0; i < convertedPtgs.length; i++) {
+ Ptg originalPtg = originalPtgs[i];
+ Ptg convertedPtg = convertedPtgs[i];
+ if (originalPtg.getPtgClass() != convertedPtg.getPtgClass()) {
+ throw new ComparisonFailure("Different operand class for token[" + i + "]",
+ String.valueOf(originalPtg.getPtgClass()), String.valueOf(convertedPtg.getPtgClass()));
+ }
+ }
+ }
public void testConvertSharedFormulas() {
HSSFWorkbook wb = new HSSFWorkbook();
@@ -138,111 +138,111 @@ public final class TestSharedFormulaRecord extends TestCase {
}
/**
- * Make sure that POI preserves {@link SharedFormulaRecord}s
- */
- public void testPreserveOnReserialize() {
- HSSFWorkbook wb;
- HSSFSheet sheet;
- HSSFCell cellB32769;
- HSSFCell cellC32769;
+ * Make sure that POI preserves {@link SharedFormulaRecord}s
+ */
+ public void testPreserveOnReserialize() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cellB32769;
+ HSSFCell cellC32769;
- // Reading directly from XLS file
- wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
- sheet = wb.getSheetAt(0);
- cellB32769 = sheet.getRow(32768).getCell(1);
- cellC32769 = sheet.getRow(32768).getCell(2);
- // check reading of formulas which are shared (two cells from a 1R x 8C range)
- assertEquals("B32770*2", cellB32769.getCellFormula());
- assertEquals("C32770*2", cellC32769.getCellFormula());
- confirmCellEvaluation(wb, cellB32769, 4);
- confirmCellEvaluation(wb, cellC32769, 6);
- // Confirm this example really does have SharedFormulas.
- // there are 3 others besides the one at A32769:H32769
- assertEquals(4, countSharedFormulas(sheet));
+ // Reading directly from XLS file
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
+ // check reading of formulas which are shared (two cells from a 1R x 8C range)
+ assertEquals("B32770*2", cellB32769.getCellFormula());
+ assertEquals("C32770*2", cellC32769.getCellFormula());
+ confirmCellEvaluation(wb, cellB32769, 4);
+ confirmCellEvaluation(wb, cellC32769, 6);
+ // Confirm this example really does have SharedFormulas.
+ // there are 3 others besides the one at A32769:H32769
+ assertEquals(4, countSharedFormulas(sheet));
- // Re-serialize and check again
- wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
- sheet = wb.getSheetAt(0);
- cellB32769 = sheet.getRow(32768).getCell(1);
- cellC32769 = sheet.getRow(32768).getCell(2);
- assertEquals("B32770*2", cellB32769.getCellFormula());
- confirmCellEvaluation(wb, cellB32769, 4);
- assertEquals(4, countSharedFormulas(sheet));
- }
+ // Re-serialize and check again
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
+ assertEquals("B32770*2", cellB32769.getCellFormula());
+ confirmCellEvaluation(wb, cellB32769, 4);
+ assertEquals(4, countSharedFormulas(sheet));
+ }
- public void testUnshareFormulaDueToChangeFormula() {
- HSSFWorkbook wb;
- HSSFSheet sheet;
- HSSFCell cellB32769;
- HSSFCell cellC32769;
+ public void testUnshareFormulaDueToChangeFormula() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cellB32769;
+ HSSFCell cellC32769;
- wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
- sheet = wb.getSheetAt(0);
- cellB32769 = sheet.getRow(32768).getCell(1);
- cellC32769 = sheet.getRow(32768).getCell(2);
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
+ cellB32769 = sheet.getRow(32768).getCell(1);
+ cellC32769 = sheet.getRow(32768).getCell(2);
- // Updating cell formula, causing it to become unshared
- cellB32769.setCellFormula("1+1");
- confirmCellEvaluation(wb, cellB32769, 2);
- // currently (Oct 2008) POI handles this by exploding the whole shared formula group
- assertEquals(3, countSharedFormulas(sheet)); // one less now
- // check that nearby cell of the same group still has the same formula
- assertEquals("C32770*2", cellC32769.getCellFormula());
- confirmCellEvaluation(wb, cellC32769, 6);
- }
- public void testUnshareFormulaDueToDelete() {
- HSSFWorkbook wb;
- HSSFSheet sheet;
- HSSFCell cell;
- final int ROW_IX = 2;
+ // Updating cell formula, causing it to become unshared
+ cellB32769.setCellFormula("1+1");
+ confirmCellEvaluation(wb, cellB32769, 2);
+ // currently (Oct 2008) POI handles this by exploding the whole shared formula group
+ assertEquals(3, countSharedFormulas(sheet)); // one less now
+ // check that nearby cell of the same group still has the same formula
+ assertEquals("C32770*2", cellC32769.getCellFormula());
+ confirmCellEvaluation(wb, cellC32769, 6);
+ }
+ public void testUnshareFormulaDueToDelete() {
+ HSSFWorkbook wb;
+ HSSFSheet sheet;
+ HSSFCell cell;
+ final int ROW_IX = 2;
- // changing shared formula cell to blank
- wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
- sheet = wb.getSheetAt(0);
+ // changing shared formula cell to blank
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
- assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
- cell = sheet.getRow(ROW_IX).getCell(1);
- cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
- assertEquals(3, countSharedFormulas(sheet));
+ assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+ cell = sheet.getRow(ROW_IX).getCell(1);
+ cell.setCellType(HSSFCell.CELL_TYPE_BLANK);
+ assertEquals(3, countSharedFormulas(sheet));
- wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
- sheet = wb.getSheetAt(0);
- assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
- // deleting shared formula cell
- wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
- sheet = wb.getSheetAt(0);
+ // deleting shared formula cell
+ wb = HSSFTestDataSamples.openSampleWorkbook(SHARED_FORMULA_TEST_XLS);
+ sheet = wb.getSheetAt(0);
- assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
- cell = sheet.getRow(ROW_IX).getCell(1);
- sheet.getRow(ROW_IX).removeCell(cell);
- assertEquals(3, countSharedFormulas(sheet));
+ assertEquals("A$1*2", sheet.getRow(ROW_IX).getCell(1).getCellFormula());
+ cell = sheet.getRow(ROW_IX).getCell(1);
+ sheet.getRow(ROW_IX).removeCell(cell);
+ assertEquals(3, countSharedFormulas(sheet));
- wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
- sheet = wb.getSheetAt(0);
- assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
- }
+ wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
+ sheet = wb.getSheetAt(0);
+ assertEquals("A$1*2", sheet.getRow(ROW_IX+1).getCell(1).getCellFormula());
+ }
- private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) {
- HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
- CellValue cv = fe.evaluate(cell);
- assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
- assertEquals(expectedValue, cv.getNumberValue(), 0.0);
- }
+ private static void confirmCellEvaluation(HSSFWorkbook wb, HSSFCell cell, double expectedValue) {
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ CellValue cv = fe.evaluate(cell);
+ assertEquals(HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(expectedValue, cv.getNumberValue(), 0.0);
+ }
- /**
- * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet
- */
- private static int countSharedFormulas(HSSFSheet sheet) {
- Record[] records = RecordInspector.getRecords(sheet, 0);
- int count = 0;
- for (int i = 0; i < records.length; i++) {
- Record rec = records[i];
- if(rec instanceof SharedFormulaRecord) {
- count++;
- }
- }
- return count;
- }
+ /**
+ * @return the number of {@link SharedFormulaRecord}s encoded for the specified sheet
+ */
+ private static int countSharedFormulas(HSSFSheet sheet) {
+ Record[] records = RecordInspector.getRecords(sheet, 0);
+ int count = 0;
+ for (int i = 0; i < records.length; i++) {
+ Record rec = records[i];
+ if(rec instanceof SharedFormulaRecord) {
+ count++;
+ }
+ }
+ return count;
+ }
}
diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java
index 10f7a70a1..c2118a147 100644
--- a/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java
+++ b/src/testcases/org/apache/poi/ss/formula/functions/TestIndirect.java
@@ -36,32 +36,32 @@ import org.junit.Test;
* Tests for the INDIRECT() function.
*/
public final class TestIndirect {
- // convenient access to namespace
- // private static final ErrorEval EE = null;
+ // convenient access to namespace
+ // private static final ErrorEval EE = null;
- private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) {
- HSSFRow row = sheet.createRow(rowIndex);
- for (int i = 0; i < vals.length; i++) {
- row.createCell(i).setCellValue(vals[i]);
- }
- }
+ private static void createDataRow(HSSFSheet sheet, int rowIndex, double... vals) {
+ HSSFRow row = sheet.createRow(rowIndex);
+ for (int i = 0; i < vals.length; i++) {
+ row.createCell(i).setCellValue(vals[i]);
+ }
+ }
- private static HSSFWorkbook createWBA() {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet1 = wb.createSheet("Sheet1");
- HSSFSheet sheet2 = wb.createSheet("Sheet2");
- HSSFSheet sheet3 = wb.createSheet("John's sales");
+ private static HSSFWorkbook createWBA() {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("Sheet1");
+ HSSFSheet sheet2 = wb.createSheet("Sheet2");
+ HSSFSheet sheet3 = wb.createSheet("John's sales");
- createDataRow(sheet1, 0, 11, 12, 13, 14);
- createDataRow(sheet1, 1, 21, 22, 23, 24);
- createDataRow(sheet1, 2, 31, 32, 33, 34);
+ createDataRow(sheet1, 0, 11, 12, 13, 14);
+ createDataRow(sheet1, 1, 21, 22, 23, 24);
+ createDataRow(sheet1, 2, 31, 32, 33, 34);
- createDataRow(sheet2, 0, 50, 55, 60, 65);
- createDataRow(sheet2, 1, 51, 56, 61, 66);
- createDataRow(sheet2, 2, 52, 57, 62, 67);
+ createDataRow(sheet2, 0, 50, 55, 60, 65);
+ createDataRow(sheet2, 1, 51, 56, 61, 66);
+ createDataRow(sheet2, 2, 52, 57, 62, 67);
- createDataRow(sheet3, 0, 30, 31, 32);
- createDataRow(sheet3, 1, 33, 34, 35);
+ createDataRow(sheet3, 0, 30, 31, 32);
+ createDataRow(sheet3, 1, 33, 34, 35);
HSSFName name1 = wb.createName();
name1.setNameName("sales1");
@@ -75,131 +75,131 @@ public final class TestIndirect {
row.createCell(0).setCellValue("sales1"); //A4
row.createCell(1).setCellValue("sales2"); //B4
- return wb;
- }
+ return wb;
+ }
- private static HSSFWorkbook createWBB() {
- HSSFWorkbook wb = new HSSFWorkbook();
- HSSFSheet sheet1 = wb.createSheet("Sheet1");
- HSSFSheet sheet2 = wb.createSheet("Sheet2");
- HSSFSheet sheet3 = wb.createSheet("## Look here!");
+ private static HSSFWorkbook createWBB() {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet1 = wb.createSheet("Sheet1");
+ HSSFSheet sheet2 = wb.createSheet("Sheet2");
+ HSSFSheet sheet3 = wb.createSheet("## Look here!");
- createDataRow(sheet1, 0, 400, 440, 480, 520);
- createDataRow(sheet1, 1, 420, 460, 500, 540);
+ createDataRow(sheet1, 0, 400, 440, 480, 520);
+ createDataRow(sheet1, 1, 420, 460, 500, 540);
- createDataRow(sheet2, 0, 50, 55, 60, 65);
- createDataRow(sheet2, 1, 51, 56, 61, 66);
+ createDataRow(sheet2, 0, 50, 55, 60, 65);
+ createDataRow(sheet2, 1, 51, 56, 61, 66);
- createDataRow(sheet3, 0, 42);
+ createDataRow(sheet3, 0, 42);
- return wb;
- }
+ return wb;
+ }
- @Test
- public void testBasic() throws Exception {
+ @Test
+ public void testBasic() throws Exception {
- HSSFWorkbook wbA = createWBA();
- HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
- HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
+ HSSFWorkbook wbA = createWBA();
+ HSSFCell c = wbA.getSheetAt(0).createRow(5).createCell(2);
+ HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
- // non-error cases
- confirm(feA, c, "INDIRECT(\"C2\")", 23);
- confirm(feA, c, "INDIRECT(\"$C2\")", 23);
- confirm(feA, c, "INDIRECT(\"C$2\")", 23);
- confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
- confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref
- confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name
- confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes
- confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name
- confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref
- confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref
- confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far
+ // non-error cases
+ confirm(feA, c, "INDIRECT(\"C2\")", 23);
+ confirm(feA, c, "INDIRECT(\"$C2\")", 23);
+ confirm(feA, c, "INDIRECT(\"C$2\")", 23);
+ confirm(feA, c, "SUM(INDIRECT(\"Sheet2!B1:C3\"))", 351); // area ref
+ confirm(feA, c, "SUM(INDIRECT(\"Sheet2! B1 : C3 \"))", 351); // spaces in area ref
+ confirm(feA, c, "SUM(INDIRECT(\"'John''s sales'!A1:C1\"))", 93); // special chars in sheet name
+ confirm(feA, c, "INDIRECT(\"'Sheet1'!B3\")", 32); // redundant sheet name quotes
+ confirm(feA, c, "INDIRECT(\"sHeet1!B3\")", 32); // case-insensitive sheet name
+ confirm(feA, c, "INDIRECT(\" D3 \")", 34); // spaces around cell ref
+ confirm(feA, c, "INDIRECT(\"Sheet1! D3 \")", 34); // spaces around cell ref
+ confirm(feA, c, "INDIRECT(\"A1\", TRUE)", 11); // explicit arg1. only TRUE supported so far
- confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4)
+ confirm(feA, c, "INDIRECT(\"A1:G1\")", 13); // de-reference area ref (note formula is in C4)
confirm(feA, c, "SUM(INDIRECT(A4))", 50); // indirect defined name
confirm(feA, c, "SUM(INDIRECT(B4))", 351); // indirect defined name pointinh to other sheet
- // simple error propagation:
+ // simple error propagation:
- // arg0 is evaluated to text first
- confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
- confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
- confirm(feA, c, "INDIRECT(#NAME?, \"x\")", ErrorEval.NAME_INVALID);
- confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR);
+ // arg0 is evaluated to text first
+ confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
+ confirm(feA, c, "INDIRECT(#DIV/0!)", ErrorEval.DIV_ZERO);
+ confirm(feA, c, "INDIRECT(#NAME?, \"x\")", ErrorEval.NAME_INVALID);
+ confirm(feA, c, "INDIRECT(#NUM!, #N/A)", ErrorEval.NUM_ERROR);
- // arg1 is evaluated to boolean before arg0 is decoded
- confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", ErrorEval.NA);
- confirm(feA, c, "INDIRECT(\"garbage\", \"\")", ErrorEval.VALUE_INVALID); // empty string is not valid boolean
- confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE"
+ // arg1 is evaluated to boolean before arg0 is decoded
+ confirm(feA, c, "INDIRECT(\"garbage\", #N/A)", ErrorEval.NA);
+ confirm(feA, c, "INDIRECT(\"garbage\", \"\")", ErrorEval.VALUE_INVALID); // empty string is not valid boolean
+ confirm(feA, c, "INDIRECT(\"garbage\", \"flase\")", ErrorEval.VALUE_INVALID); // must be "TRUE" or "FALSE"
- // spaces around sheet name (with or without quotes makes no difference)
- confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", ErrorEval.REF_INVALID);
- confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", ErrorEval.REF_INVALID);
- confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", ErrorEval.REF_INVALID);
+ // spaces around sheet name (with or without quotes makes no difference)
+ confirm(feA, c, "INDIRECT(\"'Sheet1 '!D3\")", ErrorEval.REF_INVALID);
+ confirm(feA, c, "INDIRECT(\" Sheet1!D3\")", ErrorEval.REF_INVALID);
+ confirm(feA, c, "INDIRECT(\"'Sheet1' !D3\")", ErrorEval.REF_INVALID);
- confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", ErrorEval.REF_INVALID); // bad quote escaping
- confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", ErrorEval.REF_INVALID); // unknown external workbook
- confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet
-// if (false) { // TODO - support evaluation of defined names
-// confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", ErrorEval.REF_INVALID); // bad column
-// confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row
-// }
- confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref
-
- wbA.close();
- }
+ confirm(feA, c, "SUM(INDIRECT(\"'John's sales'!A1:C1\"))", ErrorEval.REF_INVALID); // bad quote escaping
+ confirm(feA, c, "INDIRECT(\"[Book1]Sheet1!A1\")", ErrorEval.REF_INVALID); // unknown external workbook
+ confirm(feA, c, "INDIRECT(\"Sheet3!A1\")", ErrorEval.REF_INVALID); // unknown sheet
+// if (false) { // TODO - support evaluation of defined names
+// confirm(feA, c, "INDIRECT(\"Sheet1!IW1\")", ErrorEval.REF_INVALID); // bad column
+// confirm(feA, c, "INDIRECT(\"Sheet1!A65537\")", ErrorEval.REF_INVALID); // bad row
+// }
+ confirm(feA, c, "INDIRECT(\"Sheet1!A 1\")", ErrorEval.REF_INVALID); // space in cell ref
+
+ wbA.close();
+ }
- @Test
- public void testMultipleWorkbooks() throws Exception {
- HSSFWorkbook wbA = createWBA();
- HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0);
- HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
+ @Test
+ public void testMultipleWorkbooks() throws Exception {
+ HSSFWorkbook wbA = createWBA();
+ HSSFCell cellA = wbA.getSheetAt(0).createRow(10).createCell(0);
+ HSSFFormulaEvaluator feA = new HSSFFormulaEvaluator(wbA);
- HSSFWorkbook wbB = createWBB();
- HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0);
- HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB);
+ HSSFWorkbook wbB = createWBB();
+ HSSFCell cellB = wbB.getSheetAt(0).createRow(10).createCell(0);
+ HSSFFormulaEvaluator feB = new HSSFFormulaEvaluator(wbB);
- String[] workbookNames = { "MyBook", "Figures for January", };
- HSSFFormulaEvaluator[] evaluators = { feA, feB, };
- HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
+ String[] workbookNames = { "MyBook", "Figures for January", };
+ HSSFFormulaEvaluator[] evaluators = { feA, feB, };
+ HSSFFormulaEvaluator.setupEnvironment(workbookNames, evaluators);
- confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb
- confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks
+ confirm(feB, cellB, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // same wb
+ confirm(feA, cellA, "INDIRECT(\"'[Figures for January]## Look here!'!A1\")", 42); // across workbooks
- // 2 level recursion
- confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level
- confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB
-
- wbB.close();
- wbA.close();
- }
+ // 2 level recursion
+ confirm(feB, cellB, "INDIRECT(\"[MyBook]Sheet2!A1\")", 50); // set up (and check) first level
+ confirm(feA, cellA, "INDIRECT(\"'[Figures for January]Sheet1'!A11\")", 50); // points to cellB
+
+ wbB.close();
+ wbA.close();
+ }
- private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
- double expectedResult) {
- fe.clearAllCachedResultValues();
- cell.setCellFormula(formula);
- CellValue cv = fe.evaluate(cell);
- if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
- fail("expected numeric cell type but got " + cv.formatAsString());
- }
- assertEquals(expectedResult, cv.getNumberValue(), 0.0);
- }
-
- private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
- ErrorEval expectedResult) {
- fe.clearAllCachedResultValues();
- cell.setCellFormula(formula);
- CellValue cv = fe.evaluate(cell);
- if (cv.getCellType() != Cell.CELL_TYPE_ERROR) {
- fail("expected error cell type but got " + cv.formatAsString());
- }
- int expCode = expectedResult.getErrorCode();
- if (cv.getErrorValue() != expCode) {
- fail("Expected error '" + ErrorEval.getText(expCode)
- + "' but got '" + cv.formatAsString() + "'.");
- }
- }
+ private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
+ double expectedResult) {
+ fe.clearAllCachedResultValues();
+ cell.setCellFormula(formula);
+ CellValue cv = fe.evaluate(cell);
+ if (cv.getCellType() != Cell.CELL_TYPE_NUMERIC) {
+ fail("expected numeric cell type but got " + cv.formatAsString());
+ }
+ assertEquals(expectedResult, cv.getNumberValue(), 0.0);
+ }
+
+ private static void confirm(FormulaEvaluator fe, Cell cell, String formula,
+ ErrorEval expectedResult) {
+ fe.clearAllCachedResultValues();
+ cell.setCellFormula(formula);
+ CellValue cv = fe.evaluate(cell);
+ if (cv.getCellType() != Cell.CELL_TYPE_ERROR) {
+ fail("expected error cell type but got " + cv.formatAsString());
+ }
+ int expCode = expectedResult.getErrorCode();
+ if (cv.getErrorValue() != expCode) {
+ fail("Expected error '" + ErrorEval.getText(expCode)
+ + "' but got '" + cv.formatAsString() + "'.");
+ }
+ }
}