KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > poi > hssf > model > FormulaParser


1
2 /* ====================================================================
3    Copyright 2002-2004 Apache Software Foundation
4
5    Licensed under the Apache License, Version 2.0 (the "License");
6    you may not use this file except in compliance with the License.
7    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
19
20 package org.apache.poi.hssf.model;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Iterator JavaDoc;
24 import java.util.LinkedList JavaDoc;
25 import java.util.List JavaDoc;
26
27 //import PTG's .. since we need everything, import *
28
import org.apache.poi.hssf.record.formula.*;
29
30
31
32 /**
33  * This class parses a formula string into a List of tokens in RPN order.
34  * Inspired by
35  * Lets Build a Compiler, by Jack Crenshaw
36  * BNF for the formula expression is :
37  * <expression> ::= <term> [<addop> <term>]*
38  * <term> ::= <factor> [ <mulop> <factor> ]*
39  * <factor> ::= <number> | (<expression>) | <cellRef> | <function>
40  * <function> ::= <functionName> ([expression [, expression]*])
41  *
42  * @author Avik Sengupta <avik AT Avik Sengupta DOT com>
43  * @author Andrew C. oliver (acoliver at apache dot org)
44  * @author Eric Ladner (eladner at goldinc dot com)
45  * @author Cameron Riley (criley at ekmail.com)
46  * @author Peter M. Murray (pete at quantrix dot com)
47  */

