-1 means that
- * the scope of the name will be ignored and the parser will match named ranges only by name
- *
- * @return the parsed formula tokens
+ /**
+ * @param formula the formula to parse
+ * @param workbook the parent workbook
+ * @param formulaType a constant from {@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 named ranges only by name
+ *
+ * @return the parsed formula tokens
* @throws FormulaParseException if the formula has incorrect syntax or is otherwise invalid
- */
- public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
- return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex, -1);
- }
+ */
+ public static Ptg[] parse(String formula, HSSFWorkbook workbook, int formulaType, int sheetIndex) throws FormulaParseException {
+ return FormulaParser.parse(formula, createParsingWorkbook(workbook), formulaType, sheetIndex, -1);
+ }
- /**
- * Static method to convert an array of {@link Ptg}s in RPN order
- * to a human readable string format in infix mode.
- * @param book used for defined names and 3D references
- * @param ptgs must not be null
- * @return a human readable String
- */
- public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
- return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
- }
+ /**
+ * Static method to convert an array of {@link Ptg}s in RPN order
+ * to a human readable string format in infix mode.
+ * @param book used for defined names and 3D references
+ * @param ptgs must not be null
+ * @return a human readable String
+ */
+ public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
+ return FormulaRenderer.toFormulaString(HSSFEvaluationWorkbook.create(book), ptgs);
+ }
}
diff --git a/src/java/org/apache/poi/ss/formula/FormulaParser.java b/src/java/org/apache/poi/ss/formula/FormulaParser.java
index b2e7acf730..357e875d8d 100644
--- a/src/java/org/apache/poi/ss/formula/FormulaParser.java
+++ b/src/java/org/apache/poi/ss/formula/FormulaParser.java
@@ -6,7 +6,7 @@
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
+ http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
@@ -80,7 +80,7 @@ import org.apache.poi.util.POILogger;
/**
* This class parses a formula string into a List of tokens in RPN order.
* Inspired by
- * Lets Build a Compiler, by Jack Crenshaw
+ * Lets Build a Compiler, by Jack Crenshaw
* BNF for the formula expression is :
* -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, int rowIndex) {
- FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
- fp.parse();
- return fp.getRPNPtg(formulaType);
- }
-
+ /**
+ * 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.
+ * @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.
+ * 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, int rowIndex) {
+ FormulaParser fp = new FormulaParser(formula, workbook, sheetIndex, rowIndex);
+ fp.parse();
+ return fp.getRPNPtg(formulaType);
+ }
+
public static Ptg[] parse(String formula, FormulaParsingWorkbook workbook, int formulaType, int sheetIndex) {
return parse(formula, workbook, formulaType, sheetIndex, -1);
}
@@ -187,268 +187,268 @@ public final class FormulaParser {
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;
- }
- }
+ /** 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;
+ }
+ }
- /** Report What Was Expected */
- private RuntimeException expected(String s) {
- String msg;
+ /** Report What Was Expected */
+ private RuntimeException expected(String s) {
+ String msg;
- 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);
- }
+ 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 an Alpha Character */
- private static boolean IsAlpha(char c) {
- return Character.isLetter(c) || c == '$' || c=='_';
- }
+ /** Recognize an Alpha Character */
+ private static boolean IsAlpha(char c) {
+ return Character.isLetter(c) || c == '$' || c=='_';
+ }
- /** Recognize a Decimal Digit */
- private static boolean IsDigit(char c) {
- return Character.isDigit(c);
- }
+ /** Recognize a Decimal Digit */
+ private static boolean IsDigit(char c) {
+ return Character.isDigit(c);
+ }
- /** Recognize White Space */
- private static boolean IsWhite( char c) {
- return c ==' ' || c== TAB || c == CR || c == LF;
- }
+ /** Recognize White Space */
+ private static boolean IsWhite( char c) {
+ return c ==' ' || c== TAB || c == CR || c == LF;
+ }
- /** Skip Over Leading White Space */
- private void SkipWhite() {
- while (IsWhite(look)) {
- GetChar();
- }
- }
+ /** Skip Over Leading White Space */
+ private void SkipWhite() {
+ while (IsWhite(look)) {
+ GetChar();
+ }
+ }
- /**
- * 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();
- }
+ /**
+ * 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();
+ }
- /** Get a Number */
- private String GetNum() {
- StringBuffer value = new StringBuffer();
+ /** Get a Number */
+ private String GetNum() {
+ StringBuffer value = new StringBuffer();
- while (IsDigit(this.look)){
- value.append(this.look);
- GetChar();
- }
- return value.length() == 0 ? null : value.toString();
- }
+ while (IsDigit(this.look)){
+ value.append(this.look);
+ GetChar();
+ }
+ return value.length() == 0 ? null : value.toString();
+ }
- 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 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"
- checkValidRangeOperand("LHS", pos, result);
- checkValidRangeOperand("RHS", pos, nextPart);
+ checkValidRangeOperand("LHS", pos, result);
+ checkValidRangeOperand("RHS", pos, nextPart);
- ParseNode[] children = { result, nextPart, };
- result = new ParseNode(RangePtg.instance, children);
- hasRange = true;
- }
- if (hasRange) {
- return augmentWithMemPtg(result);
- }
- return result;
- }
+ ParseNode[] children = { result, nextPart, };
+ result = new ParseNode(RangePtg.instance, children);
+ hasRange = true;
+ }
+ if (hasRange) {
+ return augmentWithMemPtg(result);
+ }
+ return result;
+ }
- 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;
- }
+ 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;
+ }
- 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;
- }
+ 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;
- }
+ return false;
+ }
- /**
- * @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.");
- }
- }
+ /**
+ * @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.");
+ }
+ }
- /**
- * @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 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;
+ }
- // 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;
- }
+ // 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;
+ }
- // one special case of ControlPtg
- if (tkn instanceof ParenthesisPtg) {
- // parenthesis Ptg should have only one child
- return isValidRangeOperand(a.getChildren()[0]);
- }
+ // one special case of ControlPtg
+ if (tkn instanceof ParenthesisPtg) {
+ // parenthesis Ptg should have only one child
+ return isValidRangeOperand(a.getChildren()[0]);
+ }
- // one special case of ScalarConstantPtg
- if (tkn == ErrPtg.REF_INVALID) {
- return true;
- }
+ // one special case of ScalarConstantPtg
+ if (tkn == ErrPtg.REF_INVALID) {
+ return true;
+ }
- // All other ControlPtgs and ScalarConstantPtgs cannot be used with ':'
- return false;
- }
+ // 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
+ /**
+ * 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 {
@@ -465,131 +465,131 @@ 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){ - + + 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("Strctured references work only on XSSF (Excel 2007)!"); } @@ -767,9 +767,9 @@ public final class FormulaParser { SheetIdentifier sheetIden = new SheetIdentifier( null, new NameIdentifier(sheetName, true)); Ptg ptg = _book.get3DReferencePtg(new AreaReference(tl, br), sheetIden); return new ParseNode(ptg); - } - - /** + } + + /** * Tries to parse the next as column - can contain whitespace * Caller should save pointer. * @return @@ -816,61 +816,61 @@ public final class FormulaParser { } - /** - * 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); + /** + * 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 (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); - } - //TODO Livshen's code - if(look == '['){ - return parseStructuredReference(name); - } - //TODO End of Livshen's code - 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 (look == '(') { + return function(name); + } + //TODO Livshen's code + if(look == '['){ + return parseStructuredReference(name); + } + //TODO End of Livshen's code + 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 or backslash @@ -884,1062 +884,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