| |
| /* ==================================================================== |
| * The Apache Software License, Version 1.1 |
| * |
| * Copyright (c) 2003, 2003 The Apache Software Foundation. All rights |
| * reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * 3. The end-user documentation included with the redistribution, |
| * if any, must include the following acknowledgment: |
| * "This product includes software developed by the |
| * Apache Software Foundation (http://www.apache.org/)." |
| * Alternately, this acknowledgment may appear in the software itself, |
| * if and wherever such third-party acknowledgments normally appear. |
| * |
| * 4. The names "Apache" and "Apache Software Foundation" and |
| * "Apache POI" must not be used to endorse or promote products |
| * derived from this software without prior written permission. For |
| * written permission, please contact apache@apache.org. |
| * |
| * 5. Products derived from this software may not be called "Apache", |
| * "Apache POI", nor may "Apache" appear in their name, without |
| * prior written permission of the Apache Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR |
| * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF |
| * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| * ==================================================================== |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals on behalf of the Apache Software Foundation. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| |
| package org.apache.poi.hssf.model; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| //import PTG's .. since we need everything, import * |
| import org.apache.poi.hssf.record.formula.*; |
| |
| |
| |
| /** |
| * This class parses a formula string into a List of tokens in RPN order. |
| * Inspired by |
| * Lets Build a Compiler, by Jack Crenshaw |
| * BNF for the formula expression is : |
| * <expression> ::= <term> [<addop> <term>]* |
| * <term> ::= <factor> [ <mulop> <factor> ]* |
| * <factor> ::= <number> | (<expression>) | <cellRef> | <function> |
| * <function> ::= <functionName> ([expression [, expression]*]) |
| * |
| * @author Avik Sengupta <avik AT Avik Sengupta DOT com> |
| * @author Andrew C. oliver (acoliver at apache dot org) |
| * @author Eric Ladner (eladner at goldinc dot com) |
| * @author Cameron Riley (criley at ekmail.com) |
| */ |
| public class FormulaParser { |
| |
| 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 Stack tokens = new java.util.Stack(); |
| private List result = new ArrayList(); |
| private int numParen; |
| |
| private static char TAB = '\t'; |
| private static char CR = '\n'; |
| |
| private char look; // Lookahead Character |
| private boolean inFunction = false; |
| |
| private Workbook book; |
| |
| |
| /** create the parser with the string that is to be parsed |
| * 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 |
| */ |
| public FormulaParser(String formula, Workbook book){ |
| formulaString = formula; |
| pointer=0; |
| this.book = book; |
| formulaLength = formulaString.length(); |
| } |
| |
| |
| /** 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) { |
| look = (char)0; |
| return; |
| } |
| look=formulaString.charAt(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); |
| } |
| |
| |
| |
| /** Report What Was Expected */ |
| private void Expected(String s) { |
| Abort(s + " Expected"); |
| } |
| |
| |
| |
| /** Recognize an Alpha Character */ |
| private boolean IsAlpha(char c) { |
| return Character.isLetter(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() { |
| while (IsWhite(look)) { |
| GetChar(); |
| } |
| } |
| |
| |
| |
| /** Match a Specific Input Character */ |
| private void Match(char x) { |
| if (look != x) { |
| Expected("" + x + ""); |
| }else { |
| GetChar(); |
| SkipWhite(); |
| } |
| } |
| |
| /** Get an Identifier */ |
| private String GetName() { |
| StringBuffer Token = new StringBuffer(); |
| if (!IsAlpha(look)) { |
| Expected("Name"); |
| } |
| while (IsAlNum(look)) { |
| Token = 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(); |
| } |
| return Token.toString(); |
| } |
| |
| |
| /** Get a Number */ |
| private String GetNum() { |
| String Value =""; |
| if (!IsDigit(look)) Expected("Integer"); |
| while (IsDigit(look)){ |
| Value = Value + look; |
| GetChar(); |
| } |
| SkipWhite(); |
| return Value; |
| } |
| |
| /** 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() { |
| String name; |
| name = GetName(); |
| if (look == '('){ |
| //This is a function |
| function(name); |
| } else if (look == ':') { // this is a AreaReference |
| String first = name; |
| Match(':'); |
| String second = GetName(); |
| tokens.add(new AreaPtg(first+":"+second)); |
| } else if (look == '!') { |
| Match('!'); |
| String sheetName = name; |
| String first = GetName(); |
| short externIdx = book.checkExternSheet(book.getSheetIndex(sheetName)); |
| if (look == ':') { |
| Match(':'); |
| String second=GetName(); |
| |
| tokens.add(new Area3DPtg(first+":"+second,externIdx)); |
| } else { |
| tokens.add(new Ref3DPtg(first,externIdx)); |
| } |
| } else { |
| //this can be either a cell ref or a named range !! |
| boolean cellRef = true ; //we should probably do it with reg exp?? |
| boolean boolLit = (name.equals("TRUE") || name.equals("FALSE")); |
| if (boolLit) { |
| tokens.add(new BoolPtg(name)); |
| } else if (cellRef) { |
| tokens.add(new ReferencePtg(name)); |
| }else { |
| //handle after named range is integrated!! |
| } |
| } |
| } |
| |
| /** |
| * 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 function(String name) { |
| //average 2 args per function |
| this.functionTokens.add(0, new ArrayList(2)); |
| |
| Match('('); |
| int numArgs = Arguments(); |
| 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); |
| } |
| |
| /** |
| * Adds the size of all the ptgs after the provided index (inclusive). |
| * <p> |
| * Initially used to count a goto |
| * @param index |
| * @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; |
| } |
| |
| private int getPtgSize(int start, int end) { |
| int count = 0; |
| int index = start; |
| Iterator ptgIterator = tokens.listIterator(index); |
| while (ptgIterator.hasNext() && index <= end) { |
| Ptg ptg = (Ptg)ptgIterator.next(); |
| 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 |
| * @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); |
| } |
| |
| return retval; |
| } |
| |
| /** get arguments to a function */ |
| private int Arguments() { |
| int numArgs = 0; |
| if (look != ')') { |
| numArgs++; |
| Expression(); |
| addArgumentPointer(); |
| } |
| while (look == ',' || look == ';') { //TODO handle EmptyArgs |
| if(look == ',') { |
| Match(','); |
| } |
| else { |
| Match(';'); |
| } |
| Expression(); |
| addArgumentPointer(); |
| numArgs++; |
| } |
| return numArgs; |
| } |
| |
| /** Parse and Translate a Math Factor */ |
| private void Factor() { |
| if (look == '(' ) { |
| Match('('); |
| Expression(); |
| Match(')'); |
| tokens.add(new ParenthesisPtg()); |
| } else if (IsAlpha(look)){ |
| Ident(); |
| } else if(look == '"') { |
| StringLiteral(); |
| } else { |
| |
| String number = GetNum(); |
| if (look=='.') { |
| Match('.'); |
| String decimalPart = null; |
| if (IsDigit(look)) number = number +"."+ GetNum(); //this also takes care of someone entering "1234." |
| tokens.add(new NumberPtg(number)); |
| } else { |
| tokens.add(new IntPtg(number)); //TODO:what if the number is too big to be a short? ..add factory to return Int or Number! |
| } |
| } |
| } |
| |
| private void StringLiteral() { |
| Match('"'); |
| 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()); |
| |
| } |
| |
| |
| /** Parse and Translate a Math Term */ |
| private void Term(){ |
| Factor(); |
| while (look == '*' || look == '/' || look == '^' || look == '&') { |
| |
| ///TODO do we need to do anything here?? |
| if (look == '*') Multiply(); |
| else if (look == '/') Divide(); |
| else if (look == '^') Power(); |
| else if (look == '&') Concat(); |
| } |
| } |
| |
| /** Recognize and Translate an Add */ |
| private void Add() { |
| Match('+'); |
| Term(); |
| tokens.add(new AddPtg()); |
| } |
| |
| /** Recognize and Translate a Concatination */ |
| private void Concat() { |
| Match('&'); |
| Term(); |
| tokens.add(new ConcatPtg()); |
| } |
| |
| /** Recognize and Translate a test for Equality */ |
| private void Equal() { |
| Match('='); |
| Expression(); |
| tokens.add(new EqualPtg()); |
| } |
| |
| /** Recognize and Translate a Subtract */ |
| private void Subtract() { |
| Match('-'); |
| Term(); |
| tokens.add(new SubtractPtg()); |
| } |
| |
| private void Power() { |
| Match('^'); |
| Term(); |
| tokens.add(new PowerPtg()); |
| } |
| |
| |
| /** Parse and Translate an Expression */ |
| private void Expression() { |
| if (IsAddop(look)) { |
| EmitLn("CLR D0"); //unaryAdd ptg??? |
| } else { |
| Term(); |
| } |
| while (IsAddop(look)) { |
| if (look == '+' ) Add(); |
| else if (look == '-') Subtract(); |
| } |
| |
| /* |
| * 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 } |
| /** |
| procedure Assignment; |
| var Name: string[8]; |
| begin |
| Name := GetName; |
| Match('='); |
| Expression; |
| |
| end; |
| **/ |
| |
| |
| /** Initialize */ |
| |
| private void init() { |
| GetChar(); |
| SkipWhite(); |
| } |
| |
| /** API call to execute the parsing of the formula |
| * |
| */ |
| public void parse() { |
| synchronized (tokens) { |
| init(); |
| Expression(); |
| } |
| } |
| |
| |
| /********************************* |
| * PARSER IMPLEMENTATION ENDS HERE |
| * EXCEL SPECIFIC METHODS BELOW |
| *******************************/ |
| |
| /** 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(); |
| if (formulaType == this.FORMULA_TYPE_NAMEDRANGE) { |
| if (p.getDefaultOperandClass() == Ptg.CLASS_REF) { |
| setClass(n,Ptg.CLASS_REF); |
| } else { |
| setClass(n,Ptg.CLASS_ARRAY); |
| } |
| } else { |
| setClass(n,Ptg.CLASS_VALUE); |
| } |
| |
| } |
| |
| private void setParameterRVA(Node n, int formulaType) { |
| Ptg p = (Ptg) n.getValue(); |
| if (p instanceof AbstractFunctionPtg) { |
| int numOperands = n.getNumChildren(); |
| for (int i =0;i<n.getNumChildren();i++) { |
| setParameterRVA(n.getChild(i),((AbstractFunctionPtg)p).getParameterClass(i),formulaType); |
| if (n.getChild(i).getValue() instanceof AbstractFunctionPtg) { |
| setParameterRVA(n.getChild(i),formulaType); |
| } |
| } |
| } else { |
| for (int i =0;i<n.getNumChildren();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 |
| if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) { |
| setClass(n, Ptg.CLASS_REF); |
| } |
| if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE) { |
| if (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED) { |
| setClass(n,Ptg.CLASS_VALUE); |
| } else { |
| setClass(n,Ptg.CLASS_ARRAY); |
| } |
| } |
| if (p.getDefaultOperandClass() == Ptg.CLASS_ARRAY ) { |
| setClass(n, Ptg.CLASS_ARRAY); |
| } |
| } else if (expectedClass == Ptg.CLASS_VALUE) { //pg 15, table 2 |
| if (formulaType == FORMULA_TYPE_NAMEDRANGE) { |
| setClass(n,Ptg.CLASS_ARRAY) ; |
| } else { |
| setClass(n,Ptg.CLASS_VALUE); |
| } |
| } else { //Array class, pg 16. |
| if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE && |
| (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) { |
| setClass(n,Ptg.CLASS_VALUE); |
| } else { |
| setClass(n,Ptg.CLASS_ARRAY); |
| } |
| } |
| } |
| |
| private void setClass(Node n, byte theClass) { |
| Ptg p = (Ptg) n.getValue(); |
| if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) { |
| p.setClass(theClass); |
| } else { |
| for (int i =0;i<n.getNumChildren();i++) { |
| setClass(n.getChild(i),theClass); |
| } |
| } |
| } |
| /** |
| * Convience method which takes in a list then passes it to the 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 |
| */ |
| public static String toFormulaString(Workbook book, List lptgs) { |
| String retval = null; |
| if (lptgs == null || lptgs.size() == 0) return "#NAME"; |
| Ptg[] ptgs = new Ptg[lptgs.size()]; |
| ptgs = (Ptg[])lptgs.toArray(ptgs); |
| retval = toFormulaString(book, ptgs); |
| return retval; |
| } |
| |
| /** |
| * Static method to convert an array of Ptgs in RPN order |
| * to a human readable string format in infix mode. |
| * @param book workbook for named and 3D references |
| * @param ptgs array of Ptg, can be null or empty |
| * @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; |
| |
| // 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)); |
| continue; |
| } |
| |
| if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) { |
| ifptg = (AttrPtg) ptgs[i]; |
| 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()})); |
| 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)); |
| } |
| } |
| } |
| // TODO: catch stack underflow and throw parse exception. |
| return (String) stack.pop(); |
| } |
| |
| |
| /** Create a tree representation of the RPN token array |
| *used to run the class(RVA) change algo |
| */ |
| private Node createTree() { |
| java.util.Stack stack = new java.util.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(); |
| } |
| Node result = new Node(o); |
| result.setChildren(operands); |
| stack.push(result); |
| } else { |
| stack.push(new Node((Ptg)tokens.get(i))); |
| } |
| } |
| return (Node) stack.pop(); |
| } |
| |
| /** toString on the parser instance returns the RPN ordered list of tokens |
| * Useful for testing |
| */ |
| public String toString() { |
| StringBuffer buf = new StringBuffer(); |
| 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 Ptg value=null; |
| private Node[] children=new Node[0]; |
| private int numChild=0; |
| public Node(Ptg 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;} |
| } |