48 public class FormulaParser {
49     
50     public static int FORMULA_TYPE_CELL = 0;
51     public static int FORMULA_TYPE_SHARED = 1;
52     public static int FORMULA_TYPE_ARRAY =2;
53     public static int FORMULA_TYPE_CONDFOMRAT = 3;
54     public static int FORMULA_TYPE_NAMEDRANGE = 4;
55     
56     private String JavaDoc formulaString;
57     private int pointer=0;
58     private int formulaLength;
59     
60     private List JavaDoc tokens = new java.util.Stack JavaDoc();
61     
62     /**
63      * Using an unsynchronized linkedlist to implement a stack since we're not multi-threaded.
64      */

65     private List JavaDoc functionTokens = new LinkedList JavaDoc();
66     
67     //private Stack tokens = new java.util.Stack();
68
private List JavaDoc result = new ArrayList JavaDoc();
69     private int numParen;
70     
71     private static char TAB = '\t';
72     private static char CR = '\n';
73     
74    private char look; // Lookahead Character
75
private boolean inFunction = false;
76    
77    private Workbook book;
78     
79     
80     /** create the parser with the string that is to be parsed
81      * later call the parse() method to return ptg list in rpn order
82      * then call the getRPNPtg() to retrive the parse results
83      * This class is recommended only for single threaded use
84      */

85     public FormulaParser(String JavaDoc formula, Workbook book){
86         formulaString = formula;
87         pointer=0;
88         this.book = book;
89         formulaLength = formulaString.length();
90     }
91     
92
93     /** Read New Character From Input Stream */
94     private void GetChar() {
95         // Check to see if we've walked off the end of the string.
96
// Just return if so and reset Look to smoething to keep
97
// SkipWhitespace from spinning
98
if (pointer == formulaLength) {
99             look = (char)0;
100         return;
101     }
102         look=formulaString.charAt(pointer++);
103         //System.out.println("Got char: "+ look);
104
}
105     
106
107     /** Report an Error */
108     private void Error(String JavaDoc s) {
109         System.out.println("Error: "+s);
110     }
111     
112     
113  
114     /** Report Error and Halt */
115     private void Abort(String JavaDoc s) {
116         Error(s);
117         //System.exit(1); //throw exception??
118
throw new RuntimeException JavaDoc("Cannot Parse, sorry : "+s);
119     }
120     
121     
122
123     /** Report What Was Expected */
124     private void Expected(String JavaDoc s) {
125         Abort(s + " Expected");
126     }
127     
128     
129  
130     /** Recognize an Alpha Character */
131     private boolean IsAlpha(char c) {
132         return Character.isLetter(c) || c == '$';
133     }
134     
135     
136  
137     /** Recognize a Decimal Digit */
138     private boolean IsDigit(char c) {
139         //System.out.println("Checking digit for"+c);
140
return Character.isDigit(c);
141     }
142     
143     
144
145     /** Recognize an Alphanumeric */
146     private boolean IsAlNum(char c) {
147         return (IsAlpha(c) || IsDigit(c));
148     }
149     
150     
151
152     /** Recognize an Addop */
153     private boolean IsAddop( char c) {
154         return (c =='+' || c =='-');
155     }
156     
157
158     /** Recognize White Space */
159     private boolean IsWhite( char c) {
160         return (c ==' ' || c== TAB);
161     }
162     
163     /**
164      * Determines special characters;primarily in use for definition of string literals
165      * @param c
166      * @return boolean
167      */

168     private boolean IsSpecialChar(char c) {
169         return (c == '>' || c== '<' || c== '=' || c=='&' || c=='[' || c==']');
170     }
171     
172
173     /** Skip Over Leading White Space */
174     private void SkipWhite() {
175         while (IsWhite(look)) {
176             GetChar();
177         }
178     }
179     
180     
181
182     /** Match a Specific Input Character */
183     private void Match(char x) {
184         if (look != x) {
185             Expected("" + x + "");
186         }else {
187             GetChar();
188             SkipWhite();
189         }
190     }
191     
192     /** Get an Identifier */
193     private String JavaDoc GetName() {
194         StringBuffer JavaDoc Token = new StringBuffer JavaDoc();
195         if (!IsAlpha(look) && look != '\'') {
196             Expected("Name");
197         }
198         if(look == '\'')
199         {
200             Match('\'');
201             boolean done = look == '\'';
202             while(!done)
203             {
204                 Token.append(Character.toUpperCase(look));
205                 GetChar();
206                 if(look == '\'')
207                 {
208                     Match('\'');
209                     done = look != '\'';
210                 }
211             }
212         }
213         else
214         {
215             while (IsAlNum(look)) {
216                 Token.append(Character.toUpperCase(look));
217                 GetChar();
218             }
219         }
220         SkipWhite();
221         return Token.toString();
222     }
223     
224     /**Get an Identifier AS IS, without stripping white spaces or
225        converting to uppercase; used for literals */

226     private String JavaDoc GetNameAsIs() {
227         StringBuffer JavaDoc Token = new StringBuffer JavaDoc();
228         
229         while (IsAlNum(look) || IsWhite(look) || IsSpecialChar(look)) {
230             Token = Token.append(look);
231             GetChar();
232         }
233         return Token.toString();
234     }
235     
236     
237     /** Get a Number */
238     private String JavaDoc GetNum() {
239         String JavaDoc Value ="";
240         if (!IsDigit(look)) Expected("Integer");
241         while (IsDigit(look)){
242             Value = Value + look;
243             GetChar();
244         }
245         SkipWhite();
246         return Value;
247     }
248
249     /** Output a String with Tab */
250     private void Emit(String JavaDoc s){
251         System.out.print(TAB+s);
252     }
253
254     /** Output a String with Tab and CRLF */
255     private void EmitLn(String JavaDoc s) {
256         Emit(s);
257         System.out.println();;
258     }
259     
260     /** Parse and Translate a String Identifier */
261     private void Ident() {
262         String JavaDoc name;
263         name = GetName();
264         if (look == '('){
265             //This is a function
266
function(name);
267         } else if (look == ':') { // this is a AreaReference
268
String JavaDoc first = name;
269             Match(':');
270             String JavaDoc second = GetName();
271             tokens.add(new AreaPtg(first+":"+second));
272         } else if (look == '!') {
273             Match('!');
274             String JavaDoc sheetName = name;
275             String JavaDoc first = GetName();
276             short externIdx = book.checkExternSheet(book.getSheetIndex(sheetName));
277             if (look == ':') {
278                 Match(':');
279                 String JavaDoc second=GetName();
280                 
281                 tokens.add(new Area3DPtg(first+":"+second,externIdx));
282             } else {
283                 tokens.add(new Ref3DPtg(first,externIdx));
284             }
285         } else {
286             //this can be either a cell ref or a named range !!
287
boolean cellRef = true ; //we should probably do it with reg exp??
288
boolean boolLit = (name.equals("TRUE") || name.equals("FALSE"));
289             if (boolLit) {
290                 tokens.add(new BoolPtg(name));
291             } else if (cellRef) {
292                 tokens.add(new ReferencePtg(name));
293             }else {
294                 //handle after named range is integrated!!
295
}
296         }
297     }
298     
299     /**
300      * Adds a pointer to the last token to the latest function argument list.
301      * @param obj
302      */

303     private void addArgumentPointer() {
304         if (this.functionTokens.size() > 0) {
305             //no bounds check because this method should not be called unless a token array is setup by function()
306
List JavaDoc arguments = (List JavaDoc)this.functionTokens.get(0);
307             arguments.add(tokens.get(tokens.size()-1));
308         }
309     }
310     
311     private void function(String JavaDoc name) {
312         //average 2 args per function
313
this.functionTokens.add(0, new ArrayList JavaDoc(2));
314         
315         Match('(');
316         int numArgs = Arguments();
317         Match(')');
318                 
319         AbstractFunctionPtg functionPtg = getFunction(name,(byte)numArgs);
320         
321         tokens.add(functionPtg);
322  
323         if (functionPtg.getName().equals("externalflag")) {
324             tokens.add(new NamePtg(name, this.book));
325         }
326
327         //remove what we just put in
328
this.functionTokens.remove(0);
329     }
330     
331     /**
332      * Adds the size of all the ptgs after the provided index (inclusive).
333      * <p>
334      * Initially used to count a goto
335      * @param index
336      * @return int
337      */

338     private int getPtgSize(int index) {
339         int count = 0;
340         
341         Iterator JavaDoc ptgIterator = tokens.listIterator(index);
342         while (ptgIterator.hasNext()) {
343             Ptg ptg = (Ptg)ptgIterator.next();
344             count+=ptg.getSize();
345         }
346         
347         return count;
348     }
349     
350     private int getPtgSize(int start, int end) {
351         int count = 0;
352         int index = start;
353         Iterator JavaDoc ptgIterator = tokens.listIterator(index);
354         while (ptgIterator.hasNext() && index <= end) {
355             Ptg ptg = (Ptg)ptgIterator.next();
356             count+=ptg.getSize();
357             index++;
358         }
359         
360         return count;
361     }
362     /**
363      * Generates the variable function ptg for the formula.
364      * <p>
365      * For IF Formulas, additional PTGs are added to the tokens
366      * @param name
367      * @param numArgs
368      * @return Ptg a null is returned if we're in an IF formula, it needs extreme manipulation and is handled in this function
369      */

370     private AbstractFunctionPtg getFunction(String JavaDoc name, byte numArgs) {
371         AbstractFunctionPtg retval = null;
372         
373         if (name.equals("IF")) {
374             retval = new FuncVarPtg(AbstractFunctionPtg.ATTR_NAME, numArgs);
375             
376             //simulated pop, no bounds checking because this list better be populated by function()
377
List JavaDoc argumentPointers = (List JavaDoc)this.functionTokens.get(0);
378             
379             
380             AttrPtg ifPtg = new AttrPtg();
381             ifPtg.setData((short)7); //mirroring excel output
382
ifPtg.setOptimizedIf(true);
383             
384             if (argumentPointers.size() != 2 && argumentPointers.size() != 3) {
385                 throw new IllegalArgumentException JavaDoc("["+argumentPointers.size()+"] Arguments Found - An IF formula requires 2 or 3 arguments. IF(CONDITION, TRUE_VALUE, FALSE_VALUE [OPTIONAL]");
386             }
387             
388             //Biffview of an IF formula record indicates the attr ptg goes after the condition ptgs and are
389
//tracked in the argument pointers
390
//The beginning first argument pointer is the last ptg of the condition
391
int ifIndex = tokens.indexOf(argumentPointers.get(0))+1;
392             tokens.add(ifIndex, ifPtg);
393             
394             //we now need a goto ptgAttr to skip to the end of the formula after a true condition
395
//the true condition is should be inserted after the last ptg in the first argument
396

397             int gotoIndex = tokens.indexOf(argumentPointers.get(1))+1;
398             
399             AttrPtg goto1Ptg = new AttrPtg();
400             goto1Ptg.setGoto(true);
401             
402             
403             tokens.add(gotoIndex, goto1Ptg);
404             
405             
406             if (numArgs > 2) { //only add false jump if there is a false condition
407

408                 //second goto to skip past the function ptg
409
AttrPtg goto2Ptg = new AttrPtg();
410                 goto2Ptg.setGoto(true);
411                 goto2Ptg.setData((short)(retval.getSize()-1));
412                 //Page 472 of the Microsoft Excel Developer's kit states that:
413
//The b(or w) field specifies the number byes (or words to skip, minus 1
414

415                 tokens.add(goto2Ptg); //this goes after all the arguments are defined
416
}
417             
418             //data portion of the if ptg points to the false subexpression (Page 472 of MS Excel Developer's kit)
419
//count the number of bytes after the ifPtg to the False Subexpression
420
//doesn't specify -1 in the documentation
421
ifPtg.setData((short)(getPtgSize(ifIndex+1, gotoIndex)));
422             
423             //count all the additional (goto) ptgs but dont count itself
424
int ptgCount = this.getPtgSize(gotoIndex)-goto1Ptg.getSize()+retval.getSize();
425             if (ptgCount > (int)Short.MAX_VALUE) {
426                 throw new RuntimeException JavaDoc("Ptg Size exceeds short when being specified for a goto ptg in an if");
427             }
428             
429             goto1Ptg.setData((short)(ptgCount-1));
430             
431         } else {
432             
433             retval = new FuncVarPtg(name,numArgs);
434         }
435         
436         return retval;
437     }
438     
439     /** get arguments to a function */
440     private int Arguments() {
441         int numArgs = 0;
442         if (look != ')') {
443             numArgs++;
444             Expression();
445                addArgumentPointer();
446         }
447         while (look == ',' || look == ';') { //TODO handle EmptyArgs
448
if(look == ',') {
449               Match(',');
450             }
451             else {
452               Match(';');
453             }
454             Expression();
455                addArgumentPointer();
456             numArgs++;
457         }
458         return numArgs;
459     }
460
461    /** Parse and Translate a Math Factor */
462     private void Factor() {
463         if (look == '-')
464         {
465             Match('-');
466             Factor();
467             tokens.add(new UnaryMinusPtg());
468         }
469         else if (look == '(' ) {
470             Match('(');
471             Expression();
472             Match(')');
473             tokens.add(new ParenthesisPtg());
474         } else if (IsAlpha(look) || look == '\''){
475             Ident();
476         } else if(look == '"') {
477            StringLiteral();
478         } else {
479              
480             String JavaDoc number = GetNum();
481             if (look=='.') {
482                 Match('.');
483                 String JavaDoc decimalPart = null;
484                 if (IsDigit(look)) number = number +"."+ GetNum(); //this also takes care of someone entering "1234."
485
tokens.add(new NumberPtg(number));
486             } else {
487                 tokens.add(new IntPtg(number)); //TODO:what if the number is too big to be a short? ..add factory to return Int or Number!
488
}
489         }
490     }
491     
492     private void StringLiteral()
493     {
494         // Can't use match here 'cuz it consumes whitespace
495
// which we need to preserve inside the string.
496
// - pete
497
// Match('"');
498
if (look != '"')
499             Expected("\"");
500         else
501         {
502             GetChar();
503             StringBuffer JavaDoc Token = new StringBuffer JavaDoc();
504             for (;;)
505             {
506                 if (look == '"')
507                 {
508                     GetChar();
509                     SkipWhite(); //potential white space here since it doesnt matter up to the operator
510
if (look == '"')
511                         Token.append("\"");
512                     else
513                         break;
514                 }
515                 else if (look == 0)
516                 {
517                     break;
518                 }
519                 else
520                 {
521                     Token.append(look);
522                     GetChar();
523                 }
524             }
525             tokens.add(new StringPtg(Token.toString()));
526         }
527     }
528     
529     /** Recognize and Translate a Multiply */
530     private void Multiply(){
531         Match('*');
532         Factor();
533         tokens.add(new MultiplyPtg());
534   
535     }
536     
537     
538     /** Recognize and Translate a Divide */
539     private void Divide() {
540         Match('/');
541         Factor();
542         tokens.add(new DividePtg());
543
544     }
545     
546     
547     /** Parse and Translate a Math Term */
548     private void Term(){
549         Factor();
550          while (look == '*' || look == '/' || look == '^' || look == '&') {
551         
552             ///TODO do we need to do anything here??
553
if (look == '*') Multiply();
554             else if (look == '/') Divide();
555             else if (look == '^') Power();
556             else if (look == '&') Concat();
557         }
558     }
559     
560     /** Recognize and Translate an Add */
561     private void Add() {
562         Match('+');
563         Term();
564         tokens.add(new AddPtg());
565     }
566     
567     /** Recognize and Translate a Concatination */
568     private void Concat() {
569         Match('&');
570         Term();
571         tokens.add(new ConcatPtg());
572     }
573     
574     /** Recognize and Translate a test for Equality */
575     private void Equal() {
576         Match('=');
577         Expression();
578         tokens.add(new EqualPtg());
579     }
580     
581     /** Recognize and Translate a Subtract */
582     private void Subtract() {
583         Match('-');
584         Term();
585         tokens.add(new SubtractPtg());
586     }
587
588     private void Power() {
589         Match('^');
590         Term();
591         tokens.add(new PowerPtg());
592     }
593     
594     
595     /** Parse and Translate an Expression */
596     private void Expression() {
597         Term();
598         while (IsAddop(look)) {
599             if (look == '+' ) Add();
600             else if (look == '-') Subtract();
601         }
602         
603         /*
604          * This isn't quite right since it would allow multiple comparison operators.
605          */

606         
607           if(look == '=' || look == '>' || look == '<') {
608                 if (look == '=') Equal();
609               else if (look == '>') GreaterThan();
610               else if (look == '<') LessThan();
611               return;
612           }
613         
614         
615     }
616     
617     /** Recognize and Translate a Greater Than */
618     private void GreaterThan() {
619         Match('>');
620         if(look == '=')
621             GreaterEqual();
622         else {
623             Expression();
624             tokens.add(new GreaterThanPtg());
625         }
626     }
627     
628     /** Recognize and Translate a Less Than */
629     private void LessThan() {
630         Match('<');
631         if(look == '=')
632             LessEqual();
633         else if(look == '>')
634             NotEqual();
635         else {
636             Expression();
637             tokens.add(new LessThanPtg());
638         }
639
640     }
641    
642    /**
643     * Recognize and translate Greater than or Equal
644     *
645     */

646     private void GreaterEqual() {
647         Match('=');
648         Expression();
649         tokens.add(new GreaterEqualPtg());
650     }
651
652     /**
653      * Recognize and translate Less than or Equal
654      *
655      */

656
657     private void LessEqual() {
658         Match('=');
659         Expression();
660         tokens.add(new LessEqualPtg());
661     }
662     
663     /**
664      * Recognize and not Equal
665      *
666      */

667
668     private void NotEqual() {
669         Match('>');
670         Expression();
671         tokens.add(new NotEqualPtg());
672     }
673     
674     //{--------------------------------------------------------------}
675
//{ Parse and Translate an Assignment Statement }
676
/**
677 procedure Assignment;
678 var Name: string[8];
679 begin
680    Name := GetName;
681    Match('=');
682    Expression;
683
684 end;
685      **/

686     
687  
688     /** Initialize */
689     
690     private void init() {
691         GetChar();
692         SkipWhite();
693     }
694     
695     /** API call to execute the parsing of the formula
696      *
697      */

698     public void parse() {
699         synchronized (tokens) {
700             init();
701             Expression();
702         }
703     }
704     
705     
706     /*********************************
707      * PARSER IMPLEMENTATION ENDS HERE
708      * EXCEL SPECIFIC METHODS BELOW
709      *******************************/

710     
711     /** API call to retrive the array of Ptgs created as
712      * a result of the parsing
713      */

714     public Ptg[] getRPNPtg() {
715      return getRPNPtg(FORMULA_TYPE_CELL);
716     }
717     
718     public Ptg[] getRPNPtg(int formulaType) {
719         Node node = createTree();
720         setRootLevelRVA(node, formulaType);
721         setParameterRVA(node,formulaType);
722         return (Ptg[]) tokens.toArray(new Ptg[0]);
723     }
724     
725     private void setRootLevelRVA(Node n, int formulaType) {
726         //Pg 16, excelfileformat.pdf @ openoffice.org
727
Ptg p = (Ptg) n.getValue();
728             if (formulaType == FormulaParser.FORMULA_TYPE_NAMEDRANGE) {
729                 if (p.getDefaultOperandClass() == Ptg.CLASS_REF) {
730                     setClass(n,Ptg.CLASS_REF);
731                 } else {
732                     setClass(n,Ptg.CLASS_ARRAY);
733                 }
734             } else {
735                 setClass(n,Ptg.CLASS_VALUE);
736             }
737         
738     }
739     
740     private void setParameterRVA(Node n, int formulaType) {
741         Ptg p = n.getValue();
742         int numOperands = n.getNumChildren();
743         if (p instanceof AbstractFunctionPtg) {
744             for (int i =0;i<numOperands;i++) {
745                 setParameterRVA(n.getChild(i),((AbstractFunctionPtg)p).getParameterClass(i),formulaType);
746 // if (n.getChild(i).getValue() instanceof AbstractFunctionPtg) {
747
// setParameterRVA(n.getChild(i),formulaType);
748
// }
749
setParameterRVA(n.getChild(i),formulaType);
750             }
751         } else {
752             for (int i =0;i<numOperands;i++) {
753                 setParameterRVA(n.getChild(i),formulaType);
754             }
755         }
756     }
757     private void setParameterRVA(Node n, int expectedClass,int formulaType) {
758         Ptg p = (Ptg) n.getValue();
759         if (expectedClass == Ptg.CLASS_REF) { //pg 15, table 1
760
if (p.getDefaultOperandClass() == Ptg.CLASS_REF ) {
761                 setClass(n, Ptg.CLASS_REF);
762             }
763             if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE) {
764                 if (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED) {
765                     setClass(n,Ptg.CLASS_VALUE);
766                 } else {
767                     setClass(n,Ptg.CLASS_ARRAY);
768                 }
769             }
770             if (p.getDefaultOperandClass() == Ptg.CLASS_ARRAY ) {
771                 setClass(n, Ptg.CLASS_ARRAY);
772             }
773         } else if (expectedClass == Ptg.CLASS_VALUE) { //pg 15, table 2
774
if (formulaType == FORMULA_TYPE_NAMEDRANGE) {
775                 setClass(n,Ptg.CLASS_ARRAY) ;
776             } else {
777                 setClass(n,Ptg.CLASS_VALUE);
778             }
779         } else { //Array class, pg 16.
780
if (p.getDefaultOperandClass() == Ptg.CLASS_VALUE &&
781                  (formulaType==FORMULA_TYPE_CELL || formulaType == FORMULA_TYPE_SHARED)) {
782                  setClass(n,Ptg.CLASS_VALUE);
783             } else {
784                 setClass(n,Ptg.CLASS_ARRAY);
785             }
786         }
787     }
788     
789      private void setClass(Node n, byte theClass) {
790         Ptg p = (Ptg) n.getValue();
791         if (p instanceof AbstractFunctionPtg || !(p instanceof OperationPtg)) {
792             p.setClass(theClass);
793         } else {
794             for (int i =0;i<n.getNumChildren();i++) {
795                 setClass(n.getChild(i),theClass);
796             }
797         }
798      }
799     /**
800      * Convience method which takes in a list then passes it to the other toFormulaString
801      * signature.
802      * @param book workbook for 3D and named references
803      * @param lptgs list of Ptg, can be null or empty
804      * @return a human readable String
805      */

