|
|
|
|
|
|
|
|
private final static POILogger log = POILogFactory.getLogger(FormulaParser.class); |
|
|
private final static POILogger log = POILogFactory.getLogger(FormulaParser.class); |
|
|
private final String _formulaString; |
|
|
private final String _formulaString; |
|
|
private final int _formulaLength; |
|
|
private final int _formulaLength; |
|
|
/** points at the next character to be read (after the {@link #look} char) */ |
|
|
|
|
|
|
|
|
/** points at the next character to be read (after the {@link #look} codepoint) */ |
|
|
private int _pointer; |
|
|
private int _pointer; |
|
|
|
|
|
|
|
|
private ParseNode _rootNode; |
|
|
private ParseNode _rootNode; |
|
|
|
|
|
|
|
|
private final static char LF = '\n'; // Normally just XSSF |
|
|
private final static char LF = '\n'; // Normally just XSSF |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Lookahead Character. |
|
|
|
|
|
|
|
|
* Lookahead unicode codepoint |
|
|
* gets value '\0' when the input string is exhausted |
|
|
* gets value '\0' when the input string is exhausted |
|
|
*/ |
|
|
*/ |
|
|
private char look; |
|
|
|
|
|
|
|
|
private int look; |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Tracks whether the run of whitespace preceding "look" could be an |
|
|
* Tracks whether the run of whitespace preceding "look" could be an |
|
|
|
|
|
|
|
|
throw new RuntimeException("too far"); |
|
|
throw new RuntimeException("too far"); |
|
|
} |
|
|
} |
|
|
if (_pointer < _formulaLength) { |
|
|
if (_pointer < _formulaLength) { |
|
|
look=_formulaString.charAt(_pointer); |
|
|
|
|
|
|
|
|
look=_formulaString.codePointAt(_pointer); |
|
|
} else { |
|
|
} else { |
|
|
// Just return if so and reset 'look' to something to keep |
|
|
// Just return if so and reset 'look' to something to keep |
|
|
// SkipWhitespace from spinning |
|
|
// SkipWhitespace from spinning |
|
|
look = (char)0; |
|
|
look = (char)0; |
|
|
_inIntersection = false; |
|
|
_inIntersection = false; |
|
|
} |
|
|
} |
|
|
_pointer++; |
|
|
|
|
|
//System.out.println("Got char: "+ look); |
|
|
|
|
|
|
|
|
_pointer += Character.charCount(look); |
|
|
|
|
|
//System.out.println(new StringBuilder("Got char: ").appendCodePoint(look)).toString(); |
|
|
} |
|
|
} |
|
|
private void resetPointer(int ptr) { |
|
|
private void resetPointer(int ptr) { |
|
|
_pointer = ptr; |
|
|
_pointer = ptr; |
|
|
if (_pointer <= _formulaLength) { |
|
|
if (_pointer <= _formulaLength) { |
|
|
look=_formulaString.charAt(_pointer-1); |
|
|
|
|
|
|
|
|
look=_formulaString.codePointAt(_pointer - Character.charCount(look)); |
|
|
} else { |
|
|
} else { |
|
|
// Just return if so and reset 'look' to something to keep |
|
|
// Just return if so and reset 'look' to something to keep |
|
|
// SkipWhitespace from spinning |
|
|
// SkipWhitespace from spinning |
|
|
|
|
|
|
|
|
msg = "The specified formula '" + _formulaString |
|
|
msg = "The specified formula '" + _formulaString |
|
|
+ "' starts with an equals sign which is not allowed."; |
|
|
+ "' starts with an equals sign which is not allowed."; |
|
|
} else { |
|
|
} else { |
|
|
msg = "Parse error near char " + (_pointer-1) + " '" + look + "'" |
|
|
|
|
|
+ " in specified formula '" + _formulaString + "'. Expected " |
|
|
|
|
|
+ s; |
|
|
|
|
|
|
|
|
msg = new StringBuilder("Parse error near char ") |
|
|
|
|
|
.append(_pointer-1) //this is the codepoint index, not char index, which may be larger if there are multi-byte chars |
|
|
|
|
|
.append(" '") |
|
|
|
|
|
.appendCodePoint(look) |
|
|
|
|
|
.append("'") |
|
|
|
|
|
.append(" in specified formula '") |
|
|
|
|
|
.append(_formulaString) |
|
|
|
|
|
.append("'. Expected ") |
|
|
|
|
|
.append(s) |
|
|
|
|
|
.toString(); |
|
|
} |
|
|
} |
|
|
return new FormulaParseException(msg); |
|
|
return new FormulaParseException(msg); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** Recognize an Alpha Character */ |
|
|
/** Recognize an Alpha Character */ |
|
|
private static boolean IsAlpha(char c) { |
|
|
|
|
|
|
|
|
private static boolean IsAlpha(int c) { |
|
|
return Character.isLetter(c) || c == '$' || c=='_'; |
|
|
return Character.isLetter(c) || c == '$' || c=='_'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** Recognize a Decimal Digit */ |
|
|
/** Recognize a Decimal Digit */ |
|
|
private static boolean IsDigit(char c) { |
|
|
|
|
|
|
|
|
private static boolean IsDigit(int c) { |
|
|
return Character.isDigit(c); |
|
|
return Character.isDigit(c); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** Recognize White Space */ |
|
|
/** Recognize White Space */ |
|
|
private static boolean IsWhite( char c) { |
|
|
|
|
|
|
|
|
private static boolean IsWhite(int c) { |
|
|
return c ==' ' || c== TAB || c == CR || c == LF; |
|
|
return c ==' ' || c== TAB || c == CR || c == LF; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* unchecked exception. This method does <b>not</b> consume whitespace (before or after the |
|
|
* unchecked exception. This method does <b>not</b> consume whitespace (before or after the |
|
|
* matched character). |
|
|
* matched character). |
|
|
*/ |
|
|
*/ |
|
|
private void Match(char x) { |
|
|
|
|
|
|
|
|
private void Match(int x) { |
|
|
if (look != x) { |
|
|
if (look != x) { |
|
|
throw expected("'" + x + "'"); |
|
|
|
|
|
|
|
|
throw expected(new StringBuilder() |
|
|
|
|
|
.append("'") |
|
|
|
|
|
.appendCodePoint(x) |
|
|
|
|
|
.append("'") |
|
|
|
|
|
.toString()); |
|
|
} |
|
|
} |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
StringBuilder value = new StringBuilder(); |
|
|
StringBuilder value = new StringBuilder(); |
|
|
|
|
|
|
|
|
while (IsDigit(this.look)){ |
|
|
while (IsDigit(this.look)){ |
|
|
value.append(this.look); |
|
|
|
|
|
|
|
|
value.appendCodePoint(this.look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
return value.length() == 0 ? null : value.toString(); |
|
|
return value.length() == 0 ? null : value.toString(); |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
StringBuilder name = new StringBuilder(); |
|
|
StringBuilder name = new StringBuilder(); |
|
|
while (look!=']') { |
|
|
while (look!=']') { |
|
|
name.append(look); |
|
|
|
|
|
|
|
|
name.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
Match(']'); |
|
|
Match(']'); |
|
|
|
|
|
|
|
|
throw expected("number, string, defined name, or data table"); |
|
|
throw expected("number, string, defined name, or data table"); |
|
|
} |
|
|
} |
|
|
while (isValidDefinedNameChar(look)) { |
|
|
while (isValidDefinedNameChar(look)) { |
|
|
sb.append(look); |
|
|
|
|
|
|
|
|
sb.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
SkipWhite(); |
|
|
SkipWhite(); |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* |
|
|
|
|
|
|
|
|
* @param ch unicode codepoint |
|
|
* @return <code>true</code> if the specified character may be used in a defined name |
|
|
* @return <code>true</code> if the specified character may be used in a defined name |
|
|
*/ |
|
|
*/ |
|
|
private static boolean isValidDefinedNameChar(char ch) { |
|
|
|
|
|
|
|
|
private static boolean isValidDefinedNameChar(int ch) { |
|
|
if (Character.isLetterOrDigit(ch)) { |
|
|
if (Character.isLetterOrDigit(ch)) { |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
// the sheet naming rules are vague on whether unicode characters are allowed |
|
|
|
|
|
// assume they're allowed. |
|
|
|
|
|
if (ch > 128) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
switch (ch) { |
|
|
switch (ch) { |
|
|
case '.': |
|
|
case '.': |
|
|
case '_': |
|
|
case '_': |
|
|
|
|
|
|
|
|
case '\\': // of all things |
|
|
case '\\': // of all things |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
// includes special non-name control characters like ! $ : , ( ) [ ] and space |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
StringBuilder sb = new StringBuilder(); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
while (look != ']') { |
|
|
while (look != ']') { |
|
|
sb.append(look); |
|
|
|
|
|
|
|
|
sb.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
StringBuilder sb = new StringBuilder(); |
|
|
boolean done = look == '\''; |
|
|
boolean done = look == '\''; |
|
|
while(!done) { |
|
|
while(!done) { |
|
|
sb.append(look); |
|
|
|
|
|
|
|
|
sb.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
if(look == '\'') |
|
|
if(look == '\'') |
|
|
{ |
|
|
{ |
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder(); |
|
|
StringBuilder sb = new StringBuilder(); |
|
|
// can concatenate idens with dots |
|
|
// can concatenate idens with dots |
|
|
while (isUnquotedSheetNameChar(look)) { |
|
|
while (isUnquotedSheetNameChar(look)) { |
|
|
sb.append(look); |
|
|
|
|
|
|
|
|
sb.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
NameIdentifier iden = new NameIdentifier(sb.toString(), false); |
|
|
NameIdentifier iden = new NameIdentifier(sb.toString(), false); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* very similar to {@link SheetNameFormatter#isSpecialChar(char)} |
|
|
* very similar to {@link SheetNameFormatter#isSpecialChar(char)} |
|
|
|
|
|
* @param ch unicode codepoint |
|
|
*/ |
|
|
*/ |
|
|
private static boolean isUnquotedSheetNameChar(char ch) { |
|
|
|
|
|
|
|
|
private static boolean isUnquotedSheetNameChar(int ch) { |
|
|
if(Character.isLetterOrDigit(ch)) { |
|
|
if(Character.isLetterOrDigit(ch)) { |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
// the sheet naming rules are vague on whether unicode characters are allowed |
|
|
|
|
|
// assume they're allowed. |
|
|
|
|
|
if (ch > 128) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
switch(ch) { |
|
|
switch(ch) { |
|
|
case '.': // dot is OK |
|
|
case '.': // dot is OK |
|
|
case '_': // underscore is OK |
|
|
case '_': // underscore is OK |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private static boolean isArgumentDelimiter(char ch) { |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* @param ch unicode codepoint |
|
|
|
|
|
* |
|
|
|
|
|
*/ |
|
|
|
|
|
private static boolean isArgumentDelimiter(int ch) { |
|
|
return ch == ',' || ch == ')'; |
|
|
return ch == ',' || ch == ')'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
StringBuilder sb = new StringBuilder(); |
|
|
StringBuilder sb = new StringBuilder(); |
|
|
while (Character.isLetterOrDigit(look) || look == '.') { |
|
|
while (Character.isLetterOrDigit(look) || look == '.') { |
|
|
sb.append(look); |
|
|
|
|
|
|
|
|
sb.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
if (sb.length() < 1) { |
|
|
if (sb.length() < 1) { |
|
|
|
|
|
|
|
|
break; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
token.append(look); |
|
|
|
|
|
|
|
|
token.appendCodePoint(look); |
|
|
GetChar(); |
|
|
GetChar(); |
|
|
} |
|
|
} |
|
|
return token.toString(); |
|
|
return token.toString(); |