/** * Note - caller should reset {@link #_pointer} upon <code>null</code> result * * @return The sheet name as an identifier <code>null</code> 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 != '\''; } } Identifier iden = new Identifier(sb.toString(), true); // quoted identifier - can't concatenate anything more SkipWhite(); if (look == '!') { GetChar(); return new SheetIdentifier(bookName, iden, _book); } 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(); } SkipWhite(); if (look == '!') { GetChar(); return new SheetIdentifier(bookName, new Identifier(sb.toString(), false), _book); } return null; } return null; }
/** 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 = comparisonExpression(); 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 reference or constant literal"); }
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 ParseNode percentFactor() { ParseNode result = parseSimpleFactor(); while (true) { SkipWhite(); if (look != '%') { return result; } Match('%'); result = new ParseNode(PercentPtg.instance, 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 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; }
/** * Parses simple factors that are not primitive ranges or range components i.e. '!', ':'(and equiv * '...') do not appear Examples * * <pre> * my.named...range. * foo.bar(123.456, "abc") * 123.456 * "abc" * true * </pre> */ 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 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"); } while (isValidDefinedNameChar(look)) { sb.append(look); GetChar(); } SkipWhite(); String name = sb.toString(); 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 + "'"); } // 20101115, [email protected]: shall provide a temporary defined named record // EvaluationName evalName = _book.getName(name, _sheetIndex); EvaluationName evalName = _book.getOrCreateName(name, _sheetIndex); if (evalName == null) { // 20101112, [email protected]: shall return #NAME? error // throw new FormulaParseException("Specified named range '" // + name + "' does not exist in the current workbook."); return new ParseNode(ErrPtg.NAME_INVALID); } // 20101115, [email protected]: unnecessary check // if (evalName.isRange()) { // return new ParseNode(evalName.createPtg()); // } // TODO - what about NameX ? // throw new FormulaParseException("Specified name '" // + name + "' is not a range as expected."); return new ParseNode(evalName.createPtg()); }
/** get arguments to a function */ private ParseNode[] Arguments() { // average 2 args per function List<ParseNode> temp = new ArrayList<ParseNode>(2); SkipWhite(); if (look == ')') { return ParseNode.EMPTY_ARRAY; } boolean missedPrevArg = true; int numArgs = 0; while (true) { SkipWhite(); if (isArgumentDelimiter(look)) { if (missedPrevArg) { temp.add(new ParseNode(MissingArgPtg.instance)); numArgs++; } if (look == ')') { break; } Match(','); missedPrevArg = true; continue; } temp.add(comparisonExpression()); numArgs++; missedPrevArg = false; SkipWhite(); if (!isArgumentDelimiter(look)) { throw expected("',' or ')'"); } } ParseNode[] result = new ParseNode[temp.size()]; temp.toArray(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 ParseNode unionExpression() { ParseNode result = comparisonExpression(); boolean hasUnions = false; while (true) { SkipWhite(); switch (look) { case ',': GetChar(); hasUnions = true; ParseNode other = comparisonExpression(); result = new ParseNode(UnionPtg.instance, result, other); continue; } if (hasUnions) { return augmentWithMemPtg(result); } 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 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 Object[] parseArrayRow() { List<Object> temp = new ArrayList<Object>(); 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; }
/** @return <code>true</code> 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()) != 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; }
/** * Parses area refs (things which could be the operand of ':') and simple factors Examples * * <pre> * A$1 * $A$1 : $B1 * A1 ....... C2 * Sheet1 !$A1 * a..b!A1 * 'my sheet'!A1 * .my.sheet!A1 * my.named..range. * foo.bar(123.456, "abc") * 123.456 * "abc" * true * </pre> */ 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) { if (look == '#') { // error ref like MySheet!#REF! return new ParseNode(ErrPtg.valueOf(parseErrorLiteral())); } else { throw new FormulaParseException( "Cell reference expected after sheet name at index " + _pointer + "."); } } 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) 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 = ""; } else { prefix = "'" + sheetIden.getSheetIdentifier().getName() + '!'; } throw new FormulaParseException(prefix + part1.getRep() + "' is not a proper reference."); } return createAreaRefParseNode(sheetIden, part1, part2); } return createAreaRefParseNode(sheetIden, part1, part2); } 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); } 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 (part1.isRowOrColumn() || part2.isRowOrColumn()) { if (dotCount != 2) { // [email protected]: shall return #NAME? // throw new FormulaParseException("Dotted range (full row or column) expression '" + // part1And2 // + "' must have exactly 2 dots."); return parseNonRange(savePointer); } } 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); }