806     public static String JavaDoc toFormulaString(Workbook book, List JavaDoc lptgs) {
807         String JavaDoc retval = null;
808         if (lptgs == null || lptgs.size() == 0) return "#NAME";
809         Ptg[] ptgs = new Ptg[lptgs.size()];
810         ptgs = (Ptg[])lptgs.toArray(ptgs);
811         retval = toFormulaString(book, ptgs);
812         return retval;
813     }
814     
815     /**
816      * Static method to convert an array of Ptgs in RPN order
817      * to a human readable string format in infix mode.
818      * @param book workbook for named and 3D references
819      * @param ptgs array of Ptg, can be null or empty
820      * @return a human readable String
821      */

822     public static String JavaDoc toFormulaString(Workbook book, Ptg[] ptgs) {
823         if (ptgs == null || ptgs.length == 0) return "#NAME";
824         java.util.Stack JavaDoc stack = new java.util.Stack JavaDoc();
825         AttrPtg ifptg = null;
826
827            // Excel allows to have AttrPtg at position 0 (such as Blanks) which
828
// do not have any operands. Skip them.
829
stack.push(ptgs[0].toFormulaString(book));
830                   
831         for (int i = 1; i < ptgs.length; i++) {
832             if (! (ptgs[i] instanceof OperationPtg)) {
833                 stack.push(ptgs[i].toFormulaString(book));
834                 continue;
835             }
836                       
837             if (ptgs[i] instanceof AttrPtg && ((AttrPtg) ptgs[i]).isOptimizedIf()) {
838                 ifptg = (AttrPtg) ptgs[i];
839                 continue;
840             }
841                       
842             final OperationPtg o = (OperationPtg) ptgs[i];
843             final String JavaDoc[] operands = new String JavaDoc[o.getNumberOfOperands()];
844
845             for (int j = operands.length; j > 0; j--) {
846                 //TODO: catch stack underflow and throw parse exception.
847
operands[j - 1] = (String JavaDoc) stack.pop();
848                       }
849
850             stack.push(o.toFormulaString(operands));
851             if (!(o instanceof AbstractFunctionPtg)) continue;
852
853             final AbstractFunctionPtg f = (AbstractFunctionPtg) o;
854             final String JavaDoc fname = f.getName();
855             if (fname == null) continue;
856
857             if ((ifptg != null) && (fname.equals("specialflag"))) {
858                              // this special case will be way different.
859
stack.push(ifptg.toFormulaString(new String JavaDoc[]{(String JavaDoc) stack.pop()}));
860                 continue;
861                       }
862             if (fname.equals("externalflag")) {
863                 final String JavaDoc top = (String JavaDoc) stack.pop();
864                 final int paren = top.indexOf('(');
865                 final int comma = top.indexOf(',');
866                 if (comma == -1) {
867                     final int rparen = top.indexOf(')');
868                     stack.push(top.substring(paren + 1, rparen) + "()");
869                   }
870                 else {
871                     stack.push(top.substring(paren + 1, comma) + '(' +
872                                top.substring(comma + 1));
873             }
874         }
875     }
876         // TODO: catch stack underflow and throw parse exception.
877
return (String JavaDoc) stack.pop();
878     }
879
880
881     /** Create a tree representation of the RPN token array
882      *used to run the class(RVA) change algo
883      */

