Save This Page
Home » poi-src-3.2-FINAL-20081019 » org.apache » poi » hssf » model » [javadoc | source]
    1   /* ====================================================================
    2      Licensed to the Apache Software Foundation (ASF) under one or more
    3      contributor license agreements.  See the NOTICE file distributed with
    4      this work for additional information regarding copyright ownership.
    5      The ASF licenses this file to You under the Apache License, Version 2.0
    6      (the "License"); you may not use this file except in compliance with
    7      the License.  You may obtain a copy of the License at
    8   
    9          http://www.apache.org/licenses/LICENSE-2.0
   10   
   11      Unless required by applicable law or agreed to in writing, software
   12      distributed under the License is distributed on an "AS IS" BASIS,
   13      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14      See the License for the specific language governing permissions and
   15      limitations under the License.
   16   ==================================================================== */
   17   
   18   package org.apache.poi.hssf.model;
   19   
   20   import java.util.ArrayList;
   21   import java.util.List;
   22   import java.util.Stack;
   23   import java.util.regex.Pattern;
   24   
   25   //import PTG's .. since we need everything, import *
   26   import org.apache.poi.hssf.record.formula;
   27   import org.apache.poi.hssf.record.formula.function.FunctionMetadata;
   28   import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry;
   29   import org.apache.poi.hssf.usermodel.HSSFWorkbook;
   30   
   31   /**
   32    * This class parses a formula string into a List of tokens in RPN order.
   33    * Inspired by
   34    *           Lets Build a Compiler, by Jack Crenshaw
   35    * BNF for the formula expression is :
   36    * <expression> ::= <term> [<addop> <term>]*
   37    * <term> ::= <factor>  [ <mulop> <factor> ]*
   38    * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
   39    * <function> ::= <functionName> ([expression [, expression]*])
   40    *
   41    *  @author Avik Sengupta <avik at apache dot org>
   42    *  @author Andrew C. oliver (acoliver at apache dot org)
   43    *  @author Eric Ladner (eladner at goldinc dot com)
   44    *  @author Cameron Riley (criley at ekmail.com)
   45    *  @author Peter M. Murray (pete at quantrix dot com)
   46    *  @author Pavel Krupets (pkrupets at palmtreebusiness dot com)
   47    */
   48   public final class FormulaParser {
   49   
   50       /**
   51        * Specific exception thrown when a supplied formula does not parse properly.<br/>
   52        * Primarily used by test cases when testing for specific parsing exceptions.</p>
   53        *
   54        */
   55       static final class FormulaParseException extends RuntimeException {
   56           // This class was given package scope until it would become clear that it is useful to
   57           // general client code.
   58           public FormulaParseException(String msg) {
   59               super(msg);
   60           }
   61       }
   62   
   63       public static final int FORMULA_TYPE_CELL = 0;
   64       public static final int FORMULA_TYPE_SHARED = 1;
   65       public static final int FORMULA_TYPE_ARRAY =2;
   66       public static final int FORMULA_TYPE_CONDFOMRAT = 3;
   67       public static final int FORMULA_TYPE_NAMEDRANGE = 4;
   68   
   69       private final String formulaString;
   70       private final int formulaLength;
   71       private int pointer;
   72   
   73       private ParseNode _rootNode;
   74   
   75       /**
   76        * Used for spotting if we have a cell reference,
   77        *  or a named range
   78        */
   79       private final static Pattern CELL_REFERENCE_PATTERN = Pattern.compile("(?:('?)[^:\\\\/\\?\\*\\[\\]]+\\1!)?\\$?[A-Za-z]+\\$?[\\d]+");
   80   
   81       private static char TAB = '\t';
   82   
   83       /**
   84        * Lookahead Character.
   85        * gets value '\0' when the input string is exhausted
   86        */
   87       private char look;
   88   
   89       private HSSFWorkbook book;
   90   
   91   
   92       /**
   93        * Create the formula parser, with the string that is to be
   94        *  parsed against the supplied workbook.
   95        * A later call the parse() method to return ptg list in
   96        *  rpn order, then call the getRPNPtg() to retrive the
   97        *  parse results.
   98        * This class is recommended only for single threaded use.
   99        *
  100        * If you only have a usermodel.HSSFWorkbook, and not a
  101        *  model.Workbook, then use the convenience method on
  102        *  usermodel.HSSFFormulaEvaluator
  103        */
  104       public FormulaParser(String formula, HSSFWorkbook book){
  105           formulaString = formula;
  106           pointer=0;
  107           this.book = book;
  108           formulaLength = formulaString.length();
  109       }
  110   
  111       public static Ptg[] parse(String formula, HSSFWorkbook book) {
  112           FormulaParser fp = new FormulaParser(formula, book);
  113           fp.parse();
  114           return fp.getRPNPtg();
  115       }
  116   
  117       /** Read New Character From Input Stream */
  118       private void GetChar() {
  119           // Check to see if we've walked off the end of the string.
  120           if (pointer > formulaLength) {
  121               throw new RuntimeException("too far");
  122           }
  123           if (pointer < formulaLength) {
  124               look=formulaString.charAt(pointer);
  125           } else {
  126               // Just return if so and reset 'look' to something to keep
  127               // SkipWhitespace from spinning
  128               look = (char)0;
  129           }
  130           pointer++;
  131           //System.out.println("Got char: "+ look);
  132       }
  133   
  134       /** Report What Was Expected */
  135       private RuntimeException expected(String s) {
  136           String msg = "Parse error near char " + (pointer-1) + " '" + look + "'"
  137               + " in specified formula '" + formulaString + "'. Expected "
  138               + s;
  139           return new FormulaParseException(msg);
  140       }
  141   
  142       /** Recognize an Alpha Character */
  143       private boolean IsAlpha(char c) {
  144           return Character.isLetter(c) || c == '$' || c=='_';
  145       }
  146   
  147       /** Recognize a Decimal Digit */
  148       private boolean IsDigit(char c) {
  149           return Character.isDigit(c);
  150       }
  151   
  152       /** Recognize an Alphanumeric */
  153       private boolean  IsAlNum(char c) {
  154           return  (IsAlpha(c) || IsDigit(c));
  155       }
  156   
  157       /** Recognize White Space */
  158       private boolean IsWhite( char c) {
  159           return  (c ==' ' || c== TAB);
  160       }
  161   
  162       /** Skip Over Leading White Space */
  163       private void SkipWhite() {
  164           while (IsWhite(look)) {
  165               GetChar();
  166           }
  167       }
  168   
  169       /**
  170        *  Consumes the next input character if it is equal to the one specified otherwise throws an
  171        *  unchecked exception. This method does <b>not</b> consume whitespace (before or after the
  172        *  matched character).
  173        */
  174       private void Match(char x) {
  175           if (look != x) {
  176               throw expected("'" + x + "'");
  177           }
  178           GetChar();
  179       }
  180   
  181       /** Get an Identifier */
  182       private String GetName() {
  183           StringBuffer Token = new StringBuffer();
  184           if (!IsAlpha(look) && look != '\'') {
  185               throw expected("Name");
  186           }
  187           if(look == '\'')
  188           {
  189               Match('\'');
  190               boolean done = look == '\'';
  191               while(!done)
  192               {
  193                   Token.append(look);
  194                   GetChar();
  195                   if(look == '\'')
  196                   {
  197                       Match('\'');
  198                       done = look != '\'';
  199                   }
  200               }
  201           }
  202           else
  203           {
  204               while (IsAlNum(look)) {
  205                   Token.append(look);
  206                   GetChar();
  207               }
  208           }
  209           return Token.toString();
  210       }
  211   
  212       /** Get a Number */
  213       private String GetNum() {
  214           StringBuffer value = new StringBuffer();
  215   
  216           while (IsDigit(this.look)){
  217               value.append(this.look);
  218               GetChar();
  219           }
  220           return value.length() == 0 ? null : value.toString();
  221       }
  222   
  223       private ParseNode parseFunctionOrIdentifier() {
  224           String name = GetName();
  225           if (look == '('){
  226               //This is a function
  227               return function(name);
  228           }
  229           return new ParseNode(parseIdentifier(name));
  230       }
  231       private Ptg parseIdentifier(String name) {
  232   
  233           if (look == ':' || look == '.') { // this is a AreaReference
  234               GetChar();
  235   
  236               while (look == '.') { // formulas can have . or .. or ... instead of :
  237                   GetChar();
  238               }
  239   
  240               String first = name;
  241               String second = GetName();
  242               return new AreaPtg(first+":"+second);
  243           }
  244   
  245           if (look == '!') {
  246               Match('!');
  247               String sheetName = name;
  248               String first = GetName();
  249               short externIdx = book.getExternalSheetIndex(book.getSheetIndex(sheetName));
  250               if (look == ':') {
  251                   Match(':');
  252                   String second=GetName();
  253                   if (look == '!') {
  254                       //The sheet name was included in both of the areas. Only really
  255                       //need it once
  256                       Match('!');
  257                       String third=GetName();
  258   
  259                       if (!sheetName.equals(second))
  260                           throw new RuntimeException("Unhandled double sheet reference.");
  261   
  262                       return new Area3DPtg(first+":"+third,externIdx);
  263                   }
  264                   return new Area3DPtg(first+":"+second,externIdx);
  265               }
  266               return new Ref3DPtg(first,externIdx);
  267           }
  268           if (name.equalsIgnoreCase("TRUE") || name.equalsIgnoreCase("FALSE")) {
  269               return new BoolPtg(name.toUpperCase());
  270           }
  271   
  272           // This can be either a cell ref or a named range
  273           // Try to spot which it is
  274           boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches();
  275   
  276           if (cellRef) {
  277               return new RefPtg(name);
  278           }
  279   
  280           for(int i = 0; i < book.getNumberOfNames(); i++) {
  281               // named range name matching is case insensitive
  282               if(book.getNameAt(i).getNameName().equalsIgnoreCase(name)) {
  283                   return new NamePtg(name, book);
  284               }
  285           }
  286           throw new FormulaParseException("Found reference to named range \""
  287                       + name + "\", but that named range wasn't defined!");
  288       }
  289   
  290       /**
  291        * Note - Excel function names are 'case aware but not case sensitive'.  This method may end
  292        * up creating a defined name record in the workbook if the specified name is not an internal
  293        * Excel function, and has not been encountered before.
  294        *
  295        * @param name case preserved function name (as it was entered/appeared in the formula).
  296        */
  297       private ParseNode function(String name) {
  298           NamePtg nameToken = null;
  299           // Note regarding parameter -
  300           if(!AbstractFunctionPtg.isInternalFunctionName(name)) {
  301               // external functions get a Name token which points to a defined name record
  302               nameToken = new NamePtg(name, this.book);
  303   
  304               // in the token tree, the name is more or less the first argument
  305           }
  306   
  307           Match('(');
  308           ParseNode[] args = Arguments();
  309           Match(')');
  310   
  311           return getFunction(name, nameToken, args);
  312       }
  313   
  314       /**
  315        * Generates the variable function ptg for the formula.
  316        * <p>
  317        * For IF Formulas, additional PTGs are added to the tokens
  318        * @param name
  319        * @param numArgs
  320        * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
  321        */
  322       private ParseNode getFunction(String name, NamePtg namePtg, ParseNode[] args) {
  323   
  324           FunctionMetadata fm = FunctionMetadataRegistry.getFunctionByName(name.toUpperCase());
  325           int numArgs = args.length;
  326           if(fm == null) {
  327               if (namePtg == null) {
  328                   throw new IllegalStateException("NamePtg must be supplied for external functions");
  329               }
  330               // must be external function
  331               ParseNode[] allArgs = new ParseNode[numArgs+1];
  332               allArgs[0] = new ParseNode(namePtg);
  333               System.arraycopy(args, 0, allArgs, 1, numArgs);
  334               return new ParseNode(new FuncVarPtg(name, (byte)(numArgs+1)), allArgs);
  335           }
  336   
  337           if (namePtg != null) {
  338               throw new IllegalStateException("NamePtg no applicable to internal functions");
  339           }
  340           boolean isVarArgs = !fm.hasFixedArgsLength();
  341           int funcIx = fm.getIndex();
  342           validateNumArgs(args.length, fm);
  343   
  344           AbstractFunctionPtg retval;
  345           if(isVarArgs) {
  346               retval = new FuncVarPtg(name, (byte)numArgs);
  347           } else {
  348               retval = new FuncPtg(funcIx);
  349           }
  350           return new ParseNode(retval, args);
  351       }
  352   
  353       private void validateNumArgs(int numArgs, FunctionMetadata fm) {
  354           if(numArgs < fm.getMinParams()) {
  355               String msg = "Too few arguments to function '" + fm.getName() + "'. ";
  356               if(fm.hasFixedArgsLength()) {
  357                   msg += "Expected " + fm.getMinParams();
  358               } else {
  359                   msg += "At least " + fm.getMinParams() + " were expected";
  360               }
  361               msg += " but got " + numArgs + ".";
  362               throw new FormulaParseException(msg);
  363            }
  364           if(numArgs > fm.getMaxParams()) {
  365               String msg = "Too many arguments to function '" + fm.getName() + "'. ";
  366               if(fm.hasFixedArgsLength()) {
  367                   msg += "Expected " + fm.getMaxParams();
  368               } else {
  369                   msg += "At most " + fm.getMaxParams() + " were expected";
  370               }
  371               msg += " but got " + numArgs + ".";
  372               throw new FormulaParseException(msg);
  373          }
  374       }
  375   
  376       private static boolean isArgumentDelimiter(char ch) {
  377           return ch ==  ',' || ch == ')';
  378       }
  379   
  380       /** get arguments to a function */
  381       private ParseNode[] Arguments() {
  382           //average 2 args per function
  383           List temp = new ArrayList(2);
  384           SkipWhite();
  385           if(look == ')') {
  386               return ParseNode.EMPTY_ARRAY;
  387           }
  388   
  389           boolean missedPrevArg = true;
  390           int numArgs = 0;
  391           while (true) {
  392               SkipWhite();
  393               if (isArgumentDelimiter(look)) {
  394                   if (missedPrevArg) {
  395                       temp.add(new ParseNode(MissingArgPtg.instance));
  396                       numArgs++;
  397                   }
  398                   if (look == ')') {
  399                       break;
  400                   }
  401                   Match(',');
  402                   missedPrevArg = true;
  403                   continue;
  404               }
  405               temp.add(comparisonExpression());
  406               numArgs++;
  407               missedPrevArg = false;
  408               SkipWhite();
  409               if (!isArgumentDelimiter(look)) {
  410                   throw expected("',' or ')'");
  411               }
  412           }
  413           ParseNode[] result = new ParseNode[temp.size()];
  414           temp.toArray(result);
  415           return result;
  416       }
  417   
  418      /** Parse and Translate a Math Factor  */
  419       private ParseNode powerFactor() {
  420           ParseNode result = percentFactor();
  421           while(true) {
  422               SkipWhite();
  423               if(look != '^') {
  424                   return result;
  425               }
  426               Match('^');
  427               ParseNode other = percentFactor();
  428               result = new ParseNode(PowerPtg.instance, result, other);
  429           }
  430       }
  431   
  432       private ParseNode percentFactor() {
  433           ParseNode result = parseSimpleFactor();
  434           while(true) {
  435               SkipWhite();
  436               if(look != '%') {
  437                   return result;
  438               }
  439               Match('%');
  440               result = new ParseNode(PercentPtg.instance, result);
  441           }
  442       }
  443   
  444   
  445       /**
  446        * factors (without ^ or % )
  447        */
  448       private ParseNode parseSimpleFactor() {
  449           SkipWhite();
  450           switch(look) {
  451               case '#':
  452                   return new ParseNode(parseErrorLiteral());
  453               case '-':
  454                   Match('-');
  455                   return new ParseNode(UnaryMinusPtg.instance, powerFactor());
  456               case '+':
  457                   Match('+');
  458                   return new ParseNode(UnaryPlusPtg.instance, powerFactor());
  459               case '(':
  460                   Match('(');
  461                   ParseNode inside = comparisonExpression();
  462                   Match(')');
  463                   return new ParseNode(ParenthesisPtg.instance, inside);
  464               case '"':
  465                   return new ParseNode(parseStringLiteral());
  466           }
  467           if (IsAlpha(look) || look == '\''){
  468               return parseFunctionOrIdentifier();
  469           }
  470           // else - assume number
  471           return new ParseNode(parseNumber());
  472       }
  473   
  474   
  475       private Ptg parseNumber() {
  476           String number2 = null;
  477           String exponent = null;
  478           String number1 = GetNum();
  479   
  480           if (look == '.') {
  481               GetChar();
  482               number2 = GetNum();
  483           }
  484   
  485           if (look == 'E') {
  486               GetChar();
  487   
  488               String sign = "";
  489               if (look == '+') {
  490                   GetChar();
  491               } else if (look == '-') {
  492                   GetChar();
  493                   sign = "-";
  494               }
  495   
  496               String number = GetNum();
  497               if (number == null) {
  498                   throw expected("Integer");
  499               }
  500               exponent = sign + number;
  501           }
  502   
  503           if (number1 == null && number2 == null) {
  504               throw expected("Integer");
  505           }
  506   
  507           return getNumberPtgFromString(number1, number2, exponent);
  508       }
  509   
  510   
  511       private ErrPtg parseErrorLiteral() {
  512           Match('#');
  513           String part1 = GetName().toUpperCase();
  514   
  515           switch(part1.charAt(0)) {
  516               case 'V':
  517                   if(part1.equals("VALUE")) {
  518                       Match('!');
  519                       return ErrPtg.VALUE_INVALID;
  520                   }
  521                   throw expected("#VALUE!");
  522               case 'R':
  523                   if(part1.equals("REF")) {
  524                       Match('!');
  525                       return ErrPtg.REF_INVALID;
  526                   }
  527                   throw expected("#REF!");
  528               case 'D':
  529                   if(part1.equals("DIV")) {
  530                       Match('/');
  531                       Match('0');
  532                       Match('!');
  533                       return ErrPtg.DIV_ZERO;
  534                   }
  535                   throw expected("#DIV/0!");
  536               case 'N':
  537                   if(part1.equals("NAME")) {
  538                       Match('?');  // only one that ends in '?'
  539                       return ErrPtg.NAME_INVALID;
  540                   }
  541                   if(part1.equals("NUM")) {
  542                       Match('!');
  543                       return ErrPtg.NUM_ERROR;
  544                   }
  545                   if(part1.equals("NULL")) {
  546                       Match('!');
  547                       return ErrPtg.NULL_INTERSECTION;
  548                   }
  549                   if(part1.equals("N")) {
  550                       Match('/');
  551                       if(look != 'A' && look != 'a') {
  552                           throw expected("#N/A");
  553                       }
  554                       Match(look);
  555                       // Note - no '!' or '?' suffix
  556                       return ErrPtg.N_A;
  557                   }
  558                   throw expected("#NAME?, #NUM!, #NULL! or #N/A");
  559   
  560           }
  561           throw expected("#VALUE!, #REF!, #DIV/0!, #NAME?, #NUM!, #NULL! or #N/A");
  562       }
  563   
  564   
  565       /**
  566        * Get a PTG for an integer from its string representation.
  567        * return Int or Number Ptg based on size of input
  568        */
  569       private static Ptg getNumberPtgFromString(String number1, String number2, String exponent) {
  570           StringBuffer number = new StringBuffer();
  571   
  572           if (number2 == null) {
  573               number.append(number1);
  574   
  575               if (exponent != null) {
  576                   number.append('E');
  577                   number.append(exponent);
  578               }
  579   
  580               String numberStr = number.toString();
  581               int intVal;
  582               try {
  583                   intVal = Integer.parseInt(numberStr);
  584               } catch (NumberFormatException e) {
  585                   return new NumberPtg(numberStr);
  586               }
  587               if (IntPtg.isInRange(intVal)) {
  588                   return new IntPtg(intVal);
  589               }
  590               return new NumberPtg(numberStr);
  591           }
  592   
  593           if (number1 != null) {
  594               number.append(number1);
  595           }
  596   
  597           number.append('.');
  598           number.append(number2);
  599   
  600           if (exponent != null) {
  601               number.append('E');
  602               number.append(exponent);
  603           }
  604   
  605           return new NumberPtg(number.toString());
  606       }
  607   
  608   
  609       private StringPtg parseStringLiteral() {
  610           Match('"');
  611   
  612           StringBuffer token = new StringBuffer();
  613           while (true) {
  614               if (look == '"') {
  615                   GetChar();
  616                   if (look != '"') {
  617                       break;
  618                   }
  619                }
  620               token.append(look);
  621               GetChar();
  622           }
  623           return new StringPtg(token.toString());
  624       }
  625   
  626       /** Parse and Translate a Math Term */
  627       private ParseNode  Term() {
  628           ParseNode result = powerFactor();
  629           while(true) {
  630               SkipWhite();
  631               Ptg operator;
  632               switch(look) {
  633                   case '*':
  634                       Match('*');
  635                       operator = MultiplyPtg.instance;
  636                       break;
  637                   case '/':
  638                       Match('/');
  639                       operator = DividePtg.instance;
  640                       break;
  641                   default:
  642                       return result; // finished with Term
  643               }
  644               ParseNode other = powerFactor();
  645               result = new ParseNode(operator, result, other);
  646           }
  647       }
  648   
  649       private ParseNode comparisonExpression() {
  650           ParseNode result = concatExpression();
  651           while (true) {
  652               SkipWhite();
  653               switch(look) {
  654                   case '=':
  655                   case '>':
  656                   case '<':
  657                       Ptg comparisonToken = getComparisonToken();
  658                       ParseNode other = concatExpression();
  659                       result = new ParseNode(comparisonToken, result, other);
  660                       continue;
  661               }
  662               return result; // finished with predicate expression
  663           }
  664       }
  665   
  666       private Ptg getComparisonToken() {
  667           if(look == '=') {
  668               Match(look);
  669               return EqualPtg.instance;
  670           }
  671           boolean isGreater = look == '>';
  672           Match(look);
  673           if(isGreater) {
  674               if(look == '=') {
  675                   Match('=');
  676                   return GreaterEqualPtg.instance;
  677               }
  678               return GreaterThanPtg.instance;
  679           }
  680           switch(look) {
  681               case '=':
  682                   Match('=');
  683                   return LessEqualPtg.instance;
  684               case '>':
  685                   Match('>');
  686                   return NotEqualPtg.instance;
  687           }
  688           return LessThanPtg.instance;
  689       }
  690   
  691   
  692       private ParseNode concatExpression() {
  693           ParseNode result = additiveExpression();
  694           while (true) {
  695               SkipWhite();
  696               if(look != '&') {
  697                   break; // finished with concat expression
  698               }
  699               Match('&');
  700               ParseNode other = additiveExpression();
  701               result = new ParseNode(ConcatPtg.instance, result, other);
  702           }
  703           return result;
  704       }
  705   
  706   
  707       /** Parse and Translate an Expression */
  708       private ParseNode additiveExpression() {
  709           ParseNode result = Term();
  710           while (true) {
  711               SkipWhite();
  712               Ptg operator;
  713               switch(look) {
  714                   case '+':
  715                       Match('+');
  716                       operator = AddPtg.instance;
  717                       break;
  718                   case '-':
  719                       Match('-');
  720                       operator = SubtractPtg.instance;
  721                       break;
  722                   default:
  723                       return result; // finished with additive expression
  724               }
  725               ParseNode other = Term();
  726               result = new ParseNode(operator, result, other);
  727           }
  728       }
  729   
  730       //{--------------------------------------------------------------}
  731       //{ Parse and Translate an Assignment Statement }
  732       /**
  733   procedure Assignment;
  734   var Name: string[8];
  735   begin
  736      Name := GetName;
  737      Match('=');
  738      Expression;
  739   
  740   end;
  741        **/
  742   
  743   
  744       /**
  745        *  API call to execute the parsing of the formula
  746        * @deprecated use Ptg[] FormulaParser.parse(String, HSSFWorkbook) directly
  747        */
  748       public void parse() {
  749           pointer=0;
  750           GetChar();
  751           _rootNode = comparisonExpression();
  752   
  753           if(pointer <= formulaLength) {
  754               String msg = "Unused input [" + formulaString.substring(pointer-1)
  755                   + "] after attempting to parse the formula [" + formulaString + "]";
  756               throw new FormulaParseException(msg);
  757           }
  758       }
  759   
  760   
  761       /*********************************
  762        * PARSER IMPLEMENTATION ENDS HERE
  763        * EXCEL SPECIFIC METHODS BELOW
  764        *******************************/
  765   
  766       /** API call to retrive the array of Ptgs created as
  767        * a result of the parsing
  768        */
  769       public Ptg[] getRPNPtg() {
  770           return getRPNPtg(FORMULA_TYPE_CELL);
  771       }
  772   
  773       public Ptg[] getRPNPtg(int formulaType) {
  774           OperandClassTransformer oct = new OperandClassTransformer(formulaType);
  775           // RVA is for 'operand class': 'reference', 'value', 'array'
  776           oct.transformFormula(_rootNode);
  777           return ParseNode.toTokenArray(_rootNode);
  778       }
  779   
  780       /**
  781        * Convenience method which takes in a list then passes it to the
  782        *  other toFormulaString signature.
  783        * @param book   workbook for 3D and named references
  784        * @param lptgs  list of Ptg, can be null or empty
  785        * @return a human readable String
  786        */
  787       public static String toFormulaString(HSSFWorkbook book, List lptgs) {
  788           String retval = null;
  789           if (lptgs == null || lptgs.size() == 0) return "#NAME";
  790           Ptg[] ptgs = new Ptg[lptgs.size()];
  791           ptgs = (Ptg[])lptgs.toArray(ptgs);
  792           retval = toFormulaString(book, ptgs);
  793           return retval;
  794       }
  795       /**
  796        * Convenience method which takes in a list then passes it to the
  797        *  other toFormulaString signature. Works on the current
  798        *  workbook for 3D and named references
  799        * @param lptgs  list of Ptg, can be null or empty
  800        * @return a human readable String
  801        */
  802       public String toFormulaString(List lptgs) {
  803           return toFormulaString(book, lptgs);
  804       }
  805   
  806       /**
  807        * Static method to convert an array of Ptgs in RPN order
  808        * to a human readable string format in infix mode.
  809        * @param book  workbook for named and 3D references
  810        * @param ptgs  array of Ptg, can be null or empty
  811        * @return a human readable String
  812        */
  813       public static String toFormulaString(HSSFWorkbook book, Ptg[] ptgs) {
  814           if (ptgs == null || ptgs.length == 0) {
  815               // TODO - what is the justification for returning "#NAME" (which is not "#NAME?", btw)
  816               return "#NAME";
  817           }
  818           Stack stack = new Stack();
  819   
  820           for (int i=0 ; i < ptgs.length; i++) {
  821               Ptg ptg = ptgs[i];
  822               // TODO - what about MemNoMemPtg?
  823               if(ptg instanceof MemAreaPtg || ptg instanceof MemFuncPtg || ptg instanceof MemErrPtg) {
  824                   // marks the start of a list of area expressions which will be naturally combined
  825                   // by their trailing operators (e.g. UnionPtg)
  826                   // TODO - put comment and throw exception in toFormulaString() of these classes
  827                   continue;
  828               }
  829               if (ptg instanceof ParenthesisPtg) {
  830                   String contents = (String)stack.pop();
  831                   stack.push ("(" + contents + ")");
  832                   continue;
  833               }
  834               if (ptg instanceof AttrPtg) {
  835                   AttrPtg attrPtg = ((AttrPtg) ptg);
  836                   if (attrPtg.isOptimizedIf() || attrPtg.isOptimizedChoose() || attrPtg.isGoto()) {
  837                       continue;
  838                   }
  839                   if (attrPtg.isSpace()) {
  840                       // POI currently doesn't render spaces in formulas
  841                       continue;
  842                       // but if it ever did, care must be taken:
  843                       // tAttrSpace comes *before* the operand it applies to, which may be consistent
  844                       // with how the formula text appears but is against the RPN ordering assumed here
  845                   }
  846                   if (attrPtg.isSemiVolatile()) {
  847                       // similar to tAttrSpace - RPN is violated
  848                       continue;
  849                   }
  850                   if (attrPtg.isSum()) {
  851                       String[] operands = getOperands(stack, attrPtg.getNumberOfOperands());
  852                       stack.push(attrPtg.toFormulaString(operands));
  853                       continue;
  854                   }
  855                   throw new RuntimeException("Unexpected tAttr: " + attrPtg.toString());
  856               }
  857   
  858               if (! (ptg instanceof OperationPtg)) {
  859                   stack.push(ptg.toFormulaString(book));
  860                   continue;
  861               }
  862   
  863               OperationPtg o = (OperationPtg) ptg;
  864               String[] operands = getOperands(stack, o.getNumberOfOperands());
  865               stack.push(o.toFormulaString(operands));
  866           }
  867           if(stack.isEmpty()) {
  868               // inspection of the code above reveals that every stack.pop() is followed by a
  869               // stack.push(). So this is either an internal error or impossible.
  870               throw new IllegalStateException("Stack underflow");
  871           }
  872           String result = (String) stack.pop();
  873           if(!stack.isEmpty()) {
  874               // Might be caused by some tokens like AttrPtg and Mem*Ptg, which really shouldn't
  875               // put anything on the stack
  876               throw new IllegalStateException("too much stuff left on the stack");
  877           }
  878           return result;
  879       }
  880       
  881       private static String[] getOperands(Stack stack, int nOperands) {
  882           String[] operands = new String[nOperands];
  883   
  884           for (int j = nOperands-1; j >= 0; j--) { // reverse iteration because args were pushed in-order
  885               if(stack.isEmpty()) {
  886                  String msg = "Too few arguments supplied to operation. Expected (" + nOperands
  887                       + ") operands but got (" + (nOperands - j - 1) + ")";
  888                   throw new IllegalStateException(msg);
  889               }
  890               operands[j] = (String) stack.pop();
  891           }
  892           return operands;
  893       }
  894       /**
  895        * Static method to convert an array of Ptgs in RPN order
  896        *  to a human readable string format in infix mode. Works
  897        *  on the current workbook for named and 3D references.
  898        * @param ptgs  array of Ptg, can be null or empty
  899        * @return a human readable String
  900        */
  901       public String toFormulaString(Ptg[] ptgs) {
  902           return toFormulaString(book, ptgs);
  903       }
  904   }

Save This Page
Home » poi-src-3.2-FINAL-20081019 » org.apache » poi » hssf » model » [javadoc | source]