Fix 43670, 44501 - Fix how HDGF deals with trailing data in the list of chunk headers
[poi.git] / src / documentation / content / xdocs / hssf / eval-devguide.xml
blob29c33b8a203bb787dd82c408f102d2545bd35be9
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!--
3    ====================================================================
4    Licensed to the Apache Software Foundation (ASF) under one or more
5    contributor license agreements.  See the NOTICE file distributed with
6    this work for additional information regarding copyright ownership.
7    The ASF licenses this file to You under the Apache License, Version 2.0
8    (the "License"); you may not use this file except in compliance with
9    the License.  You may obtain a copy of the License at
11        http://www.apache.org/licenses/LICENSE-2.0
13    Unless required by applicable law or agreed to in writing, software
14    distributed under the License is distributed on an "AS IS" BASIS,
15    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16    See the License for the specific language governing permissions and
17    limitations under the License.
18    ====================================================================
19 -->
20 <!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V1.1//EN" "../dtd/document-v11.dtd">
22 <document>
23     <header>
24         <title>Developing Formula Evaluation</title>
25         <authors>
26                         <person email="amoweb@yahoo.com" name="Amol Deshmukh" id="AD"/>
27         </authors>
28     </header>
29     <body>
30         <section><title>Introduction</title>
31                 <p>This document is for developers wishing to contribute to the 
32                         FormulaEvaluator API functionality.</p>
33                 <p>Currently, contribution is desired for implementing the standard MS 
34                         excel functions. Place holder classes for these have been created, 
35                         contributors only need to insert implementation for the 
36                         individual "evaluate()" methods that do the actual evaluation.</p>
37         </section>
38         <section><title>Overview of FormulaEvaluator </title>
39                 <p>Briefly, a formula string (along with the sheet and workbook that 
40                         form the context in which the formula is evaluated) is first parsed 
41                         into RPN tokens using the FormulaParser class in POI-HSSF main. 
42                         (If you dont know what RPN tokens are, now is a good time to 
43                         read <link href="http://www-stone.ch.cam.ac.uk/documentation/rrf/rpn.html">
44                         this</link>.)
45                 </p>
46                 <section><title> The big picture</title>
47                         <p>RPN tokens are mapped to Eval classes. (Class hierarchy for the Evals 
48                                 is best understood if you view the class diagram in a class diagram 
49                                 viewer.) Depending on the type of RPN token (also called as Ptgs 
50                                 henceforth since that is what the FormulaParser calls the classes) a 
51                                 specific type of Eval wrapper is constructed to wrap the RPN token and 
52                                 is pushed on the stack.... UNLESS the Ptg is an OperationPtg. If it is an 
53                                 OperationPtg, an OperationEval instance is created for the specific 
54                                 type of OperationPtg. And depending on how many operands it takes, 
55                                 that many Evals are popped of the stack and passed in an array to 
56                                 the OperationEval instance's evaluate method which returns an Eval 
57                                 of subtype ValueEval.Thus an operation in the formula is evaluated. </p>
58                                 <note> An Eval is of subinterface ValueEval or OperationEval. 
59                                 Operands are always ValueEvals, Operations are always OperationEvals.</note> 
60                                 <p><code>OperationEval.evaluate(Eval[])</code> returns an Eval which is supposed 
61                                 to be of type ValueEval (actually since ValueEval is an interface, 
62                                 the return value is instance of one of the implementations of 
63                                 ValueEval). The valueEval resulting from evaluate() is pushed on the 
64                                 stack and the next RPN token is evaluated.... this continues till 
65                                 eventually there are no more RPN tokens at which point, if the formula 
66                                 string was correctly parsed, there should be just one Eval on the 
67                                 stack - which contains the result of evaluating the formula.</p>
68                         <p>Ofcourse I glossed over the details of how AreaPtg and ReferencePtg 
69                                 are handled a little differently, but the code should be self 
70                                 explanatory for that. Very briefly, the cells included in AreaPtg and 
71                                 RefPtg are examined and their values are populated in individual 
72                                 ValueEval objects which are set into the AreaEval and RefEval (ok, 
73                                 since AreaEval and RefEval are interfaces, the implementations of 
74                                 AreaEval and RefEval - but you'll figure all that out from the code)</p>
75                         <p>OperationEvals for the standard operators have been implemented and tested.</p>
76                 </section>
77                 <section><title> FunctionEval and FuncVarEval</title>
78                         <p>FunctionEval is an abstract super class of FuncVarEval. The reason for this is that in the FormulaParser Ptg classes, there are two Ptgs, FuncPtg and FuncVarPtg. In my tests, I did not see FuncPtg being used so there is no corresponding FuncEval right now. But in case the need arises for a FuncVal class, FuncEval and FuncVarEval need to be isolated with a common interface/abstract class, hence FunctionEval.</p>
79                         <p>FunctionEval also contains the mapping of which function class maps to which function index. This mapping has been done for all the functions, so all you really have to do is implement the evaluate method in the function class that has not already been implemented. The Function indexes are defined in AbstractFunctionPtg class in POI main.</p>
80                 </section>
81         </section>
82         <section><title>Walkthrough of an "evaluate()" implementation.</title>
83                 <p>So here is the fun part - lets walk through the implementation of the excel 
84                         function... <strong>SQRT()</strong> </p>
85                 <section><title>The Code</title>
86                 <source>
87 public class Sqrt extends NumericFunction {
88     
89     private static final ValueEvalToNumericXlator NUM_XLATOR = 
90         new ValueEvalToNumericXlator((short)
91                 ( ValueEvalToNumericXlator.BOOL_IS_PARSED 
92                 | ValueEvalToNumericXlator.EVALUATED_REF_BOOL_IS_PARSED
93                 | ValueEvalToNumericXlator.EVALUATED_REF_STRING_IS_PARSED
94                 | ValueEvalToNumericXlator.REF_BOOL_IS_PARSED
95                 | ValueEvalToNumericXlator.STRING_IS_PARSED
96                 ));
98     protected ValueEvalToNumericXlator getXlator() {
99         return NUM_XLATOR;
100     }
102     public Eval evaluate(Eval[] operands, int srcRow, short srcCol) {
103         double d = 0;
104         ValueEval retval = null;
105         
106         switch (operands.length) {
107         default:
108             retval = ErrorEval.VALUE_INVALID;
109             break;
110         case 1:
111             ValueEval ve = singleOperandEvaluate(operands[0], srcRow, srcCol);
112             if (ve instanceof NumericValueEval) {
113                 NumericValueEval ne = (NumericValueEval) ve;
114                 d = ne.getNumberValue();
115             }
116             else if (ve instanceof BlankEval) {
117                 // do nothing
118             }
119             else {
120                 retval = ErrorEval.NUM_ERROR;
121             }
122         }
123         
124         if (retval == null) {
125             d = Math.sqrt(d);
126             retval = (Double.isNaN(d)) ? (ValueEval) ErrorEval.VALUE_INVALID : new NumberEval(d);
127         }
128         return retval;
129     }
132         </source>
133                 </section>
134                 <section><title>Implementation Details</title>
135                 <ul>
136                         <li>The first thing to realise is that classes already exist, even for functions that are not yet implemented.
137                         Just that they extend from DefaultFunctionImpl whose behaviour is to return an ErrorEval.FUNCTION_NOT_IMPLEMENTED value.</li>
138                         <li>In order to implement SQRT(..), we need to: a. Extend from the correct Abstract super class; b. implement the evaluate(..) method</li>
139                         <li>Hence we extend SQRT(..) from the predefined class NumericFunction</li>
140                         <li>Since SQRT(..) takes a single argument, we verify the length of the operands array else set the return value to ErrorEval.VALUE_INVALID</li>
141                         <li>Next we normalize each operand to a limited set of ValueEval subtypes, specifically, we call the function 
142                         <code>singleOperandEvaluate(..)</code> to do conversions of different value eval types to one of: NumericValueEval,
143                         BlankEval and ErrorEval. The conversion logic is configured by a ValueEvalToNumericXlator instance which
144                         is returned by the Factory method: <code>getXlator(..)</code> The flags used to create the ValueEvalToNumericXlator
145                         instance are briefly explained as follows:
146                         BOOL_IS_PARSED means whether this function treats Boolean values as 1, 
147                         REF_BOOL_IS_PARSED means whether Boolean values in cell references are parsed or not.
148                         So also, EVALUATED_REF_BOOL_IS_PARSED means if the operand was a RefEval that was assigned a
149                         Boolean value as a result of evaluation of the formula that it contained.
150                         eg. SQRT(TRUE) returns 1: This means BOOL_IS_PARSED should be set.
151                         SQRT(A1) returns 1 when A1 has TRUE: This means REF_BOOL_IS_PARSED should be set.
152                         SQRT(A1) returns 1 when A1 has a formula that evaluates to TRUE: This means EVALUATED_REF_BOOL_IS_PARSED should be set.
153                         If the flag is not set for a particular case, that case is ignored (treated as if the cell is blank) _unless_
154                         there is a flag like: STRING_IS_INVALID_VALUE (which means that Strings should be treated as resulting in VALUE_INVALID ErrorEval)
155                         </li>
156                         <li>Next perform the appropriate Math function on the double value (if an error didnt occur already).</li>
157                         <li>Finally before returning the NumberEval wrapping the double value that 
158                                 you computed, do one final check to see if the double is a NaN, (or if it is "Infinite")
159                                 If it is return the appropriate ErrorEval instance. Note: The OpenOffice.org error codes
160                                 should NOT be preferred. Instead use the excel specific error codes like VALUE_INVALID, NUM_ERROR, DIV_ZERO etc. 
161                                 (Thanks to Avik for bringing this issue up early!) The Oo.o ErrorCodes will be removed (if they havent already been :)</li>
162                 </ul>
163                 </section>
164                 <section><title>Modelling Excel Semantics</title>
165                         <p>Strings are ignored. Booleans are ignored!!!. Actually here's the info on Bools: 
166                                 if you have formula: "=TRUE+1", it evaluates to 2. 
167                                 So also, when you use TRUE like this: "=SUM(1,TRUE)", you see the result is: 2. 
168                                 So TRUE means 1 when doing numeric calculations, right? 
169                                 Wrong!
170                                 Because when you use TRUE in referenced cells with arithmetic functions, it evaluates to blank - meaning it is not evaluated - as if it was string or a blank cell. 
171                                 eg. "=SUM(1,A1)" when A1 is TRUE evaluates to 1.
172                                 This behaviour changes depending on which function you are using. eg. SQRT(..) that was 
173                                 described earlier treats a TRUE as 1 in all cases. This is why the configurable ValueEvalToNumericXlator
174                                 class had to be written.
175                                 </p>
176                         <p>Note that when you are extending from an abstract function class like
177                         NumericFunction (rather than implementing the interface o.a.p.hssf.record.formula.eval.Function directly)
178                         you can use the utility methods in the super class - singleOperandEvaluate(..) - to quickly
179                         reduce the different ValueEval subtypes to a small set of possible types. However when
180                         implemenitng the Function interface directly, you will have to handle the possiblity
181                         of all different ValueEval subtypes being sent in as 'operands'. (Hard to put this in
182                         word, please have a look at the code for NumericFunction for an example of
183                         how/why different ValueEvals need to be handled)
184                         </p>
185                 </section>
186         </section>
187         <section><title>Testing Framework</title>
188         <p>Automated testing of the implemented Function is easy.
189         The source code for this is in the file: o.a.p.h.record.formula.GenericFormulaTestCase.java
190         This class has a reference to the test xls file (not /a/ test xls, /the/ test xls :)
191         which may need to be changed for your environment. Once you do that, in the test xls,
192         locate the entry for the function that you have implemented and enter different tests 
193         in a cell in the FORMULA row. Then copy the "value of" the formula that you entered in the
194         cell just below it (this is easily done in excel as: 
195         [copy the formula cell] > [go to cell below] > Edit > Paste Special > Values > "ok").
196         You can enter multiple such formulas and paste their values in the cell below and the
197         test framework will automatically test if the formula evaluation matches the expected
198         value (Again, hard to put in words, so if you will, please take time to quickly look
199         at the code and the currently entered tests in the patch attachment "FormulaEvalTestData.xls" 
200         file).
201         </p>    
202         </section>
203         </body>
204 </document>