1    
2    /* ====================================================================
3     * The Apache Software License, Version 1.1
4     *
5     * Copyright (c) 2002 The Apache Software Foundation.  All rights
6     * reserved.
7     *
8     * Redistribution and use in source and binary forms, with or without
9     * modification, are permitted provided that the following conditions
10    * are met:
11    *
12    * 1. Redistributions of source code must retain the above copyright
13    *    notice, this list of conditions and the following disclaimer.
14    *
15    * 2. Redistributions in binary form must reproduce the above copyright
16    *    notice, this list of conditions and the following disclaimer in
17    *    the documentation and/or other materials provided with the
18    *    distribution.
19    *
20    * 3. The end-user documentation included with the redistribution,
21    *    if any, must include the following acknowledgment:
22    *       "This product includes software developed by the
23    *        Apache Software Foundation (http://www.apache.org/)."
24    *    Alternately, this acknowledgment may appear in the software itself,
25    *    if and wherever such third-party acknowledgments normally appear.
26    *
27    * 4. The names "Apache" and "Apache Software Foundation" and
28    *    "Apache POI" must not be used to endorse or promote products
29    *    derived from this software without prior written permission. For
30    *    written permission, please contact apache@apache.org.
31    *
32    * 5. Products derived from this software may not be called "Apache",
33    *    "Apache POI", nor may "Apache" appear in their name, without
34    *    prior written permission of the Apache Software Foundation.
35    *
36    * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    * SUCH DAMAGE.
48    * ====================================================================
49    *
50    * This software consists of voluntary contributions made by many
51    * individuals on behalf of the Apache Software Foundation.  For more
52    * information on the Apache Software Foundation, please see
53    * <http://www.apache.org/>.
54    */
55   
56   /*
57    * FormulaRecord.java
58    *
59    * Created on October 28, 2001, 5:44 PM
60    */
61   package org.apache.poi.hssf.record;
62   
63   import java.util.Stack;
64   import java.util.List;
65   
66   import org.apache.poi.util.LittleEndian;
67   import org.apache.poi.hssf.record.formula.*;
68   
69   /**
70    * Formula Record - This is not really supported in this release.  Its here for future use.<P>
71    * REFERENCE:  PG 317/444 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P>
72    * @author Andrew C. Oliver (acoliver at apache dot org)
73    * @version 2.0-pre
74    */
75   
76   public class FormulaRecord
77       extends Record
78       implements CellValueRecordInterface, Comparable
79   {
80       
81       public static final boolean EXPERIMENTAL_FORMULA_SUPPORT_ENABLED=true;
82       
83       public static final short sid =
84           0x06;   // docs say 406...because of a bug Microsoft support site article #Q184647)
85       
86       private short             field_1_row;
87       private short             field_2_column;
88       private short             field_3_xf;
89       private double            field_4_value;
90       private short             field_5_options;
91       private int               field_6_zero;
92       private short             field_7_expression_len;
93       private Stack             field_8_parsed_expr;
94       
95       private byte[]            all_data; //if formula support is not enabled then
96                                           //we'll just store/reserialize
97   
98       /** Creates new FormulaRecord */
99   
100      public FormulaRecord()
101      {
102          field_8_parsed_expr = new Stack();
103      }
104  
105      /**
106       * Constructs a Formula record and sets its fields appropriately.
107       *
108       * @param id     id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an "explanation of
109       * this bug in the documentation) or an exception will be throw upon validation
110       * @param size  the size of the data area of the record
111       * @param data  data of the record (should not contain sid/len)
112       */
113  
114      public FormulaRecord(short id, short size, byte [] data)
115      {
116          super(id, size, data);
117      }
118  
119      /**
120       * Constructs a Formula record and sets its fields appropriately.
121       *
122       * @param id     id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an "explanation of
123       * this bug in the documentation) or an exception will be throw upon validation
124       * @param size  the size of the data area of the record
125       * @param data  data of the record (should not contain sid/len)
126       * @param offset of the record's data
127       */
128  
129      public FormulaRecord(short id, short size, byte [] data, int offset)
130      {
131          super(id, size, data, offset);
132      }
133  
134      protected void fillFields(byte [] data, short size, int offset)
135      {
136          if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) {
137          field_1_row            = LittleEndian.getShort(data, 0 + offset);
138          field_2_column         = LittleEndian.getShort(data, 2 + offset);
139          field_3_xf             = LittleEndian.getShort(data, 4 + offset);
140          field_4_value          = LittleEndian.getDouble(data, 6 + offset);
141          field_5_options        = LittleEndian.getShort(data, 14 + offset);
142          field_6_zero           = LittleEndian.getInt(data, 16 + offset);
143          field_7_expression_len = LittleEndian.getShort(data, 20 + offset);
144          field_8_parsed_expr    = getParsedExpressionTokens(data, size,
145                                   offset);
146          
147          } else {
148              all_data = new byte[size+4];
149              LittleEndian.putShort(all_data,0,sid);
150              LittleEndian.putShort(all_data,2,size);
151              System.arraycopy(data,offset,all_data,4,size);
152          }
153  
154      }
155  
156      private Stack getParsedExpressionTokens(byte [] data, short size,
157                                              int offset)
158      {
159          Stack stack = new Stack();
160          int   pos   = 22 + offset;
161  
162          while (pos < size)
163          {
164              Ptg ptg = Ptg.createPtg(data, pos);
165  
166              pos += ptg.getSize();
167              stack.push(ptg);
168          }
169          return stack;
170      }
171  
172      public void setRow(short row)
173      {
174          field_1_row = row;
175      }
176  
177      public void setColumn(short column)
178      {
179          field_2_column = column;
180      }
181  
182      public void setXFIndex(short xf)
183      {
184          field_3_xf = xf;
185      }
186  
187      /**
188       * set the calculated value of the formula
189       *
190       * @param value  calculated value
191       */
192  
193      public void setValue(double value)
194      {
195          field_4_value = value;
196      }
197  
198      /**
199       * set the option flags
200       *
201       * @param options  bitmask
202       */
203  
204      public void setOptions(short options)
205      {
206          field_5_options = options;
207      }
208  
209      /**
210       * set the length (in number of tokens) of the expression
211       * @param len  length
212       */
213  
214      public void setExpressionLength(short len)
215      {
216          field_7_expression_len = len;
217      }
218  
219      public short getRow()
220      {
221          return field_1_row;
222      }
223  
224      public short getColumn()
225      {
226          return field_2_column;
227      }
228  
229      public short getXFIndex()
230      {
231          return field_3_xf;
232      }
233  
234      /**
235       * get the calculated value of the formula
236       *
237       * @return calculated value
238       */
239  
240      public double getValue()
241      {
242          return field_4_value;
243      }
244  
245      /**
246       * get the option flags
247       *
248       * @return bitmask
249       */
250  
251      public short getOptions()
252      {
253          return field_5_options;
254      }
255  
256      /**
257       * get the length (in number of tokens) of the expression
258       * @return  expression length
259       */
260  
261      public short getExpressionLength()
262      {
263          return field_7_expression_len;
264      }
265  
266      /**
267       * push a token onto the stack
268       *
269       * @param ptg  the token
270       */
271  
272      public void pushExpressionToken(Ptg ptg)
273      {
274          field_8_parsed_expr.push(ptg);
275      }
276  
277      /**
278       * pop a token off of the stack
279       *
280       * @return Ptg - the token
281       */
282  
283      public Ptg popExpressionToken()
284      {
285          return ( Ptg ) field_8_parsed_expr.pop();
286      }
287  
288      /**
289       * peek at the token on the top of stack
290       *
291       * @return Ptg - the token
292       */
293  
294      public Ptg peekExpressionToken()
295      {
296          return ( Ptg ) field_8_parsed_expr.peek();
297      }
298  
299      /**
300       * get the size of the stack
301       * @return size of the stack
302       */
303  
304      public int getNumberOfExpressionTokens()
305      {
306          return field_8_parsed_expr.size();
307      }
308  
309      /**
310       * get the stack as a list
311       *
312       * @return list of tokens (casts stack to a list and returns it!)
313       */
314  
315      public List getParsedExpression()
316      {
317          return ( List ) field_8_parsed_expr;
318      }
319  
320      /**
321       * called by constructor, should throw runtime exception in the event of a
322       * record passed with a differing ID.
323       *
324       * @param id alleged id for this record
325       */
326  
327      protected void validateSid(short id)
328      {
329          if (id != sid)
330          {
331              throw new RecordFormatException("NOT A FORMULA RECORD");
332          }
333      }
334  
335      public short getSid()
336      {
337          return sid;
338      }
339  
340      /**
341       * called by the class that is responsible for writing this sucker.
342       * Subclasses should implement this so that their data is passed back in a
343       * byte array.
344       *
345       * @return byte array containing instance data
346       */
347  
348      public int serialize(int offset, byte [] data)
349      {
350          if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) {
351          int ptgSize = getTotalPtgSize();
352  
353          LittleEndian.putShort(data, 0 + offset, sid);
354          LittleEndian.putShort(data, 2 + offset, ( short ) (22 + ptgSize));
355          LittleEndian.putShort(data, 4 + offset, getRow());
356          LittleEndian.putShort(data, 6 + offset, getColumn());
357          LittleEndian.putShort(data, 8 + offset, getXFIndex());
358          LittleEndian.putDouble(data, 10 + offset, getValue());
359          LittleEndian.putShort(data, 18 + offset, getOptions());
360          LittleEndian.putInt(data, 20 + offset, field_6_zero);
361          LittleEndian.putShort(data, 24 + offset, getExpressionLength());
362          serializePtgs(data, 26+offset);
363          } else {
364              System.arraycopy(all_data,0,data,offset,all_data.length);
365          }
366          return getRecordSize();
367      }
368      
369      
370      
371  
372      public int getRecordSize()
373      {
374          int retval =0;
375          
376          if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) {
377              retval = getTotalPtgSize() + 26;
378          } else {
379              retval =all_data.length;
380          }
381          return retval;
382  
383          // return getTotalPtgSize() + 28;
384      }
385  
386      private int getTotalPtgSize()
387      {
388          List list   = getParsedExpression();
389          int  retval = 0;
390  
391          for (int k = 0; k < list.size(); k++)
392          {
393              Ptg ptg = ( Ptg ) list.get(k);
394  
395              retval += ptg.getSize();
396          }
397          return retval;
398      }
399  
400      private void serializePtgs(byte [] data, int offset)
401      {
402          int pos = offset;
403  
404          for (int k = 0; k < field_8_parsed_expr.size(); k++)
405          {
406              Ptg ptg = ( Ptg ) field_8_parsed_expr.get(k);
407  
408              ptg.writeBytes(data, pos);
409              pos += ptg.getSize();
410          }
411      }
412  
413      public boolean isBefore(CellValueRecordInterface i)
414      {
415          if (this.getRow() > i.getRow())
416          {
417              return false;
418          }
419          if ((this.getRow() == i.getRow())
420                  && (this.getColumn() > i.getColumn()))
421          {
422              return false;
423          }
424          if ((this.getRow() == i.getRow())
425                  && (this.getColumn() == i.getColumn()))
426          {
427              return false;
428          }
429          return true;
430      }
431  
432      public boolean isAfter(CellValueRecordInterface i)
433      {
434          if (this.getRow() < i.getRow())
435          {
436              return false;
437          }
438          if ((this.getRow() == i.getRow())
439                  && (this.getColumn() < i.getColumn()))
440          {
441              return false;
442          }
443          if ((this.getRow() == i.getRow())
444                  && (this.getColumn() == i.getColumn()))
445          {
446              return false;
447          }
448          return true;
449      }
450  
451      public boolean isEqual(CellValueRecordInterface i)
452      {
453          return ((this.getRow() == i.getRow())
454                  && (this.getColumn() == i.getColumn()));
455      }
456  
457      public boolean isInValueSection()
458      {
459          return true;
460      }
461  
462      public boolean isValue()
463      {
464          return true;
465      }
466  
467      public int compareTo(Object obj)
468      {
469          CellValueRecordInterface loc = ( CellValueRecordInterface ) obj;
470  
471          if ((this.getRow() == loc.getRow())
472                  && (this.getColumn() == loc.getColumn()))
473          {
474              return 0;
475          }
476          if (this.getRow() < loc.getRow())
477          {
478              return -1;
479          }
480          if (this.getRow() > loc.getRow())
481          {
482              return 1;
483          }
484          if (this.getColumn() < loc.getColumn())
485          {
486              return -1;
487          }
488          if (this.getColumn() > loc.getColumn())
489          {
490              return 1;
491          }
492          return -1;
493      }
494  
495      public boolean equals(Object obj)
496      {
497          if (!(obj instanceof CellValueRecordInterface))
498          {
499              return false;
500          }
501          CellValueRecordInterface loc = ( CellValueRecordInterface ) obj;
502  
503          if ((this.getRow() == loc.getRow())
504                  && (this.getColumn() == loc.getColumn()))
505          {
506              return true;
507          }
508          return false;
509      }
510      
511      
512      public String toString()
513      {
514          StringBuffer buffer = new StringBuffer();
515          if (EXPERIMENTAL_FORMULA_SUPPORT_ENABLED) {
516              buffer.append("[FORMULA]\n");
517              buffer.append("    .row       = ")
518                  .append(Integer.toHexString(getRow())).append("\n");
519              buffer.append("    .column    = ")
520                  .append(Integer.toHexString(getColumn()))
521                  .append("\n");
522              buffer.append("    .xf              = ")
523                  .append(Integer.toHexString(getXFIndex())).append("\n");
524              buffer.append("    .value           = ").append(getValue())
525                  .append("\n");
526              buffer.append("    .options         = ").append(getOptions())
527                  .append("\n");
528              buffer.append("    .zero            = ").append(field_6_zero)
529                  .append("\n");
530              buffer.append("    .expressionlength= ").append(getExpressionLength())
531                  .append("\n");
532              buffer.append("    .numptgsinarray  = ").append(field_8_parsed_expr.size())
533                  .append("\n");
534              
535              
536              for (int k = 0; k < field_8_parsed_expr.size(); k++ ) {
537  /*                buffer.append("formula ").append(k).append(" ")
538                 .append(((Ptg)field_8_parsed_expr.get(k)).toFormulaString());*/
539                  buffer.append("Formula ")
540                  .append(k)
541                  .append("=")
542                  .append(((Ptg)field_8_parsed_expr.get(k)).toString())
543                  .append("\n")
544                  .append(((Ptg)field_8_parsed_expr.get(k)).toDebugString())
545                  .append("\n");                
546              }
547              
548              
549              buffer.append("[/FORMULA]\n");
550          } else {
551              buffer.append(super.toString());
552          }
553          return buffer.toString();
554      }
555      
556  }
557