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 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); ParseNode[] children = { result, nextPart, }; result = new ParseNode(RangePtg.instance, children); hasRange = true; } if (hasRange) { return augmentWithMemPtg(result); } return result; }
private String parseStringLiteral() { Match('"'); StringBuffer token = new StringBuffer(); while (true) { if (look == '"') { GetChar(); if (look != '"') { break; } } token.append(look); GetChar(); } return token.toString(); }
/** 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(); }
/** * 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; }
/** * 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()); }
/** 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); } }
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(); }
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; } }
/** * 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); }
/** * Consumes the next input character if it is equal to the one specified otherwise throws an * unchecked exception. This method does <b>not</b> consume whitespace (before or after the * matched character). */ private void Match(char x) { if (look != x) { throw expected("'" + x + "'"); } GetChar(); }
/** Skip Over Leading White Space */ private void SkipWhite() { while (IsWhite(look)) { GetChar(); } }