0001: /* ====================================================================
0002: Licensed to the Apache Software Foundation (ASF) under one or more
0003: contributor license agreements. See the NOTICE file distributed with
0004: this work for additional information regarding copyright ownership.
0005: The ASF licenses this file to You under the Apache License, Version 2.0
0006: (the "License"); you may not use this file except in compliance with
0007: the License. You may obtain a copy of the License at
0008:
0009: http://www.apache.org/licenses/LICENSE-2.0
0010:
0011: Unless required by applicable law or agreed to in writing, software
0012: distributed under the License is distributed on an "AS IS" BASIS,
0013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: See the License for the specific language governing permissions and
0015: limitations under the License.
0016: ==================================================================== */
0017:
0018: package org.apache.poi.hssf.model;
0019:
0020: import java.util.ArrayList;
0021: import java.util.Iterator;
0022: import java.util.LinkedList;
0023: import java.util.List;
0024:
0025: //import PTG's .. since we need everything, import *
0026: import org.apache.poi.hssf.record.formula.*;
0027:
0028: /**
0029: * This class parses a formula string into a List of tokens in RPN order.
0030: * Inspired by
0031: * Lets Build a Compiler, by Jack Crenshaw
0032: * BNF for the formula expression is :
0033: * <expression> ::= <term> [<addop> <term>]*
0034: * <term> ::= <factor> [ <mulop> <factor> ]*
0035: * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
0036: * <function> ::= <functionName> ([expression [, expression]*])
0037: *
0038: * @author Avik Sengupta <avik at apache dot org>
0039: * @author Andrew C. oliver (acoliver at apache dot org)
0040: * @author Eric Ladner (eladner at goldinc dot com)
0041: * @author Cameron Riley (criley at ekmail.com)
0042: * @author Peter M. Murray (pete at quantrix dot com)
0043: * @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
0044: */
0045: public class FormulaParser {
0046:
0047: public static int FORMULA_TYPE_CELL = 0;
0048: public static int FORMULA_TYPE_SHARED = 1;
0049: public static int FORMULA_TYPE_ARRAY = 2;
0050: public static int FORMULA_TYPE_CONDFOMRAT = 3;
0051: public static int FORMULA_TYPE_NAMEDRANGE = 4;
0052:
0053: private String formulaString;
0054: private int pointer = 0;
0055: private int formulaLength;
0056:
0057: private List tokens = new java.util.Stack();
0058:
0059: /**
0060: * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded.
0061: */
0062: private List functionTokens = new LinkedList();
0063:
0064: private static char TAB = '\t';
0065: private static char CR = '\n';
0066:
0067: private char look; // Lookahead Character
0068:
0069: private Workbook book;
0070:
0071: /**
0072: * Create the formula parser, with the string that is to be
0073: * parsed against the supplied workbook.
0074: * A later call the parse() method to return ptg list in
0075: * rpn order, then call the getRPNPtg() to retrive the
0076: * parse results.
0077: * This class is recommended only for single threaded use.
0078: *
0079: * If you only have a usermodel.HSSFWorkbook, and not a
0080: * model.Workbook, then use the convenience method on
0081: * usermodel.HSSFFormulaEvaluator
0082: */
0083: public FormulaParser(String formula, Workbook book) {
0084: formulaString = formula;
0085: pointer = 0;
0086: this .book = book;
0087: formulaLength = formulaString.length();
0088: }
0089:
0090: /** Read New Character From Input Stream */
0091: private void GetChar() {
0092: // Check to see if we've walked off the end of the string.
0093: // Just return if so and reset Look to smoething to keep
0094: // SkipWhitespace from spinning
0095: if (pointer == formulaLength) {
0096: look = (char) 0;
0097: return;
0098: }
0099: look = formulaString.charAt(pointer++);
0100: //System.out.println("Got char: "+ look);
0101: }
0102:
0103: /** Report an Error */
0104: private void Error(String s) {
0105: System.out.println("Error: " + s);
0106: }
0107:
0108: /** Report Error and Halt */
0109: private void Abort(String s) {
0110: Error(s);
0111: //System.exit(1); //throw exception??
0112: throw new RuntimeException("Cannot Parse, sorry : " + s + " @ "
0113: + pointer + " [Formula String was: '" + formulaString
0114: + "']");
0115: }
0116:
0117: /** Report What Was Expected */
0118: private void Expected(String s) {
0119: Abort(s + " Expected");
0120: }
0121:
0122: /** Recognize an Alpha Character */
0123: private boolean IsAlpha(char c) {
0124: return Character.isLetter(c) || c == '$' || c == '_';
0125: }
0126:
0127: /** Recognize a Decimal Digit */
0128: private boolean IsDigit(char c) {
0129: //System.out.println("Checking digit for"+c);
0130: return Character.isDigit(c);
0131: }
0132:
0133: /** Recognize an Alphanumeric */
0134: private boolean IsAlNum(char c) {
0135: return (IsAlpha(c) || IsDigit(c));
0136: }
0137:
0138: /** Recognize an Addop */
0139: private boolean IsAddop(char c) {
0140: return (c == '+' || c == '-');
0141: }
0142:
0143: /** Recognize White Space */
0144: private boolean IsWhite(char c) {
0145: return (c == ' ' || c == TAB);
0146: }
0147:
0148: /**
0149: * Determines special characters;primarily in use for definition of string literals
0150: * @param c
0151: * @return boolean
0152: */
0153: private boolean IsSpecialChar(char c) {
0154: return (c == '>' || c == '<' || c == '=' || c == '&'
0155: || c == '[' || c == ']');
0156: }
0157:
0158: /** Skip Over Leading White Space */
0159: private void SkipWhite() {
0160: while (IsWhite(look)) {
0161: GetChar();
0162: }
0163: }
0164:
0165: /** Match a Specific Input Character */
0166: private void Match(char x) {
0167: if (look != x) {
0168: Expected("" + x + "");
0169: } else {
0170: GetChar();
0171: SkipWhite();
0172: }
0173: }
0174:
0175: /** Get an Identifier */
0176: private String GetName() {
0177: StringBuffer Token = new StringBuffer();
0178: if (!IsAlpha(look) && look != '\'') {
0179: Expected("Name");
0180: }
0181: if (look == '\'') {
0182: Match('\'');
0183: boolean done = look == '\'';
0184: while (!done) {
0185: Token.append(Character.toUpperCase(look));
0186: GetChar();
0187: if (look == '\'') {
0188: Match('\'');
0189: done = look != '\'';
0190: }
0191: }
0192: } else {
0193: while (IsAlNum(look)) {
0194: Token.append(Character.toUpperCase(look));
0195: GetChar();
0196: }
0197: }
0198: SkipWhite();
0199: return Token.toString();
0200: }
0201:
0202: /**Get an Identifier AS IS, without stripping white spaces or
0203: converting to uppercase; used for literals */
0204: private String GetNameAsIs() {
0205: StringBuffer Token = new StringBuffer();
0206:
0207: while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) {
0208: Token = Token.append(look);
0209: GetChar();
0210: }
0211: return Token.toString();
0212: }
0213:
0214: /** Get a Number */
0215: private String GetNum() {
0216: StringBuffer value = new StringBuffer();
0217:
0218: while (IsDigit(this .look)) {
0219: value.append(this .look);
0220: GetChar();
0221: }
0222:
0223: SkipWhite();
0224:
0225: return value.length() == 0 ? null : value.toString();
0226: }
0227:
0228: /** Output a String with Tab */
0229: private void Emit(String s) {
0230: System.out.print(TAB + s);
0231: }
0232:
0233: /** Output a String with Tab and CRLF */
0234: private void EmitLn(String s) {
0235: Emit(s);
0236: System.out.println();
0237: ;
0238: }
0239:
0240: /** Parse and Translate a String Identifier */
0241: private void Ident() {
0242: String name;
0243: name = GetName();
0244: if (look == '(') {
0245: //This is a function
0246: function(name);
0247: } else if (look == ':' || look == '.') { // this is a AreaReference
0248: GetChar();
0249:
0250: while (look == '.') { // formulas can have . or .. or ... instead of :
0251: GetChar();
0252: }
0253:
0254: String first = name;
0255: String second = GetName();
0256: tokens.add(new AreaPtg(first + ":" + second));
0257: } else if (look == '!') {
0258: Match('!');
0259: String sheetName = name;
0260: String first = GetName();
0261: short externIdx = book.checkExternSheet(book
0262: .getSheetIndex(sheetName));
0263: if (look == ':') {
0264: Match(':');
0265: String second = GetName();
0266: if (look == '!') {
0267: //The sheet name was included in both of the areas. Only really
0268: //need it once
0269: Match('!');
0270: String third = GetName();
0271:
0272: if (!sheetName.equals(second))
0273: throw new RuntimeException(
0274: "Unhandled double sheet reference.");
0275:
0276: tokens.add(new Area3DPtg(first + ":" + third,
0277: externIdx));
0278: } else {
0279: tokens.add(new Area3DPtg(first + ":" + second,
0280: externIdx));
0281: }
0282: } else {
0283: tokens.add(new Ref3DPtg(first, externIdx));
0284: }
0285: } else {
0286: //this can be either a cell ref or a named range !!
0287: boolean cellRef = true; //we should probably do it with reg exp??
0288: boolean boolLit = (name.equals("TRUE") || name
0289: .equals("FALSE"));
0290: if (boolLit) {
0291: tokens.add(new BoolPtg(name));
0292: } else if (cellRef) {
0293: tokens.add(new ReferencePtg(name));
0294: } else {
0295: //handle after named range is integrated!!
0296: }
0297: }
0298: }
0299:
0300: /**
0301: * Adds a pointer to the last token to the latest function argument list.
0302: * @param obj
0303: */
0304: private void addArgumentPointer() {
0305: if (this .functionTokens.size() > 0) {
0306: //no bounds check because this method should not be called unless a token array is setup by function()
0307: List arguments = (List) this .functionTokens.get(0);
0308: arguments.add(tokens.get(tokens.size() - 1));
0309: }
0310: }
0311:
0312: private void function(String name) {
0313: //average 2 args per function
0314: this .functionTokens.add(0, new ArrayList(2));
0315:
0316: Match('(');
0317: int numArgs = Arguments();
0318: Match(')');
0319:
0320: AbstractFunctionPtg functionPtg = getFunction(name,
0321: (byte) numArgs);
0322:
0323: tokens.add(functionPtg);
0324:
0325: if (functionPtg.getName().equals("externalflag")) {
0326: tokens.add(new NamePtg(name, this .book));
0327: }
0328:
0329: //remove what we just put in
0330: this .functionTokens.remove(0);
0331: }
0332:
0333: /**
0334: * Adds the size of all the ptgs after the provided index (inclusive).
0335: * <p>
0336: * Initially used to count a goto
0337: * @param index
0338: * @return int
0339: */
0340: private int getPtgSize(int index) {
0341: int count = 0;
0342:
0343: Iterator ptgIterator = tokens.listIterator(index);
0344: while (ptgIterator.hasNext()) {
0345: Ptg ptg = (Ptg) ptgIterator.next();
0346: count += ptg.getSize();
0347: }
0348:
0349: return count;
0350: }
0351:
0352: private int getPtgSize(int start, int end) {
0353: int count = 0;
0354: int index = start;
0355: Iterator ptgIterator = tokens.listIterator(index);
0356: while (ptgIterator.hasNext() && index <= end) {
0357: Ptg ptg = (Ptg) ptgIterator.next();
0358: count += ptg.getSize();
0359: index++;
0360: }
0361:
0362: return count;
0363: }
0364:
0365: /**
0366: * Generates the variable function ptg for the formula.
0367: * <p>
0368: * For IF Formulas, additional PTGs are added to the tokens
0369: * @param name
0370: * @param numArgs
0371: * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
0372: */
0373: private AbstractFunctionPtg getFunction(String name, byte numArgs) {
0374: AbstractFunctionPtg retval = null;
0375:
0376: if (name.equals("IF")) {
0377: retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME,
0378: numArgs);
0379:
0380: //simulated pop, no bounds checking because this list better be populated by function()
0381: List argumentPointers = (List) this .functionTokens.get(0);
0382:
0383: AttrPtg ifPtg = new AttrPtg();
0384: ifPtg.setData((short) 7); //mirroring excel output
0385: ifPtg.setOptimizedIf(true);
0386:
0387: if (argumentPointers.size() != 2
0388: && argumentPointers.size() != 3) {
0389: throw new IllegalArgumentException(
0390: "["
0391: + argumentPointers.size()
0392: + "] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
0393: }
0394:
0395: //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
0396: //tracked in the argument pointers
0397: //The beginning first argument pointer is the last ptg of the condition
0398: int ifIndex = tokens.indexOf(argumentPointers.get(0)) + 1;
0399: tokens.add(ifIndex, ifPtg);
0400:
0401: //we now need a goto ptgAttr to skip to the end of the formula after a true condition
0402: //the true condition is should be inserted after the last ptg in the first argument
0403:
0404: int gotoIndex = tokens.indexOf(argumentPointers.get(1)) + 1;
0405:
0406: AttrPtg goto1Ptg = new AttrPtg();
0407: goto1Ptg.setGoto(true);
0408:
0409: tokens.add(gotoIndex, goto1Ptg);
0410:
0411: if (numArgs > 2) { //only add false jump if there is a false condition
0412:
0413: //second goto to skip past the function ptg
0414: AttrPtg goto2Ptg = new AttrPtg();
0415: goto2Ptg.setGoto(true);
0416: goto2Ptg.setData((short) (retval.getSize() - 1));
0417: //Page 472 of the Microsoft Excel Developer's kit states that:
0418: //The b(or w) field specifies the number byes (or words to skip, minus 1
0419:
0420: tokens.add(goto2Ptg); //this goes after all the arguments are defined
0421: }
0422:
0423: //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
0424: //count the number of bytes after the ifPtg to the False Subexpression
0425: //doesn't specify -1 in the documentation
0426: ifPtg.setData((short) (getPtgSize(ifIndex + 1, gotoIndex)));
0427:
0428: //count all the additional (goto) ptgs but dont count itself
0429: int ptgCount = this .getPtgSize(gotoIndex)
0430: - goto1Ptg.getSize() + retval.getSize();
0431: if (ptgCount > (int) Short.MAX_VALUE) {
0432: throw new RuntimeException(
0433: "Ptg Size exceeds short when being specified for a goto ptg in an if");
0434: }
0435:
0436: goto1Ptg.setData((short) (ptgCount - 1));
0437:
0438: } else {
0439:
0440: retval = new FuncVarPtg(name, numArgs);
0441: }
0442:
0443: return retval;
0444: }
0445:
0446: /** get arguments to a function */
0447: private int Arguments() {
0448: int numArgs = 0;
0449: if (look != ')') {
0450: numArgs++;
0451: Expression();
0452: addArgumentPointer();
0453: }
0454: while (look == ',' || look == ';') { //TODO handle EmptyArgs
0455: if (look == ',') {
0456: Match(',');
0457: } else {
0458: Match(';');
0459: }
0460: Expression();
0461: addArgumentPointer();
0462: numArgs++;
0463: }
0464: return numArgs;
0465: }
0466:
0467: /** Parse and Translate a Math Factor */
0468: private void Factor() {
0469: if (look == '-') {
0470: Match('-');
0471: Factor();
0472: tokens.add(new UnaryMinusPtg());
0473: } else if (look == '+') {
0474: Match('+');
0475: Factor();
0476: tokens.add(new UnaryPlusPtg());
0477: } else if (look == '(') {
0478: Match('(');
0479: Expression();
0480: Match(')');
0481: tokens.add(new ParenthesisPtg());
0482: } else if (IsAlpha(look) || look == '\'') {
0483: Ident();
0484: } else if (look == '"') {
0485: StringLiteral();
0486: } else if (look == ')' || look == ',') {
0487: tokens.add(new MissingArgPtg());
0488: } else {
0489: String number2 = null;
0490: String exponent = null;
0491: String number1 = GetNum();
0492:
0493: if (look == '.') {
0494: GetChar();
0495: number2 = GetNum();
0496: }
0497:
0498: if (look == 'E') {
0499: GetChar();
0500:
0501: String sign = "";
0502: if (look == '+') {
0503: GetChar();
0504: } else if (look == '-') {
0505: GetChar();
0506: sign = "-";
0507: }
0508:
0509: String number = GetNum();
0510: if (number == null) {
0511: Expected("Integer");
0512: }
0513: exponent = sign + number;
0514: }
0515:
0516: if (number1 == null && number2 == null) {
0517: Expected("Integer");
0518: }
0519:
0520: tokens.add(getNumberPtgFromString(number1, number2,
0521: exponent));
0522: }
0523: }
0524:
0525: /**
0526: * Get a PTG for an integer from its string representation.
0527: * return Int or Number Ptg based on size of input
0528: */
0529: private Ptg getNumberPtgFromString(String number1, String number2,
0530: String exponent) {
0531: StringBuffer number = new StringBuffer();
0532:
0533: if (number2 == null) {
0534: number.append(number1);
0535:
0536: if (exponent != null) {
0537: number.append('E');
0538: number.append(exponent);
0539: }
0540:
0541: String numberStr = number.toString();
0542:
0543: try {
0544: return new IntPtg(numberStr);
0545: } catch (NumberFormatException e) {
0546: return new NumberPtg(numberStr);
0547: }
0548: } else {
0549: if (number1 != null) {
0550: number.append(number1);
0551: }
0552:
0553: number.append('.');
0554: number.append(number2);
0555:
0556: if (exponent != null) {
0557: number.append('E');
0558: number.append(exponent);
0559: }
0560:
0561: return new NumberPtg(number.toString());
0562: }
0563: }
0564:
0565: private void StringLiteral() {
0566: // Can't use match here 'cuz it consumes whitespace
0567: // which we need to preserve inside the string.
0568: // - pete
0569: // Match('"');
0570: if (look != '"')
0571: Expected("\"");
0572: else {
0573: GetChar();
0574: StringBuffer Token = new StringBuffer();
0575: for (;;) {
0576: if (look == '"') {
0577: GetChar();
0578: SkipWhite(); //potential white space here since it doesnt matter up to the operator
0579: if (look == '"')
0580: Token.append("\"");
0581: else
0582: break;
0583: } else if (look == 0) {
0584: break;
0585: } else {
0586: Token.append(look);
0587: GetChar();
0588: }
0589: }
0590: tokens.add(new StringPtg(Token.toString()));
0591: }
0592: }
0593:
0594: /** Recognize and Translate a Multiply */
0595: private void Multiply() {
0596: Match('*');
0597: Factor();
0598: tokens.add(new MultiplyPtg());
0599:
0600: }
0601:
0602: /** Recognize and Translate a Divide */
0603: private void Divide() {
0604: Match('/');
0605: Factor();
0606: tokens.add(new DividePtg());
0607:
0608: }
0609:
0610: /** Parse and Translate a Math Term */
0611: private void Term() {
0612: Factor();
0613: while (look == '*' || look == '/' || look == '^' || look == '&') {
0614:
0615: ///TODO do we need to do anything here??
0616: if (look == '*')
0617: Multiply();
0618: else if (look == '/')
0619: Divide();
0620: else if (look == '^')
0621: Power();
0622: else if (look == '&')
0623: Concat();
0624: }
0625: }
0626:
0627: /** Recognize and Translate an Add */
0628: private void Add() {
0629: Match('+');
0630: Term();
0631: tokens.add(new AddPtg());
0632: }
0633:
0634: /** Recognize and Translate a Concatination */
0635: private void Concat() {
0636: Match('&');
0637: Term();
0638: tokens.add(new ConcatPtg());
0639: }
0640:
0641: /** Recognize and Translate a test for Equality */
0642: private void Equal() {
0643: Match('=');
0644: Expression();
0645: tokens.add(new EqualPtg());
0646: }
0647:
0648: /** Recognize and Translate a Subtract */
0649: private void Subtract() {
0650: Match('-');
0651: Term();
0652: tokens.add(new SubtractPtg());
0653: }
0654:
0655: private void Power() {
0656: Match('^');
0657: Term();
0658: tokens.add(new PowerPtg());
0659: }
0660:
0661: /** Parse and Translate an Expression */
0662: private void Expression() {
0663: Term();
0664: while (IsAddop(look)) {
0665: if (look == '+')
0666: Add();
0667: else if (look == '-')
0668: Subtract();
0669: }
0670:
0671: /*
0672: * This isn't quite right since it would allow multiple comparison operators.
0673: */
0674:
0675: if (look == '=' || look == '>' || look == '<') {
0676: if (look == '=')
0677: Equal();
0678: else if (look == '>')
0679: GreaterThan();
0680: else if (look == '<')
0681: LessThan();
0682: return;
0683: }
0684:
0685: }
0686:
0687: /** Recognize and Translate a Greater Than */
0688: private void GreaterThan() {
0689: Match('>');
0690: if (look == '=')
0691: GreaterEqual();
0692: else {
0693: Expression();
0694: tokens.add(new GreaterThanPtg());
0695: }
0696: }
0697:
0698: /** Recognize and Translate a Less Than */
0699: private void LessThan() {
0700: Match('<');
0701: if (look == '=')
0702: LessEqual();
0703: else if (look == '>')
0704: NotEqual();
0705: else {
0706: Expression();
0707: tokens.add(new LessThanPtg());
0708: }
0709:
0710: }
0711:
0712: /**
0713: * Recognize and translate Greater than or Equal
0714: *
0715: */
0716: private void GreaterEqual() {
0717: Match('=');
0718: Expression();
0719: tokens.add(new GreaterEqualPtg());
0720: }
0721:
0722: /**
0723: * Recognize and translate Less than or Equal
0724: *
0725: */
0726:
0727: private void LessEqual() {
0728: Match('=');
0729: Expression();
0730: tokens.add(new LessEqualPtg());
0731: }
0732:
0733: /**
0734: * Recognize and not Equal
0735: *
0736: */
0737:
0738: private void NotEqual() {
0739: Match('>');
0740: Expression();
0741: tokens.add(new NotEqualPtg());
0742: }
0743:
0744: //{--------------------------------------------------------------}
0745: //{ Parse and Translate an Assignment Statement }
0746: /**
0747: procedure Assignment;
0748: var Name: string[8];
0749: begin
0750: Name := GetName;
0751: Match('=');
0752: Expression;
0753:
0754: end;
0755: **/
0756:
0757: /** Initialize */
0758:
0759: private void init() {
0760: GetChar();
0761: SkipWhite();
0762: }
0763:
0764: /** API call to execute the parsing of the formula
0765: *
0766: */
0767: public void parse() {
0768: synchronized (tokens) {
0769: init();
0770: Expression();
0771: }
0772: }
0773:
0774: /*********************************
0775: * PARSER IMPLEMENTATION ENDS HERE
0776: * EXCEL SPECIFIC METHODS BELOW
0777: *******************************/
0778:
0779: /** API call to retrive the array of Ptgs created as
0780: * a result of the parsing
0781: */
0782: public Ptg[] getRPNPtg() {
0783: return getRPNPtg(FORMULA_TYPE_CELL);
0784: }
0785:
0786: public Ptg[] getRPNPtg(int formulaType) {
0787: Node node = createTree();
0788: setRootLevelRVA(node, formulaType);
0789: setParameterRVA(node, formulaType);
0790: return (Ptg[]) tokens.toArray(new Ptg[0]);
0791: }
0792:
0793: private void setRootLevelRVA(Node n, int formulaType) {
0794: //Pg 16, excelfileformat.pdf @ openoffice.org
0795: Ptg p = (Ptg) n.getValue();
0796: if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
0797: if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
0798: setClass(n, Ptg.CLASS_REF);
0799: } else {
0800: setClass(n, Ptg.CLASS_ARRAY);
0801: }
0802: } else {
0803: setClass(n, Ptg.CLASS_VALUE);
0804: }
0805:
0806: }
0807:
0808: private void setParameterRVA(Node n, int formulaType) {
0809: Ptg p = n.getValue();
0810: int numOperands = n.getNumChildren();
0811: if (p instanceof AbstractFunctionPtg) {
0812: for (int i = 0; i < numOperands; i++) {
0813: setParameterRVA(n.getChild(i),
0814: ((AbstractFunctionPtg) p).getParameterClass(i),
0815: formulaType);
0816: // if (n.getChild(i).getValue() instanceof AbstractFunctionPtg) {
0817: // setParameterRVA(n.getChild(i),formulaType);
0818: // }
0819: setParameterRVA(n.getChild(i), formulaType);
0820: }
0821: } else {
0822: for (int i = 0; i < numOperands; i++) {
0823: setParameterRVA(n.getChild(i), formulaType);
0824: }
0825: }
0826: }
0827:
0828: private void setParameterRVA(Node n, int expectedClass,
0829: int formulaType) {
0830: Ptg p = (Ptg) n.getValue();
0831: if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
0832: if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
0833: setClass(n, Ptg.CLASS_REF);
0834: }
0835: if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE) {
0836: if (formulaType == FORMULA_TYPE_CELL
0837: || formulaType == FORMULA_TYPE_SHARED) {
0838: setClass(n, Ptg.CLASS_VALUE);
0839: } else {
0840: setClass(n, Ptg.CLASS_ARRAY);
0841: }
0842: }
0843: if (p.getDefaultOperandClass() == Ptg.CLASS_ARRAY) {
0844: setClass(n, Ptg.CLASS_ARRAY);
0845: }
0846: } else if (expectedClass == Ptg.CLASS_VALUE) { //pg 15, table 2
0847: if (formulaType == FORMULA_TYPE_NAMEDRANGE) {
0848: setClass(n, Ptg.CLASS_ARRAY);
0849: } else {
0850: setClass(n, Ptg.CLASS_VALUE);
0851: }
0852: } else { //Array class, pg 16.
0853: if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE
0854: && (formulaType == FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
0855: setClass(n, Ptg.CLASS_VALUE);
0856: } else {
0857: setClass(n, Ptg.CLASS_ARRAY);
0858: }
0859: }
0860: }
0861:
0862: private void setClass(Node n, byte theClass) {
0863: Ptg p = (Ptg) n.getValue();
0864: if (p instanceof AbstractFunctionPtg
0865: || !(p instanceof OperationPtg)) {
0866: p.setClass(theClass);
0867: } else {
0868: for (int i = 0; i < n.getNumChildren(); i++) {
0869: setClass(n.getChild(i), theClass);
0870: }
0871: }
0872: }
0873:
0874: /**
0875: * Convience method which takes in a list then passes it to the
0876: * other toFormulaString signature.
0877: * @param book workbook for 3D and named references
0878: * @param lptgs list of Ptg, can be null or empty
0879: * @return a human readable String
0880: */
0881: public static String toFormulaString(Workbook book, List lptgs) {
0882: String retval = null;
0883: if (lptgs == null || lptgs.size() == 0)
0884: return "#NAME";
0885: Ptg[] ptgs = new Ptg[lptgs.size()];
0886: ptgs = (Ptg[]) lptgs.toArray(ptgs);
0887: retval = toFormulaString(book, ptgs);
0888: return retval;
0889: }
0890:
0891: /**
0892: * Convience method which takes in a list then passes it to the
0893: * other toFormulaString signature. Works on the current
0894: * workbook for 3D and named references
0895: * @param lptgs list of Ptg, can be null or empty
0896: * @return a human readable String
0897: */
0898: public String toFormulaString(List lptgs) {
0899: return toFormulaString(book, lptgs);
0900: }
0901:
0902: /**
0903: * Static method to convert an array of Ptgs in RPN order
0904: * to a human readable string format in infix mode.
0905: * @param book workbook for named and 3D references
0906: * @param ptgs array of Ptg, can be null or empty
0907: * @return a human readable String
0908: */
0909: public static String toFormulaString(Workbook book, Ptg[] ptgs) {
0910: if (ptgs == null || ptgs.length == 0)
0911: return "#NAME";
0912: java.util.Stack stack = new java.util.Stack();
0913: AttrPtg ifptg = null;
0914:
0915: // Excel allows to have AttrPtg at position 0 (such as Blanks) which
0916: // do not have any operands. Skip them.
0917: stack.push(ptgs[0].toFormulaString(book));
0918:
0919: for (int i = 1; i < ptgs.length; i++) {
0920: if (!(ptgs[i] instanceof OperationPtg)) {
0921: stack.push(ptgs[i].toFormulaString(book));
0922: continue;
0923: }
0924:
0925: if (ptgs[i] instanceof AttrPtg
0926: && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
0927: ifptg = (AttrPtg) ptgs[i];
0928: continue;
0929: }
0930:
0931: final OperationPtg o = (OperationPtg) ptgs[i];
0932: final String[] operands = new String[o
0933: .getNumberOfOperands()];
0934:
0935: for (int j = operands.length; j > 0; j--) {
0936: //TODO: catch stack underflow and throw parse exception.
0937: operands[j - 1] = (String) stack.pop();
0938: }
0939:
0940: stack.push(o.toFormulaString(operands));
0941: if (!(o instanceof AbstractFunctionPtg))
0942: continue;
0943:
0944: final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
0945: final String fname = f.getName();
0946: if (fname == null)
0947: continue;
0948:
0949: if ((ifptg != null) && (fname.equals("specialflag"))) {
0950: // this special case will be way different.
0951: stack.push(ifptg
0952: .toFormulaString(new String[] { (String) stack
0953: .pop() }));
0954: continue;
0955: }
0956: if (fname.equals("externalflag")) {
0957: final String top = (String) stack.pop();
0958: final int paren = top.indexOf('(');
0959: final int comma = top.indexOf(',');
0960: if (comma == -1) {
0961: final int rparen = top.indexOf(')');
0962: stack.push(top.substring(paren + 1, rparen) + "()");
0963: } else {
0964: stack.push(top.substring(paren + 1, comma) + '('
0965: + top.substring(comma + 1));
0966: }
0967: }
0968: }
0969: // TODO: catch stack underflow and throw parse exception.
0970: return (String) stack.pop();
0971: }
0972:
0973: /**
0974: * Static method to convert an array of Ptgs in RPN order
0975: * to a human readable string format in infix mode. Works
0976: * on the current workbook for named and 3D references.
0977: * @param ptgs array of Ptg, can be null or empty
0978: * @return a human readable String
0979: */
0980: public String toFormulaString(Ptg[] ptgs) {
0981: return toFormulaString(book, ptgs);
0982: }
0983:
0984: /** Create a tree representation of the RPN token array
0985: *used to run the class(RVA) change algo
0986: */
0987: private Node createTree() {
0988: java.util.Stack stack = new java.util.Stack();
0989: int numPtgs = tokens.size();
0990: OperationPtg o;
0991: int numOperands;
0992: Node[] operands;
0993: for (int i = 0; i < numPtgs; i++) {
0994: if (tokens.get(i) instanceof OperationPtg) {
0995:
0996: o = (OperationPtg) tokens.get(i);
0997: numOperands = o.getNumberOfOperands();
0998: operands = new Node[numOperands];
0999: for (int j = 0; j < numOperands; j++) {
1000: operands[numOperands - j - 1] = (Node) stack.pop();
1001: }
1002: Node result = new Node(o);
1003: result.setChildren(operands);
1004: stack.push(result);
1005: } else {
1006: stack.push(new Node((Ptg) tokens.get(i)));
1007: }
1008: }
1009: return (Node) stack.pop();
1010: }
1011:
1012: /** toString on the parser instance returns the RPN ordered list of tokens
1013: * Useful for testing
1014: */
1015: public String toString() {
1016: StringBuffer buf = new StringBuffer();
1017: for (int i = 0; i < tokens.size(); i++) {
1018: buf.append(((Ptg) tokens.get(i)).toFormulaString(book));
1019: buf.append(' ');
1020: }
1021: return buf.toString();
1022: }
1023:
1024: }
1025:
1026: /** Private helper class, used to create a tree representation of the formula*/
1027: class Node {
1028: private Ptg value = null;
1029: private Node[] children = new Node[0];
1030: private int numChild = 0;
1031:
1032: public Node(Ptg val) {
1033: value = val;
1034: }
1035:
1036: public void setChildren(Node[] child) {
1037: children = child;
1038: numChild = child.length;
1039: }
1040:
1041: public int getNumChildren() {
1042: return numChild;
1043: }
1044:
1045: public Node getChild(int number) {
1046: return children[number];
1047: }
1048:
1049: public Ptg getValue() {
1050: return value;
1051: }
1052: }
|