884     private Node createTree() {
885         java.util.Stack JavaDoc stack = new java.util.Stack JavaDoc();
886         int numPtgs = tokens.size();
887         OperationPtg o;
888         int numOperands;
889         Node[] operands;
890         for (int i=0;i<numPtgs;i++) {
891             if (tokens.get(i) instanceof OperationPtg) {
892                 
893                 o = (OperationPtg) tokens.get(i);
894                 numOperands = o.getNumberOfOperands();
895                 operands = new Node[numOperands];
896                 for (int j=0;j<numOperands;j++) {
897                     operands[numOperands-j-1] = (Node) stack.pop();
898                 }
899                 Node result = new Node(o);
900                 result.setChildren(operands);
901                 stack.push(result);
902             } else {
903                 stack.push(new Node((Ptg)tokens.get(i)));
904             }
905         }
906         return (Node) stack.pop();
907     }
908    
909     /** toString on the parser instance returns the RPN ordered list of tokens
910      * Useful for testing
911      */

912     public String JavaDoc toString() {
913         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
914            for (int i=0;i<tokens.size();i++) {
915             buf.append( ( (Ptg)tokens.get(i)).toFormulaString(book));
916             buf.append(' ');
917         }
918         return buf.toString();
919     }
920     
921 }
922     /** Private helper class, used to create a tree representation of the formula*/
923     class Node {
924         private Ptg value=null;
925         private Node[] children=new Node[0];
926         private int numChild=0;
927         public Node(Ptg val) {
928             value = val;
929         }
930         public void setChildren(Node[] child) {children = child;numChild=child.length;}
931         public int getNumChildren() {return numChild;}
932         public Node getChild(int number) {return children[number];}
933         public Ptg getValue() {return value;}
934     }
935
Popular Tags