You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@poi.apache.org by ug...@apache.org on 2008/03/08 12:49:17 UTC
svn commit: r634936 [2/8] - in /poi/branches/ooxml: ./ legal/
src/contrib/src/org/apache/poi/hssf/contrib/view/
src/documentation/content/xdocs/ src/documentation/content/xdocs/getinvolved/
src/documentation/content/xdocs/hslf/ src/java/org/apache/poi/...
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/model/FormulaParser.java Sat Mar 8 03:49:00 2008
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,15 +14,13 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
-
package org.apache.poi.hssf.model;
import java.util.ArrayList;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
+import java.util.Stack;
import java.util.regex.Pattern;
//import PTG's .. since we need everything, import *
@@ -33,7 +30,7 @@
/**
* This class parses a formula string into a List of tokens in RPN order.
- * Inspired by
+ * Inspired by
* Lets Build a Compiler, by Jack Crenshaw
* BNF for the formula expression is :
* <expression> ::= <term> [<addop> <term>]*
@@ -48,138 +45,124 @@
* @author Peter M. Murray (pete at quantrix dot com)
* @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
*/
-public class FormulaParser {
+public final class FormulaParser {
+ /**
+ * Specific exception thrown when a supplied formula does not parse properly.<br/>
+ * Primarily used by test cases when testing for specific parsing exceptions.</p>
+ *
+ */
+ static final class FormulaParseException extends RuntimeException {
+ // This class was given package scope until it would become clear that it is useful to
+ // general client code.
+ public FormulaParseException(String msg) {
+ super(msg);
+ }
+ }
+
public static int FORMULA_TYPE_CELL = 0;
public static int FORMULA_TYPE_SHARED = 1;
public static int FORMULA_TYPE_ARRAY =2;
public static int FORMULA_TYPE_CONDFOMRAT = 3;
public static int FORMULA_TYPE_NAMEDRANGE = 4;
-
- private String formulaString;
- private int pointer=0;
- private int formulaLength;
-
- private List tokens = new java.util.Stack();
-
- /**
- * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded.
- */
- private List functionTokens = new LinkedList();
+
+ private final String formulaString;
+ private final int formulaLength;
+ private int pointer;
+
+ private final List tokens = new Stack();
/**
* Used for spotting if we have a cell reference,
* or a named range
*/
private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+");
-
+
private static char TAB = '\t';
- private static char CR = '\n';
-
- private char look; // Lookahead Character
-
- private Workbook book;
-
-
- /**
+
+ /**
+ * Lookahead Character.
+ * gets value '\0' when the input string is exhausted
+ */
+ private char look;
+
+ private Workbook book;
+
+
+ /**
* Create the formula parser, with the string that is to be
* parsed against the supplied workbook.
* A later call the parse() method to return ptg list in
* rpn order, then call the getRPNPtg() to retrive the
* parse results.
* This class is recommended only for single threaded use.
- *
+ *
* If you only have a usermodel.HSSFWorkbook, and not a
* model.Workbook, then use the convenience method on
- * usermodel.HSSFFormulaEvaluator
+ * usermodel.HSSFFormulaEvaluator
*/
public FormulaParser(String formula, Workbook book){
formulaString = formula;
pointer=0;
this.book = book;
- formulaLength = formulaString.length();
+ formulaLength = formulaString.length();
+ }
+
+ public static Ptg[] parse(String formula, Workbook book) {
+ FormulaParser fp = new FormulaParser(formula, book);
+ fp.parse();
+ return fp.getRPNPtg();
}
-
/** Read New Character From Input Stream */
private void GetChar() {
// Check to see if we've walked off the end of the string.
- // Just return if so and reset Look to smoething to keep
- // SkipWhitespace from spinning
- if (pointer == formulaLength) {
+ 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;
- return;
- }
- look=formulaString.charAt(pointer++);
+ }
+ pointer++;
//System.out.println("Got char: "+ look);
}
-
-
- /** Report an Error */
- private void Error(String s) {
- System.out.println("Error: "+s);
- }
-
-
-
- /** Report Error and Halt */
- private void Abort(String s) {
- Error(s);
- //System.exit(1); //throw exception??
- throw new RuntimeException("Cannot Parse, sorry : " + s + " @ " + pointer + " [Formula String was: '" + formulaString + "']");
- }
-
-
/** Report What Was Expected */
- private void Expected(String s) {
- Abort(s + " Expected");
+ private RuntimeException expected(String s) {
+ return new FormulaParseException(s + " Expected");
}
-
-
-
+
+
+
/** Recognize an Alpha Character */
private boolean IsAlpha(char c) {
return Character.isLetter(c) || c == '$' || c=='_';
}
-
-
-
+
+
+
/** Recognize a Decimal Digit */
private boolean IsDigit(char c) {
//System.out.println("Checking digit for"+c);
return Character.isDigit(c);
}
-
-
+
+
/** Recognize an Alphanumeric */
private boolean IsAlNum(char c) {
return (IsAlpha(c) || IsDigit(c));
}
-
-
- /** Recognize an Addop */
- private boolean IsAddop( char c) {
- return (c =='+' || c =='-');
- }
-
/** Recognize White Space */
private boolean IsWhite( char c) {
return (c ==' ' || c== TAB);
}
-
- /**
- * Determines special characters;primarily in use for definition of string literals
- * @param c
- * @return boolean
- */
- private boolean IsSpecialChar(char c) {
- return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']');
- }
-
/** Skip Over Leading White Space */
private void SkipWhite() {
@@ -187,108 +170,84 @@
GetChar();
}
}
-
-
- /** Match a Specific Input Character */
+ /**
+ * 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) {
- Expected("" + x + "");
- }else {
- GetChar();
- SkipWhite();
+ throw expected("'" + x + "'");
}
+ GetChar();
}
-
+
/** Get an Identifier */
private String GetName() {
StringBuffer Token = new StringBuffer();
if (!IsAlpha(look) && look != '\'') {
- Expected("Name");
+ throw expected("Name");
}
if(look == '\'')
{
- Match('\'');
- boolean done = look == '\'';
- while(!done)
- {
- Token.append(Character.toUpperCase(look));
- GetChar();
- if(look == '\'')
- {
- Match('\'');
- done = look != '\'';
- }
- }
+ Match('\'');
+ boolean done = look == '\'';
+ while(!done)
+ {
+ Token.append(look);
+ GetChar();
+ if(look == '\'')
+ {
+ Match('\'');
+ done = look != '\'';
+ }
+ }
}
else
{
- while (IsAlNum(look)) {
- Token.append(Character.toUpperCase(look));
- GetChar();
- }
- }
- SkipWhite();
- return Token.toString();
- }
-
- /**Get an Identifier AS IS, without stripping white spaces or
- converting to uppercase; used for literals */
- private String GetNameAsIs() {
- StringBuffer Token = new StringBuffer();
-
- while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) {
- Token = Token.append(look);
- GetChar();
+ while (IsAlNum(look)) {
+ 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();
}
-
- SkipWhite();
-
return value.length() == 0 ? null : value.toString();
}
-
-
- /** Output a String with Tab */
- private void Emit(String s){
- System.out.print(TAB+s);
- }
- /** Output a String with Tab and CRLF */
- private void EmitLn(String s) {
- Emit(s);
- System.out.println();;
- }
-
/** Parse and Translate a String Identifier */
- private void Ident() {
+ private Ptg parseIdent() {
String name;
name = GetName();
if (look == '('){
//This is a function
- function(name);
- } else if (look == ':' || look == '.') { // this is a AreaReference
+ return function(name);
+ }
+
+ if (look == ':' || look == '.') { // this is a AreaReference
GetChar();
-
+
while (look == '.') { // formulas can have . or .. or ... instead of :
GetChar();
}
-
+
String first = name;
String second = GetName();
- tokens.add(new AreaPtg(first+":"+second));
- } else if (look == '!') {
+ return new AreaPtg(first+":"+second);
+ }
+
+ if (look == '!') {
Match('!');
String sheetName = name;
String first = GetName();
@@ -297,79 +256,78 @@
Match(':');
String second=GetName();
if (look == '!') {
- //The sheet name was included in both of the areas. Only really
- //need it once
- Match('!');
- String third=GetName();
-
- if (!sheetName.equals(second))
- throw new RuntimeException("Unhandled double sheet reference.");
-
- tokens.add(new Area3DPtg(first+":"+third,externIdx));
- } else {
- tokens.add(new Area3DPtg(first+":"+second,externIdx));
+ //The sheet name was included in both of the areas. Only really
+ //need it once
+ Match('!');
+ String third=GetName();
+
+ if (!sheetName.equals(second))
+ throw new RuntimeException("Unhandled double sheet reference.");
+
+ return new Area3DPtg(first+":"+third,externIdx);
}
- } else {
- tokens.add(new Ref3DPtg(first,externIdx));
+ return new Area3DPtg(first+":"+second,externIdx);
}
- } else {
- // This can be either a cell ref or a named range
- // Try to spot which it is
- boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
- boolean boolLit = (name.equals("TRUE") || name.equals("FALSE"));
-
- if (boolLit) {
- tokens.add(new BoolPtg(name));
- } else if (cellRef) {
- tokens.add(new ReferencePtg(name));
- } else {
- boolean nameRecordExists = false;
- for(int i = 0; i < book.getNumNames(); i++) {
- // Our formula will by now contain an upper-cased
- // version of any named range names
- if(book.getNameRecord(i).getNameText().toUpperCase().equals(name)) {
- nameRecordExists = true;
- }
- }
- if(!nameRecordExists)
- Abort("Found reference to named range \"" + name + "\", but that named range wasn't defined!");
- tokens.add(new NamePtg(name, book));
+ return new Ref3DPtg(first,externIdx);
+ }
+ if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
+ return new BoolPtg(name.toUpperCase());
+ }
+
+ // This can be either a cell ref or a named range
+ // Try to spot which it is
+ boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
+
+ if (cellRef) {
+ return new ReferencePtg(name);
+ }
+
+ for(int i = 0; i < book.getNumNames(); i++) {
+ // named range name matching is case insensitive
+ if(book.getNameRecord(i).getNameText().equalsIgnoreCase(name)) {
+ return new NamePtg(name, book);
}
}
+ throw new FormulaParseException("Found reference to named range \""
+ + name + "\", but that named range wasn't defined!");
}
-
+
/**
* Adds a pointer to the last token to the latest function argument list.
* @param obj
*/
- private void addArgumentPointer() {
- if (this.functionTokens.size() > 0) {
- //no bounds check because this method should not be called unless a token array is setup by function()
- List arguments = (List)this.functionTokens.get(0);
- arguments.add(tokens.get(tokens.size()-1));
- }
+ private void addArgumentPointer(List argumentPointers) {
+ argumentPointers.add(tokens.get(tokens.size()-1));
}
-
- private void function(String name) {
- //average 2 args per function
- this.functionTokens.add(0, new ArrayList(2));
-
+
+ /**
+ * 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.
+ *
+ * @param name case preserved function name (as it was entered/appeared in the formula).
+ */
+ private Ptg function(String name) {
+ int numArgs =0 ;
+ // Note regarding parameter -
+ if(!AbstractFunctionPtg.isInternalFunctionName(name)) {
+ // external functions get a Name token which points to a defined name record
+ NamePtg nameToken = new NamePtg(name, this.book);
+
+ // in the token tree, the name is more or less the first argument
+ numArgs++;
+ tokens.add(nameToken);
+ }
+ //average 2 args per function
+ List argumentPointers = new ArrayList(2);
+
Match('(');
- int numArgs = Arguments();
+ numArgs += Arguments(argumentPointers);
Match(')');
-
- AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs);
-
- tokens.add(functionPtg);
-
- if (functionPtg.getName().equals("externalflag")) {
- tokens.add(new NamePtg(name, this.book));
- }
- //remove what we just put in
- this.functionTokens.remove(0);
+ return getFunction(name, numArgs, argumentPointers);
}
-
+
/**
* Adds the size of all the ptgs after the provided index (inclusive).
* <p>
@@ -378,17 +336,17 @@
* @return int
*/
private int getPtgSize(int index) {
- int count = 0;
-
- Iterator ptgIterator = tokens.listIterator(index);
- while (ptgIterator.hasNext()) {
- Ptg ptg = (Ptg)ptgIterator.next();
- count+=ptg.getSize();
- }
-
- return count;
+ int count = 0;
+
+ Iterator ptgIterator = tokens.listIterator(index);
+ while (ptgIterator.hasNext()) {
+ Ptg ptg = (Ptg)ptgIterator.next();
+ count+=ptg.getSize();
+ }
+
+ return count;
}
-
+
private int getPtgSize(int start, int end) {
int count = 0;
int index = start;
@@ -398,390 +356,430 @@
count+=ptg.getSize();
index++;
}
-
+
return count;
}
/**
* Generates the variable function ptg for the formula.
* <p>
- * For IF Formulas, additional PTGs are added to the tokens
+ * For IF Formulas, additional PTGs are added to the tokens
* @param name
* @param numArgs
* @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
*/
- private AbstractFunctionPtg getFunction(String name, byte numArgs) {
- AbstractFunctionPtg retval = null;
-
- if (name.equals("IF")) {
- retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);
-
- //simulated pop, no bounds checking because this list better be populated by function()
- List argumentPointers = (List)this.functionTokens.get(0);
-
-
- AttrPtg ifPtg = new AttrPtg();
- ifPtg.setData((short)7); //mirroring excel output
- ifPtg.setOptimizedIf(true);
-
- if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
- throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
- }
-
- //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
- //tracked in the argument pointers
- //The beginning first argument pointer is the last ptg of the condition
- int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
- tokens.add(ifIndex, ifPtg);
-
- //we now need a goto ptgAttr to skip to the end of the formula after a true condition
- //the true condition is should be inserted after the last ptg in the first argument
-
- int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
-
- AttrPtg goto1Ptg = new AttrPtg();
- goto1Ptg.setGoto(true);
-
-
- tokens.add(gotoIndex, goto1Ptg);
-
-
- if (numArgs > 2) { //only add false jump if there is a false condition
-
- //second goto to skip past the function ptg
- AttrPtg goto2Ptg = new AttrPtg();
- goto2Ptg.setGoto(true);
- goto2Ptg.setData((short)(retval.getSize()-1));
- //Page 472 of the Microsoft Excel Developer's kit states that:
- //The b(or w) field specifies the number byes (or words to skip, minus 1
-
- tokens.add(goto2Ptg); //this goes after all the arguments are defined
- }
-
- //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
- //count the number of bytes after the ifPtg to the False Subexpression
- //doesn't specify -1 in the documentation
- ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
-
- //count all the additional (goto) ptgs but dont count itself
- int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
- if (ptgCount > (int)Short.MAX_VALUE) {
- throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
- }
-
- goto1Ptg.setData((short)(ptgCount-1));
-
- } else {
-
- retval = new FuncVarPtg(name,numArgs);
+ private AbstractFunctionPtg getFunction(String name, int numArgs, List argumentPointers) {
+
+ AbstractFunctionPtg retval = new FuncVarPtg(name, (byte)numArgs);
+ if (!name.equals(AbstractFunctionPtg.FUNCTION_NAME_IF)) {
+ // early return for everything else besides IF()
+ return retval;
}
-
+
+
+ AttrPtg ifPtg = new AttrPtg();
+ ifPtg.setData((short)7); //mirroring excel output
+ ifPtg.setOptimizedIf(true);
+
+ if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
+ throw new IllegalArgumentException("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
+ }
+
+ //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
+ //tracked in the argument pointers
+ //The beginning first argument pointer is the last ptg of the condition
+ int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
+ tokens.add(ifIndex, ifPtg);
+
+ //we now need a goto ptgAttr to skip to the end of the formula after a true condition
+ //the true condition is should be inserted after the last ptg in the first argument
+
+ int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
+
+ AttrPtg goto1Ptg = new AttrPtg();
+ goto1Ptg.setGoto(true);
+
+
+ tokens.add(gotoIndex, goto1Ptg);
+
+
+ if (numArgs > 2) { //only add false jump if there is a false condition
+
+ //second goto to skip past the function ptg
+ AttrPtg goto2Ptg = new AttrPtg();
+ goto2Ptg.setGoto(true);
+ goto2Ptg.setData((short)(retval.getSize()-1));
+ //Page 472 of the Microsoft Excel Developer's kit states that:
+ //The b(or w) field specifies the number byes (or words to skip, minus 1
+
+ tokens.add(goto2Ptg); //this goes after all the arguments are defined
+ }
+
+ //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
+ //count the number of bytes after the ifPtg to the False Subexpression
+ //doesn't specify -1 in the documentation
+ ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
+
+ //count all the additional (goto) ptgs but dont count itself
+ int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
+ if (ptgCount > Short.MAX_VALUE) {
+ throw new RuntimeException("Ptg Size exceeds short when being specified for a goto ptg in an if");
+ }
+
+ goto1Ptg.setData((short)(ptgCount-1));
+
return retval;
}
+
+ private static boolean isArgumentDelimiter(char ch) {
+ return ch == ',' || ch == ')';
+ }
/** get arguments to a function */
- private int Arguments() {
+ private int Arguments(List argumentPointers) {
+ SkipWhite();
+ if(look == ')') {
+ return 0;
+ }
+
+ boolean missedPrevArg = true;
+
int numArgs = 0;
- if (look != ')') {
- numArgs++;
- Expression();
- addArgumentPointer();
- }
- while (look == ',' || look == ';') { //TODO handle EmptyArgs
- if(look == ',') {
- Match(',');
- }
- else {
- Match(';');
+ while(true) {
+ SkipWhite();
+ if(isArgumentDelimiter(look)) {
+ if(missedPrevArg) {
+ tokens.add(new MissingArgPtg());
+ addArgumentPointer(argumentPointers);
+ numArgs++;
+ }
+ if(look == ')') {
+ break;
+ }
+ Match(',');
+ missedPrevArg = true;
+ continue;
}
- Expression();
- addArgumentPointer();
+ comparisonExpression();
+ addArgumentPointer(argumentPointers);
numArgs++;
+ missedPrevArg = false;
}
return numArgs;
}
/** Parse and Translate a Math Factor */
- private void Factor() {
- if (look == '-')
- {
- Match('-');
- Factor();
- tokens.add(new UnaryMinusPtg());
- }
- else if (look == '+') {
- Match('+');
- Factor();
- tokens.add(new UnaryPlusPtg());
- }
- else if (look == '(' ) {
- Match('(');
- Expression();
- Match(')');
- tokens.add(new ParenthesisPtg());
- } else if (IsAlpha(look) || look == '\''){
- Ident();
- } else if(look == '"') {
- StringLiteral();
- } else if (look == ')' || look == ',') {
- tokens.add(new MissingArgPtg());
- } else {
- String number2 = null;
- String exponent = null;
- String number1 = GetNum();
-
- if (look == '.') {
- GetChar();
- number2 = GetNum();
+ private void powerFactor() {
+ percentFactor();
+ while(true) {
+ SkipWhite();
+ if(look != '^') {
+ return;
}
-
- if (look == 'E') {
+ Match('^');
+ percentFactor();
+ tokens.add(new PowerPtg());
+ }
+ }
+
+ private void percentFactor() {
+ tokens.add(parseSimpleFactor());
+ while(true) {
+ SkipWhite();
+ if(look != '%') {
+ return;
+ }
+ Match('%');
+ tokens.add(new PercentPtg());
+ }
+ }
+
+
+ /**
+ * factors (without ^ or % )
+ */
+ private Ptg parseSimpleFactor() {
+ SkipWhite();
+ switch(look) {
+ case '#':
+ return parseErrorLiteral();
+ case '-':
+ Match('-');
+ powerFactor();
+ return new UnaryMinusPtg();
+ case '+':
+ Match('+');
+ powerFactor();
+ return new UnaryPlusPtg();
+ case '(':
+ Match('(');
+ comparisonExpression();
+ Match(')');
+ return new ParenthesisPtg();
+ case '"':
+ return parseStringLiteral();
+ case ',':
+ case ')':
+ return new MissingArgPtg(); // TODO - not quite the right place to recognise a missing arg
+ }
+ if (IsAlpha(look) || look == '\''){
+ return parseIdent();
+ }
+ // else - assume number
+ return parseNumber();
+ }
+
+
+ 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();
-
- String sign = "";
- if (look == '+') {
- GetChar();
- } else if (look == '-') {
- GetChar();
- sign = "-";
- }
-
- String number = GetNum();
- if (number == null) {
- Expected("Integer");
- }
- exponent = sign + number;
+ } else if (look == '-') {
+ GetChar();
+ sign = "-";
}
-
- if (number1 == null && number2 == null) {
- Expected("Integer");
+
+ String number = GetNum();
+ if (number == null) {
+ throw expected("Integer");
}
-
- tokens.add(getNumberPtgFromString(number1, number2, exponent));
+ exponent = sign + number;
}
+
+ if (number1 == null && number2 == null) {
+ throw expected("Integer");
+ }
+
+ return getNumberPtgFromString(number1, number2, exponent);
}
-
- /**
- * Get a PTG for an integer from its string representation.
- * return Int or Number Ptg based on size of input
- */
- private Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
+
+
+ private ErrPtg parseErrorLiteral() {
+ Match('#');
+ String part1 = GetName().toUpperCase();
+
+ switch(part1.charAt(0)) {
+ case 'V':
+ if(part1.equals("VALUE")) {
+ Match('!');
+ return ErrPtg.VALUE_INVALID;
+ }
+ throw expected("#VALUE!");
+ case 'R':
+ if(part1.equals("REF")) {
+ Match('!');
+ return ErrPtg.REF_INVALID;
+ }
+ throw expected("#REF!");
+ case 'D':
+ if(part1.equals("DIV")) {
+ Match('/');
+ Match('0');
+ Match('!');
+ return ErrPtg.DIV_ZERO;
+ }
+ throw expected("#DIV/0!");
+ case 'N':
+ if(part1.equals("NAME")) {
+ Match('?'); // only one that ends in '?'
+ return ErrPtg.NAME_INVALID;
+ }
+ if(part1.equals("NUM")) {
+ Match('!');
+ return ErrPtg.NUM_ERROR;
+ }
+ if(part1.equals("NULL")) {
+ Match('!');
+ return ErrPtg.NULL_INTERSECTION;
+ }
+ if(part1.equals("N")) {
+ Match('/');
+ if(look != 'A' && look != 'a') {
+ throw expected("#N/A");
+ }
+ Match(look);
+ // Note - no '!' or '?' suffix
+ return ErrPtg.N_A;
+ }
+ throw expected("#NAME?, #NUM!, #NULL! or #N/A");
+
+ }
+ throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
+ }
+
+
+ /**
+ * Get a PTG for an integer from its string representation.
+ * return Int or Number Ptg based on size of input
+ */
+ private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
StringBuffer number = new StringBuffer();
-
- if (number2 == null) {
- number.append(number1);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
- }
-
+
+ if (number2 == null) {
+ number.append(number1);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+
String numberStr = number.toString();
-
+ int intVal;
try {
- return new IntPtg(numberStr);
+ intVal = Integer.parseInt(numberStr);
} catch (NumberFormatException e) {
return new NumberPtg(numberStr);
}
- } else {
- if (number1 != null) {
- number.append(number1);
- }
-
- number.append('.');
- number.append(number2);
-
- if (exponent != null) {
- number.append('E');
- number.append(exponent);
+ if (IntPtg.isInRange(intVal)) {
+ return new IntPtg(intVal);
}
-
- return new NumberPtg(number.toString());
- }
- }
-
-
- private void StringLiteral()
- {
- // Can't use match here 'cuz it consumes whitespace
- // which we need to preserve inside the string.
- // - pete
- // Match('"');
- if (look != '"')
- Expected("\"");
- else
- {
- GetChar();
- StringBuffer Token = new StringBuffer();
- for (;;)
- {
- if (look == '"')
- {
- GetChar();
- SkipWhite(); //potential white space here since it doesnt matter up to the operator
- if (look == '"')
- Token.append("\"");
- else
- break;
- }
- else if (look == 0)
- {
- break;
- }
- else
- {
- Token.append(look);
- GetChar();
- }
- }
- tokens.add(new StringPtg(Token.toString()));
- }
- }
-
- /** Recognize and Translate a Multiply */
- private void Multiply(){
- Match('*');
- Factor();
- tokens.add(new MultiplyPtg());
-
- }
-
-
- /** Recognize and Translate a Divide */
- private void Divide() {
- Match('/');
- Factor();
- tokens.add(new DividePtg());
+ return new NumberPtg(numberStr);
+ }
+
+ if (number1 != null) {
+ number.append(number1);
+ }
+
+ number.append('.');
+ number.append(number2);
+
+ if (exponent != null) {
+ number.append('E');
+ number.append(exponent);
+ }
+ return new NumberPtg(number.toString());
}
-
-
- /** Parse and Translate a Math Term */
- private void Term(){
- Factor();
- while (look == '*' || look == '/' || look == '^' || look == '&') {
+
+
+ private StringPtg parseStringLiteral()
+ {
+ Match('"');
- ///TODO do we need to do anything here??
- if (look == '*') Multiply();
- else if (look == '/') Divide();
- else if (look == '^') Power();
- else if (look == '&') Concat();
+ StringBuffer token = new StringBuffer();
+ while (true) {
+ if (look == '"') {
+ GetChar();
+ if (look != '"') {
+ break;
+ }
+ }
+ token.append(look);
+ GetChar();
}
+ return new StringPtg(token.toString());
}
-
- /** Recognize and Translate an Add */
- private void Add() {
- Match('+');
- Term();
- tokens.add(new AddPtg());
+
+ /** Parse and Translate a Math Term */
+ private void Term() {
+ powerFactor();
+ while(true) {
+ SkipWhite();
+ switch(look) {
+ case '*':
+ Match('*');
+ powerFactor();
+ tokens.add(new MultiplyPtg());
+ continue;
+ case '/':
+ Match('/');
+ powerFactor();
+ tokens.add(new DividePtg());
+ continue;
+ }
+ return; // finished with Term
+ }
}
- /** Recognize and Translate a Concatination */
- private void Concat() {
- Match('&');
- Term();
- tokens.add(new ConcatPtg());
+ private void comparisonExpression() {
+ concatExpression();
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case '=':
+ case '>':
+ case '<':
+ Ptg comparisonToken = getComparisonToken();
+ concatExpression();
+ tokens.add(comparisonToken);
+ continue;
+ }
+ return; // finished with predicate expression
+ }
}
-
- /** Recognize and Translate a test for Equality */
- private void Equal() {
- Match('=');
- Expression();
- tokens.add(new EqualPtg());
+
+ private Ptg getComparisonToken() {
+ if(look == '=') {
+ Match(look);
+ return new EqualPtg();
+ }
+ boolean isGreater = look == '>';
+ Match(look);
+ if(isGreater) {
+ if(look == '=') {
+ Match('=');
+ return new GreaterEqualPtg();
+ }
+ return new GreaterThanPtg();
+ }
+ switch(look) {
+ case '=':
+ Match('=');
+ return new LessEqualPtg();
+ case '>':
+ Match('>');
+ return new NotEqualPtg();
+ }
+ return new LessThanPtg();
}
- /** Recognize and Translate a Subtract */
- private void Subtract() {
- Match('-');
- Term();
- tokens.add(new SubtractPtg());
- }
- private void Power() {
- Match('^');
- Term();
- tokens.add(new PowerPtg());
+ private void concatExpression() {
+ additiveExpression();
+ while (true) {
+ SkipWhite();
+ if(look != '&') {
+ break; // finished with concat expression
+ }
+ Match('&');
+ additiveExpression();
+ tokens.add(new ConcatPtg());
+ }
}
-
+
/** Parse and Translate an Expression */
- private void Expression() {
+ private void additiveExpression() {
Term();
- while (IsAddop(look)) {
- if (look == '+' ) Add();
- else if (look == '-') Subtract();
+ while (true) {
+ SkipWhite();
+ switch(look) {
+ case '+':
+ Match('+');
+ Term();
+ tokens.add(new AddPtg());
+ continue;
+ case '-':
+ Match('-');
+ Term();
+ tokens.add(new SubtractPtg());
+ continue;
+ }
+ return; // finished with additive expression
}
-
- /*
- * This isn't quite right since it would allow multiple comparison operators.
- */
-
- if(look == '=' || look == '>' || look == '<') {
- if (look == '=') Equal();
- else if (look == '>') GreaterThan();
- else if (look == '<') LessThan();
- return;
- }
-
-
}
-
- /** Recognize and Translate a Greater Than */
- private void GreaterThan() {
- Match('>');
- if(look == '=')
- GreaterEqual();
- else {
- Expression();
- tokens.add(new GreaterThanPtg());
- }
- }
-
- /** Recognize and Translate a Less Than */
- private void LessThan() {
- Match('<');
- if(look == '=')
- LessEqual();
- else if(look == '>')
- NotEqual();
- else {
- Expression();
- tokens.add(new LessThanPtg());
- }
-
- }
-
- /**
- * Recognize and translate Greater than or Equal
- *
- */
- private void GreaterEqual() {
- Match('=');
- Expression();
- tokens.add(new GreaterEqualPtg());
- }
-
- /**
- * Recognize and translate Less than or Equal
- *
- */
-
- private void LessEqual() {
- Match('=');
- Expression();
- tokens.add(new LessEqualPtg());
- }
-
- /**
- * Recognize and not Equal
- *
- */
-
- private void NotEqual() {
- Match('>');
- Expression();
- tokens.add(new NotEqualPtg());
- }
-
+
//{--------------------------------------------------------------}
//{ Parse and Translate an Assignment Statement }
/**
@@ -794,48 +792,46 @@
end;
**/
-
-
- /** Initialize */
-
- private void init() {
- GetChar();
- SkipWhite();
- }
-
+
+
/** API call to execute the parsing of the formula
*
*/
public void parse() {
- synchronized (tokens) {
- init();
- Expression();
+ pointer=0;
+ GetChar();
+ comparisonExpression();
+
+ if(pointer <= formulaLength) {
+ String msg = "Unused input [" + formulaString.substring(pointer-1)
+ + "] after attempting to parse the formula [" + formulaString + "]";
+ throw new FormulaParseException(msg);
}
}
-
-
+
+
/*********************************
* PARSER IMPLEMENTATION ENDS HERE
* EXCEL SPECIFIC METHODS BELOW
*******************************/
-
- /** API call to retrive the array of Ptgs created as
+
+ /** API call to retrive the array of Ptgs created as
* a result of the parsing
*/
public Ptg[] getRPNPtg() {
return getRPNPtg(FORMULA_TYPE_CELL);
}
-
+
public Ptg[] getRPNPtg(int formulaType) {
Node node = createTree();
setRootLevelRVA(node, formulaType);
setParameterRVA(node,formulaType);
return (Ptg[]) tokens.toArray(new Ptg[0]);
}
-
+
private void setRootLevelRVA(Node n, int formulaType) {
//Pg 16, excelfileformat.pdf @ openoffice.org
- Ptg p = (Ptg) n.getValue();
+ Ptg p = n.getValue();
if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
setClass(n,Ptg.CLASS_REF);
@@ -845,9 +841,9 @@
} else {
setClass(n,Ptg.CLASS_VALUE);
}
-
+
}
-
+
private void setParameterRVA(Node n, int formulaType) {
Ptg p = n.getValue();
int numOperands = n.getNumChildren();
@@ -863,11 +859,11 @@
for (int i =0;i<numOperands;i++) {
setParameterRVA(n.getChild(i),formulaType);
}
- }
+ }
}
private void setParameterRVA(Node n, int expectedClass,int formulaType) {
- Ptg p = (Ptg) n.getValue();
- if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
+ Ptg p = n.getValue();
+ if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
setClass(n, Ptg.CLASS_REF);
}
@@ -887,7 +883,7 @@
} else {
setClass(n,Ptg.CLASS_VALUE);
}
- } else { //Array class, pg 16.
+ } else { //Array class, pg 16.
if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
(formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
setClass(n,Ptg.CLASS_VALUE);
@@ -896,9 +892,9 @@
}
}
}
-
+
private void setClass(Node n, byte theClass) {
- Ptg p = (Ptg) n.getValue();
+ Ptg p = n.getValue();
if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
p.setClass(theClass);
} else {
@@ -909,7 +905,7 @@
}
/**
* Convience method which takes in a list then passes it to the
- * other toFormulaString signature.
+ * other toFormulaString signature.
* @param book workbook for 3D and named references
* @param lptgs list of Ptg, can be null or empty
* @return a human readable String
@@ -930,9 +926,9 @@
* @return a human readable String
*/
public String toFormulaString(List lptgs) {
- return toFormulaString(book, lptgs);
+ return toFormulaString(book, lptgs);
}
-
+
/**
* Static method to convert an array of Ptgs in RPN order
* to a human readable string format in infix mode.
@@ -941,61 +937,74 @@
* @return a human readable String
*/
public static String toFormulaString(Workbook book, Ptg[] ptgs) {
- if (ptgs == null || ptgs.length == 0) return "#NAME";
- java.util.Stack stack = new java.util.Stack();
- AttrPtg ifptg = null;
+ if (ptgs == null || ptgs.length == 0) {
+ // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
+ return "#NAME";
+ }
+ Stack stack = new Stack();
// Excel allows to have AttrPtg at position 0 (such as Blanks) which
// do not have any operands. Skip them.
- stack.push(ptgs[0].toFormulaString(book));
-
- for (int i = 1; i < ptgs.length; i++) {
- if (! (ptgs[i] instanceof OperationPtg)) {
- stack.push(ptgs[i].toFormulaString(book));
+ int i;
+ if(ptgs[0] instanceof AttrPtg) {
+ AttrPtg attrPtg0 = (AttrPtg) ptgs[0];
+ if(attrPtg0.isSemiVolatile()) {
+ // no visible formula for semi-volatile
+ } else {
+ // TODO -this requirement is unclear and is not addressed by any junits
+ stack.push(ptgs[0].toFormulaString(book));
+ }
+ i=1;
+ } else {
+ i=0;
+ }
+
+ for ( ; i < ptgs.length; i++) {
+ Ptg ptg = ptgs[i];
+ // TODO - what about MemNoMemPtg?
+ if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
+ // marks the start of a list of area expressions which will be naturally combined
+ // by their trailing operators (e.g. UnionPtg)
+ // TODO - put comment and throw exception in toFormulaString() of these classes
continue;
}
-
- if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
- ifptg = (AttrPtg) ptgs[i];
+ if (! (ptg instanceof OperationPtg)) {
+ stack.push(ptg.toFormulaString(book));
continue;
}
-
- final OperationPtg o = (OperationPtg) ptgs[i];
- final String[] operands = new String[o.getNumberOfOperands()];
-
- for (int j = operands.length; j > 0; j--) {
- //TODO: catch stack underflow and throw parse exception.
- operands[j - 1] = (String) stack.pop();
- }
-
- stack.push(o.toFormulaString(operands));
- if (!(o instanceof AbstractFunctionPtg)) continue;
- final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
- final String fname = f.getName();
- if (fname == null) continue;
-
- if ((ifptg != null) && (fname.equals("specialflag"))) {
- // this special case will be way different.
- stack.push(ifptg.toFormulaString(new String[]{(String) stack.pop()}));
+ if (ptg instanceof AttrPtg && ((AttrPtg) ptg).isOptimizedIf()) {
continue;
- }
- if (fname.equals("externalflag")) {
- final String top = (String) stack.pop();
- final int paren = top.indexOf('(');
- final int comma = top.indexOf(',');
- if (comma == -1) {
- final int rparen = top.indexOf(')');
- stack.push(top.substring(paren + 1, rparen) + "()");
- }
- else {
- stack.push(top.substring(paren + 1, comma) + '(' +
- top.substring(comma + 1));
}
+
+ final OperationPtg o = (OperationPtg) ptg;
+ int nOperands = o.getNumberOfOperands();
+ final String[] operands = new String[nOperands];
+
+ for (int j = nOperands-1; j >= 0; j--) {
+ if(stack.isEmpty()) {
+ //TODO: write junit to prove this works
+ String msg = "Too few arguments suppled to operation token ("
+ + o.getClass().getName() + "). Expected (" + nOperands
+ + " but got " + (nOperands - j + 1);
+ throw new FormulaParseException(msg);
+ }
+ operands[j] = (String) stack.pop();
+ }
+ stack.push(o.toFormulaString(operands));
}
- }
- // TODO: catch stack underflow and throw parse exception.
- return (String) stack.pop();
+ if(stack.isEmpty()) {
+ // inspection of the code above reveals that every stack.pop() is followed by a
+ // stack.push(). So this is either an internal error or impossible.
+ throw new IllegalStateException("Stack underflow");
+ }
+ String result = (String) stack.pop();
+ if(!stack.isEmpty()) {
+ // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't
+ // put anything on the stack
+ throw new IllegalStateException("too much stuff left on the stack");
+ }
+ return result;
}
/**
* Static method to convert an array of Ptgs in RPN order
@@ -1005,7 +1014,7 @@
* @return a human readable String
*/
public String toFormulaString(Ptg[] ptgs) {
- return toFormulaString(book, ptgs);
+ return toFormulaString(book, ptgs);
}
@@ -1013,19 +1022,19 @@
*used to run the class(RVA) change algo
*/
private Node createTree() {
- java.util.Stack stack = new java.util.Stack();
+ Stack stack = new Stack();
int numPtgs = tokens.size();
OperationPtg o;
int numOperands;
Node[] operands;
for (int i=0;i<numPtgs;i++) {
if (tokens.get(i) instanceof OperationPtg) {
-
+
o = (OperationPtg) tokens.get(i);
numOperands = o.getNumberOfOperands();
operands = new Node[numOperands];
for (int j=0;j<numOperands;j++) {
- operands[numOperands-j-1] = (Node) stack.pop();
+ operands[numOperands-j-1] = (Node) stack.pop();
}
Node result = new Node(o);
result.setChildren(operands);
@@ -1036,7 +1045,7 @@
}
return (Node) stack.pop();
}
-
+
/** toString on the parser instance returns the RPN ordered list of tokens
* Useful for testing
*/
@@ -1045,21 +1054,21 @@
for (int i=0;i<tokens.size();i++) {
buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
buf.append(' ');
- }
+ }
return buf.toString();
}
-
-}
+
/** Private helper class, used to create a tree representation of the formula*/
- class Node {
+ private static final class Node {
private Ptg value=null;
private Node[] children=new Node[0];
private int numChild=0;
public Node(Ptg val) {
- value = val;
+ value = val;
}
public void setChildren(Node[] child) {children = child;numChild=child.length;}
public int getNumChildren() {return numChild;}
public Node getChild(int number) {return children[number];}
public Ptg getValue() {return value;}
}
+}
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/model/Workbook.java Sat Mar 8 03:49:00 2008
@@ -79,10 +79,8 @@
*/
protected SSTRecord sst = null;
- /**
- * Holds the Extern Sheet with references to bound sheets
- */
- protected ExternSheetRecord externSheet= null;
+
+ private LinkTable linkTable; // optionally occurs if there are references in the document. (4.10.3)
/**
* holds the "boundsheet" records (aka bundlesheet) so that they can have their
@@ -92,8 +90,6 @@
protected ArrayList formats = new ArrayList();
- protected ArrayList names = new ArrayList();
-
protected ArrayList hyperlinks = new ArrayList();
protected int numxfs = 0; // hold the number of extended format records
@@ -134,6 +130,7 @@
new Integer(recs.size()));
Workbook retval = new Workbook();
ArrayList records = new ArrayList(recs.size() / 3);
+ retval.records.setRecords(records);
int k;
for (k = 0; k < recs.size(); k++) {
@@ -192,21 +189,16 @@
retval.records.setBackuppos( k );
break;
case ExternSheetRecord.sid :
- if (log.check( POILogger.DEBUG ))
- log.log(DEBUG, "found extern sheet record at " + k);
- retval.externSheet = ( ExternSheetRecord ) rec;
- break;
+ throw new RuntimeException("Extern sheet is part of LinkTable");
case NameRecord.sid :
- if (log.check( POILogger.DEBUG ))
- log.log(DEBUG, "found name record at " + k);
- retval.names.add(rec);
- // retval.records.namepos = k;
- break;
+ throw new RuntimeException("DEFINEDNAME is part of LinkTable");
case SupBookRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found SupBook record at " + k);
+ retval.linkTable = new LinkTable(recs, k, retval.records);
// retval.records.supbookpos = k;
- break;
+ k+=retval.linkTable.getRecordCount() - 1;
+ continue;
case FormatRecord.sid :
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found format record at " + k);
@@ -262,8 +254,6 @@
break;
}
}
-
- retval.records.setRecords(records);
if (retval.windowOne == null) {
retval.windowOne = (WindowOneRecord) retval.createWindowOne();
@@ -283,6 +273,7 @@
log.log( DEBUG, "creating new workbook from scratch" );
Workbook retval = new Workbook();
ArrayList records = new ArrayList( 30 );
+ retval.records.setRecords(records);
ArrayList formats = new ArrayList( 8 );
records.add( retval.createBOF() );
@@ -339,8 +330,9 @@
records.add( retval.createStyle( k ) );
}
records.add( retval.createUseSelFS() );
- for ( int k = 0; k < 1; k++ )
- { // now just do 1
+
+ int nBoundSheets = 1; // now just do 1
+ for ( int k = 0; k < nBoundSheets; k++ ) {
BoundSheetRecord bsr =
(BoundSheetRecord) retval.createBoundSheet( k );
@@ -351,12 +343,14 @@
// retval.records.supbookpos = retval.records.bspos + 1;
// retval.records.namepos = retval.records.supbookpos + 2;
records.add( retval.createCountry() );
+ for ( int k = 0; k < nBoundSheets; k++ ) {
+ retval.getOrCreateLinkTable().checkExternSheet(k);
+ }
retval.sst = (SSTRecord) retval.createSST();
records.add( retval.sst );
records.add( retval.createExtendedSST() );
records.add( retval.createEOF() );
- retval.records.setRecords(records);
if (log.check( POILogger.DEBUG ))
log.log( DEBUG, "exit create new workbook from scratch" );
return retval;
@@ -369,36 +363,20 @@
* @param sheetIndex Index to match
* @return null if no builtin NameRecord matches
*/
- public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
- {
- Iterator iterator = names.iterator();
- while (iterator.hasNext()) {
- NameRecord record = ( NameRecord ) iterator.next();
-
- //print areas are one based
- if (record.getBuiltInName() == name && record.getIndexToSheet() == sheetIndex) {
- return record;
- }
- }
-
- return null;
-
- }
+ public NameRecord getSpecificBuiltinRecord(byte name, int sheetIndex)
+ {
+ return getOrCreateLinkTable().getSpecificBuiltinRecord(name, sheetIndex);
+ }
/**
* Removes the specified Builtin NameRecord that matches the name and index
* @param name byte representation of the builtin to match
* @param sheetIndex zero-based sheet reference
*/
- public void removeBuiltinRecord(byte name, int sheetIndex) {
- //the name array is smaller so searching through it should be faster than
- //using the findFirstXXXX methods
- NameRecord record = getSpecificBuiltinRecord(name, sheetIndex);
- if (record != null) {
- names.remove(record);
- }
-
- }
+ public void removeBuiltinRecord(byte name, int sheetIndex) {
+ linkTable.removeBuiltinRecord(name, sheetIndex);
+ // TODO - do we need "this.records.remove(...);" similar to that in this.removeName(int namenum) {}?
+ }
public int getNumRecords() {
return records.size();
@@ -614,6 +592,7 @@
records.add(records.getBspos()+1, bsr);
records.setBspos( records.getBspos() + 1 );
boundsheets.add(bsr);
+ getOrCreateLinkTable().checkExternSheet(sheetnum);
fixTabIdRecord();
}
}
@@ -1824,14 +1803,26 @@
protected Record createEOF() {
return new EOFRecord();
}
+
+ /**
+ * lazy initialization
+ * Note - creating the link table causes creation of 1 EXTERNALBOOK and 1 EXTERNALSHEET record
+ */
+ private LinkTable getOrCreateLinkTable() {
+ if(linkTable == null) {
+ linkTable = new LinkTable((short) getNumSheets(), records);
+ }
+ return linkTable;
+ }
public SheetReferences getSheetReferences() {
SheetReferences refs = new SheetReferences();
- if (externSheet != null) {
- for (int k = 0; k < externSheet.getNumOfREFStructures(); k++) {
+ if (linkTable != null) {
+ int numRefStructures = linkTable.getNumberOfREFStructures();
+ for (short k = 0; k < numRefStructures; k++) {
- String sheetName = findSheetNameFromExternSheet((short)k);
+ String sheetName = findSheetNameFromExternSheet(k);
refs.addSheetReference(sheetName, k);
}
@@ -1846,7 +1837,8 @@
public String findSheetNameFromExternSheet(short num){
String result="";
- short indexToSheet = externSheet.getREFRecordAt(num).getIndexToFirstSupBook();
+ short indexToSheet = linkTable.getIndexToSheet(num);
+
if (indexToSheet>-1) { //error check, bail out gracefully!
result = getSheetName(indexToSheet);
}
@@ -1861,10 +1853,7 @@
*/
public int getSheetIndexFromExternSheetIndex(int externSheetNumber)
{
- if (externSheetNumber >= externSheet.getNumOfREFStructures())
- return -1;
- else
- return externSheet.getREFRecordAt(externSheetNumber).getIndexToFirstSupBook();
+ return linkTable.getSheetIndexFromExternSheetIndex(externSheetNumber);
}
/** returns the extern sheet number for specific sheet number ,
@@ -1873,58 +1862,17 @@
* @return index to extern sheet
*/
public short checkExternSheet(int sheetNumber){
-
- int i = 0;
- boolean flag = false;
- short result = 0;
-
- if (externSheet == null) {
- externSheet = createExternSheet();
- }
-
- //Trying to find reference to this sheet
- while (i < externSheet.getNumOfREFStructures() && !flag){
- ExternSheetSubRecord record = externSheet.getREFRecordAt(i);
-
- if (record.getIndexToFirstSupBook() == sheetNumber &&
- record.getIndexToLastSupBook() == sheetNumber){
- flag = true;
- result = (short) i;
- }
-
- ++i;
- }
-
- //We Havent found reference to this sheet
- if (!flag) {
- result = addSheetIndexToExternSheet((short) sheetNumber);
- }
-
- return result;
- }
-
- private short addSheetIndexToExternSheet(short sheetNumber){
- short result;
-
- ExternSheetSubRecord record = new ExternSheetSubRecord();
- record.setIndexToFirstSupBook(sheetNumber);
- record.setIndexToLastSupBook(sheetNumber);
- externSheet.addREFRecord(record);
- externSheet.setNumOfREFStructures((short)(externSheet.getNumOfREFStructures() + 1));
- result = (short)(externSheet.getNumOfREFStructures() - 1);
-
- return result;
+ return getOrCreateLinkTable().checkExternSheet(sheetNumber);
}
-
-
/** gets the total number of names
* @return number of names
*/
public int getNumNames(){
- int result = names.size();
-
- return result;
+ if(linkTable == null) {
+ return 0;
+ }
+ return linkTable.getNumNames();
}
/** gets the name record
@@ -1932,28 +1880,14 @@
* @return name record
*/
public NameRecord getNameRecord(int index){
- NameRecord result = (NameRecord) names.get(index);
-
- return result;
-
+ return linkTable.getNameRecord(index);
}
/** creates new name
* @return new name record
*/
public NameRecord createName(){
-
- NameRecord name = new NameRecord();
-
- // Not the most efficient way but the other way was causing too many bugs
- int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
-
- records.add(idx+names.size()+1, name);
- names.add(name);
-
- return name;
+ return addName(new NameRecord());
}
@@ -1962,67 +1896,41 @@
*/
public NameRecord addName(NameRecord name)
{
- // Not the most efficient way but the other way was causing too many bugs
- int idx = findFirstRecordLocBySid(ExternSheetRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(SupBookRecord.sid);
- if (idx == -1) idx = findFirstRecordLocBySid(CountryRecord.sid);
- records.add(idx+names.size()+1, name);
- names.add(name);
+
+ getOrCreateLinkTable().addName(name);
return name;
}
- /**Generates a NameRecord to represent a built-in region
- * @return a new NameRecord unless the index is invalid
- */
- public NameRecord createBuiltInName(byte builtInName, int index)
- {
- if (index == -1 || index+1 > (int)Short.MAX_VALUE)
- throw new IllegalArgumentException("Index is not valid ["+index+"]");
-
- NameRecord name = new NameRecord(builtInName, (short)(index));
-
- addName(name);
-
- return name;
- }
+ /**Generates a NameRecord to represent a built-in region
+ * @return a new NameRecord unless the index is invalid
+ */
+ public NameRecord createBuiltInName(byte builtInName, int index)
+ {
+ if (index == -1 || index+1 > Short.MAX_VALUE)
+ throw new IllegalArgumentException("Index is not valid ["+index+"]");
+
+ NameRecord name = new NameRecord(builtInName, (short)(index));
+
+ addName(name);
+
+ return name;
+ }
/** removes the name
* @param namenum name index
*/
public void removeName(int namenum){
- if (names.size() > namenum) {
+
+ if (linkTable.getNumNames() > namenum) {
int idx = findFirstRecordLocBySid(NameRecord.sid);
records.remove(idx + namenum);
- names.remove(namenum);
+ linkTable.removeName(namenum);
}
}
- /** creates a new extern sheet record
- * @return the new extern sheet record
- */
- protected ExternSheetRecord createExternSheet(){
- ExternSheetRecord externSheet = new ExternSheetRecord();
-
- int idx = findFirstRecordLocBySid(CountryRecord.sid);
-
- records.add(idx+1, externSheet);
-// records.add(records.supbookpos + 1 , rec);
-
- //We also adds the supBook for internal reference
- SupBookRecord supbook = new SupBookRecord();
-
- supbook.setNumberOfSheets((short)getNumSheets());
- //supbook.setFlag();
-
- records.add(idx+1, supbook);
-// records.add(records.supbookpos + 1 , supbook);
-
- return externSheet;
- }
-
/**
* Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat.
* @param format the format string
@@ -2392,6 +2300,17 @@
}
return this.fileShare;
}
+
+ /**
+ * is the workbook protected with a password (not encrypted)?
+ */
+ public boolean isWriteProtected() {
+ if (this.fileShare == null) {
+ return false;
+ }
+ FileSharingRecord frec = getFileSharing();
+ return (frec.getReadOnly() == 1);
+ }
/**
* protect a workbook with a password (not encypted, just sets writeprotect
@@ -2419,5 +2338,14 @@
writeProtect = null;
}
+ /**
+ * @param refIndex Index to REF entry in EXTERNSHEET record in the Link Table
+ * @param definedNameIndex zero-based to DEFINEDNAME or EXTERNALNAME record
+ * @return the string representation of the defined or external name
+ */
+ public String resolveNameXText(int refIndex, int definedNameIndex) {
+ return linkTable.resolveNameXText(refIndex, definedNameIndex);
+ }
}
+
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/DVALRecord.java Sat Mar 8 03:49:00 2008
@@ -1,4 +1,3 @@
-
/* ====================================================================
Copyright 2002-2004 Apache Software Foundation
@@ -20,13 +19,11 @@
import org.apache.poi.util.LittleEndian;
/**
- * Title: DVAL Record<P>
+ * Title: DATAVALIDATIONS Record<P>
* Description: used in data validation ;
- * This record is the list header of all data validation records in the current sheet.
+ * This record is the list header of all data validation records (0x01BE) in the current sheet.
* @author Dragos Buleandra (dragos.buleandra@trade2b.ro)
- * @version 2.0-pre
*/
-
public class DVALRecord extends Record
{
public final static short sid = 0x01B2;
@@ -41,13 +38,14 @@
/** Object ID of the drop down arrow object for list boxes ;
* in our case this will be always FFFF , until
* MSODrawingGroup and MSODrawing records are implemented */
- private int field_cbo_id = 0xFFFFFFFF;
+ private int field_cbo_id;
/** Number of following DV Records */
- private int field_5_dv_no = 0x00000000;
+ private int field_5_dv_no;
- public DVALRecord()
- {
+ public DVALRecord() {
+ field_cbo_id = 0xFFFFFFFF;
+ field_5_dv_no = 0x00000000;
}
/**
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/FileSharingRecord.java Sat Mar 8 03:49:00 2008
@@ -19,6 +19,8 @@
package org.apache.poi.hssf.record;
import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
import org.apache.poi.util.StringUtil;
/**
@@ -29,6 +31,8 @@
*/
public class FileSharingRecord extends Record {
+ private static POILogger logger = POILogFactory.getLogger(FileSharingRecord.class);
+
public final static short sid = 0x5b;
private short field_1_readonly;
private short field_2_password;
@@ -58,8 +62,23 @@
field_1_readonly = in.readShort();
field_2_password = in.readShort();
field_3_username_length = in.readByte();
+
+ // Is this really correct? The latest docs
+ // seem to hint there's nothing between the
+ // username length and the username string
field_4_unknown = in.readShort();
- field_5_username = in.readCompressedUnicode(field_3_username_length);
+
+ // Ensure we don't try to read more data than
+ // there actually is
+ if(field_3_username_length > in.remaining()) {
+ logger.log(POILogger.WARN, "FileSharingRecord defined a username of length " + field_3_username_length + ", but only " + in.remaining() + " bytes were left, truncating");
+ field_3_username_length = (byte)in.remaining();
+ }
+ if(field_3_username_length > 0) {
+ field_5_username = in.readCompressedUnicode(field_3_username_length);
+ } else {
+ field_5_username = "";
+ }
}
//this is the world's lamest "security". thanks to Wouter van Vugt for making me
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/NameRecord.java Sat Mar 8 03:49:00 2008
@@ -726,7 +726,7 @@
for(int i=0; i<refs.length; i++) {
ptg = new Area3DPtg();
((Area3DPtg) ptg).setExternSheetIndex(externSheetIndex);
- ((Area3DPtg) ptg).setArea(refs[i].toString());
+ ((Area3DPtg) ptg).setArea(refs[i].formatAsString());
field_13_name_definition.push(ptg);
this.setDefinitionTextLength( (short)(getDefinitionLength() + ptg.getSize()) );
}
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordFactory.java Sat Mar 8 03:49:00 2008
@@ -77,7 +77,11 @@
NoteRecord.class, ObjectProtectRecord.class, ScenarioProtectRecord.class,
FileSharingRecord.class, ChartTitleFormatRecord.class,
DVRecord.class, DVALRecord.class, UncalcedRecord.class,
- HyperlinkRecord.class
+ HyperlinkRecord.class,
+ ExternalNameRecord.class, // TODO - same changes in non-@deprecated version of this class
+ SupBookRecord.class,
+ CRNCountRecord.class,
+ CRNRecord.class,
};
}
private static Map recordsMap = recordsToMap(records);
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/RecordInputStream.java Sat Mar 8 03:49:00 2008
@@ -267,7 +267,7 @@
public String readCompressedUnicode(int length) {
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
- throw new IllegalArgumentException("Illegal length");
+ throw new IllegalArgumentException("Illegal length " + length);
}
StringBuffer buf = new StringBuffer(length);
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/SharedFormulaRecord.java Sat Mar 8 03:49:00 2008
@@ -1,4 +1,3 @@
-
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -15,8 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-
-
+
package org.apache.poi.hssf.record;
import java.util.Stack;
@@ -34,10 +32,7 @@
* record types.
* @author Danny Mui at apache dot org
*/
-
-public class SharedFormulaRecord
- extends Record
-{
+public final class SharedFormulaRecord extends Record {
public final static short sid = 0x4BC;
private int field_1_first_row;
@@ -58,15 +53,15 @@
public SharedFormulaRecord(RecordInputStream in)
{
- super(in);
+ super(in);
}
protected void validateSid(short id)
{
- if (id != this.sid)
- {
- throw new RecordFormatException("Not a valid SharedFormula");
- }
+ if (id != this.sid)
+ {
+ throw new RecordFormatException("Not a valid SharedFormula");
+ }
}
public int getFirstRow() {
@@ -96,14 +91,14 @@
public int serialize(int offset, byte [] data)
{
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
+ //Because this record is converted to individual Formula records, this method is not required.
+ throw new UnsupportedOperationException("Cannot serialize a SharedFormulaRecord");
}
public int getRecordSize()
{
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
+ //Because this record is converted to individual Formula records, this method is not required.
+ throw new UnsupportedOperationException("Cannot get the size for a SharedFormulaRecord");
}
@@ -257,39 +252,40 @@
}
}
- private short fixupRelativeColumn(int currentcolumn, short column, boolean relative) {
- if(relative) {
- if((column&128)!=0) column=(short)(column-256);
- column+=currentcolumn;
- }
- return column;
- }
-
- private short fixupRelativeRow(int currentrow, short row, boolean relative) {
- if(relative) {
- row+=currentrow;
- }
- return row;
- }
-
- /**
- * Mirroring formula records so it is registered in the ValueRecordsAggregate
- */
- public boolean isInValueSection()
- {
- return true;
- }
-
-
- /**
- * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
- */
- public boolean isValue() {
- return true;
- }
+ private int fixupRelativeColumn(int currentcolumn, int column, boolean relative) {
+ if(relative) {
+ // mask out upper bits to produce 'wrapping' at column 256 ("IV")
+ return (column + currentcolumn) & 0x00FF;
+ }
+ return column;
+ }
+
+ private int fixupRelativeRow(int currentrow, int row, boolean relative) {
+ if(relative) {
+ // mask out upper bits to produce 'wrapping' at row 65536
+ return (row+currentrow) & 0x00FFFF;
+ }
+ return row;
+ }
+
+ /**
+ * Mirroring formula records so it is registered in the ValueRecordsAggregate
+ */
+ public boolean isInValueSection()
+ {
+ return true;
+ }
+
+
+ /**
+ * Register it in the ValueRecordsAggregate so it can go into the FormulaRecordAggregate
+ */
+ public boolean isValue() {
+ return true;
+ }
public Object clone() {
- //Because this record is converted to individual Formula records, this method is not required.
- throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
+ //Because this record is converted to individual Formula records, this method is not required.
+ throw new UnsupportedOperationException("Cannot clone a SharedFormulaRecord");
}
}
Modified: poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java
URL: http://svn.apache.org/viewvc/poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java?rev=634936&r1=634935&r2=634936&view=diff
==============================================================================
--- poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java (original)
+++ poi/branches/ooxml/src/java/org/apache/poi/hssf/record/StyleRecord.java Sat Mar 8 03:49:00 2008
@@ -88,13 +88,18 @@
else if (getType() == STYLE_USER_DEFINED)
{
field_2_name_length = in.readShort();
- field_3_string_options = in.readByte();
- byte[] string = in.readRemainder();
- if (fHighByte.isSet(field_3_string_options)) {
- field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
- }else {
- field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
+ // Some files from Crystal Reports lack
+ // the remaining fields, which is naughty
+ if(in.remaining() > 0) {
+ field_3_string_options = in.readByte();
+
+ byte[] string = in.readRemainder();
+ if (fHighByte.isSet(field_3_string_options)) {
+ field_4_name= StringUtil.getFromUnicodeBE(string, 0, field_2_name_length);
+ } else {
+ field_4_name=StringUtil.getFromCompressedUnicode(string, 0, field_2_name_length);
+ }
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@poi.apache.org
For additional commands, e-mail: commits-help@poi.apache.org