/** * 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()); }
/** * 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 <code>null</code> (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 > 65536) { return null; } } else { // just dollars ? can this happen? return null; } resetPointer(ptr + 1); // stepping forward return new SimpleRangePart(rep, hasLetters, hasDigits); }
/** 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"); }
/** * 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; }
/** 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 case ':': // colon is OK return true; } return false; }
/** @return <code>true</code> 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; }
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(); }
/** Recognize a Decimal Digit */ private static boolean IsDigit(char c) { return Character.isDigit(c); }
/** Recognize an Alpha Character */ private static boolean IsAlpha(char c) { return Character.isLetter(c) || c == '$' || c == '_'; }