001: /* ====================================================================
002: Licensed to the Apache Software Foundation (ASF) under one or more
003: contributor license agreements. See the NOTICE file distributed with
004: this work for additional information regarding copyright ownership.
005: The ASF licenses this file to You under the Apache License, Version 2.0
006: (the "License"); you may not use this file except in compliance with
007: the License. You may obtain a copy of the License at
008:
009: http://www.apache.org/licenses/LICENSE-2.0
010:
011: Unless required by applicable law or agreed to in writing, software
012: distributed under the License is distributed on an "AS IS" BASIS,
013: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: See the License for the specific language governing permissions and
015: limitations under the License.
016: ==================================================================== */
017:
018: /*
019: * FormulaRecord.java
020: *
021: * Created on October 28, 2001, 5:44 PM
022: */
023: package org.apache.poi.hssf.record;
024:
025: import java.util.List;
026: import java.util.Stack;
027:
028: import org.apache.poi.hssf.record.formula.Ptg;
029: import org.apache.poi.util.BitField;
030: import org.apache.poi.util.BitFieldFactory;
031: import org.apache.poi.util.LittleEndian;
032:
033: /**
034: * Formula Record.
035: * REFERENCE: PG 317/444 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)<P>
036: * @author Andrew C. Oliver (acoliver at apache dot org)
037: * @author Jason Height (jheight at chariot dot net dot au)
038: * @version 2.0-pre
039: */
040:
041: public class FormulaRecord extends Record implements
042: CellValueRecordInterface, Comparable {
043:
044: public static final short sid = 0x06; // docs say 406...because of a bug Microsoft support site article #Q184647)
045:
046: //private short field_1_row;
047: private int field_1_row;
048: private short field_2_column;
049: private short field_3_xf;
050: private double field_4_value;
051: private short field_5_options;
052: private BitField alwaysCalc = BitFieldFactory.getInstance(0x0001);
053: private BitField calcOnLoad = BitFieldFactory.getInstance(0x0002);
054: private BitField sharedFormula = BitFieldFactory
055: .getInstance(0x0008);
056: private int field_6_zero;
057: private short field_7_expression_len;
058: private Stack field_8_parsed_expr;
059:
060: /**
061: * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly
062: */
063: private byte[] value_data;
064: private byte[] all_data; //if formula support is not enabled then
065:
066: //we'll just store/reserialize
067:
068: /** Creates new FormulaRecord */
069:
070: public FormulaRecord() {
071: field_8_parsed_expr = new Stack();
072: }
073:
074: /**
075: * Constructs a Formula record and sets its fields appropriately.
076: * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an
077: * "explanation of this bug in the documentation) or an exception
078: * will be throw upon validation
079: *
080: * @param in the RecordInputstream to read the record from
081: */
082:
083: public FormulaRecord(RecordInputStream in) {
084: super (in);
085: }
086:
087: protected void fillFields(RecordInputStream in) {
088: try {
089: field_1_row = in.readUShort();
090: field_2_column = in.readShort();
091: field_3_xf = in.readShort();
092: field_4_value = in.readDouble();
093: field_5_options = in.readShort();
094:
095: if (Double.isNaN(field_4_value)) {
096: value_data = in.getNANData();
097: }
098:
099: field_6_zero = in.readInt();
100: field_7_expression_len = in.readShort();
101: field_8_parsed_expr = Ptg.createParsedExpressionTokens(
102: field_7_expression_len, in);
103: } catch (java.lang.UnsupportedOperationException uoe) {
104: throw new RecordFormatException(uoe);
105: }
106: }
107:
108: //public void setRow(short row)
109: public void setRow(int row) {
110: field_1_row = row;
111: }
112:
113: public void setColumn(short column) {
114: field_2_column = column;
115: }
116:
117: public void setXFIndex(short xf) {
118: field_3_xf = xf;
119: }
120:
121: /**
122: * set the calculated value of the formula
123: *
124: * @param value calculated value
125: */
126:
127: public void setValue(double value) {
128: field_4_value = value;
129: }
130:
131: /**
132: * set the option flags
133: *
134: * @param options bitmask
135: */
136:
137: public void setOptions(short options) {
138: field_5_options = options;
139: }
140:
141: /**
142: * set the length (in number of tokens) of the expression
143: * @param len length
144: */
145:
146: public void setExpressionLength(short len) {
147: field_7_expression_len = len;
148: }
149:
150: //public short getRow()
151: public int getRow() {
152: return field_1_row;
153: }
154:
155: public short getColumn() {
156: return field_2_column;
157: }
158:
159: public short getXFIndex() {
160: return field_3_xf;
161: }
162:
163: /**
164: * get the calculated value of the formula
165: *
166: * @return calculated value
167: */
168:
169: public double getValue() {
170: return field_4_value;
171: }
172:
173: /**
174: * get the option flags
175: *
176: * @return bitmask
177: */
178:
179: public short getOptions() {
180: return field_5_options;
181: }
182:
183: public boolean isSharedFormula() {
184: return sharedFormula.isSet(field_5_options);
185: }
186:
187: public void setSharedFormula(boolean flag) {
188: sharedFormula.setBoolean(field_5_options, flag);
189: }
190:
191: /**
192: * get the length (in number of tokens) of the expression
193: * @return expression length
194: */
195:
196: public short getExpressionLength() {
197: return field_7_expression_len;
198: }
199:
200: /**
201: * push a token onto the stack
202: *
203: * @param ptg the token
204: */
205:
206: public void pushExpressionToken(Ptg ptg) {
207: field_8_parsed_expr.push(ptg);
208: }
209:
210: /**
211: * pop a token off of the stack
212: *
213: * @return Ptg - the token
214: */
215:
216: public Ptg popExpressionToken() {
217: return (Ptg) field_8_parsed_expr.pop();
218: }
219:
220: /**
221: * peek at the token on the top of stack
222: *
223: * @return Ptg - the token
224: */
225:
226: public Ptg peekExpressionToken() {
227: return (Ptg) field_8_parsed_expr.peek();
228: }
229:
230: /**
231: * get the size of the stack
232: * @return size of the stack
233: */
234:
235: public int getNumberOfExpressionTokens() {
236: if (this .field_8_parsed_expr == null) {
237: return 0;
238: } else {
239: return field_8_parsed_expr.size();
240: }
241: }
242:
243: /**
244: * get the stack as a list
245: *
246: * @return list of tokens (casts stack to a list and returns it!)
247: * this method can return null is we are unable to create Ptgs from
248: * existing excel file
249: * callers should check for null!
250: */
251:
252: public List getParsedExpression() {
253: return field_8_parsed_expr;
254: }
255:
256: public void setParsedExpression(Stack ptgs) {
257: field_8_parsed_expr = ptgs;
258: }
259:
260: /**
261: * called by constructor, should throw runtime exception in the event of a
262: * record passed with a differing ID.
263: *
264: * @param id alleged id for this record
265: */
266:
267: protected void validateSid(short id) {
268: if (id != sid) {
269: throw new RecordFormatException("NOT A FORMULA RECORD");
270: }
271: }
272:
273: public short getSid() {
274: return sid;
275: }
276:
277: /**
278: * called by the class that is responsible for writing this sucker.
279: * Subclasses should implement this so that their data is passed back in a
280: * byte array.
281: *
282: * @return byte array containing instance data
283: */
284:
285: public int serialize(int offset, byte[] data) {
286: if (this .field_8_parsed_expr != null) {
287: int ptgSize = getTotalPtgSize();
288:
289: LittleEndian.putShort(data, 0 + offset, sid);
290: LittleEndian.putShort(data, 2 + offset,
291: (short) (22 + ptgSize));
292: //LittleEndian.putShort(data, 4 + offset, getRow());
293: LittleEndian.putShort(data, 4 + offset, (short) getRow());
294: LittleEndian.putShort(data, 6 + offset, getColumn());
295: LittleEndian.putShort(data, 8 + offset, getXFIndex());
296:
297: //only reserialize if the value is still NaN and we have old nan data
298: if (Double.isNaN(this .getValue()) && value_data != null) {
299: System.arraycopy(value_data, 0, data, 10 + offset,
300: value_data.length);
301: } else {
302: LittleEndian
303: .putDouble(data, 10 + offset, field_4_value);
304: }
305:
306: LittleEndian.putShort(data, 18 + offset, getOptions());
307:
308: //when writing the chn field (offset 20), it's supposed to be 0 but ignored on read
309: //Microsoft Excel Developer's Kit Page 318
310: LittleEndian.putInt(data, 20 + offset, 0);
311: LittleEndian.putShort(data, 24 + offset,
312: getExpressionLength());
313: Ptg.serializePtgStack(field_8_parsed_expr, data,
314: 26 + offset);
315: } else {
316: System
317: .arraycopy(all_data, 0, data, offset,
318: all_data.length);
319: }
320: return getRecordSize();
321: }
322:
323: public int getRecordSize() {
324: int retval = 0;
325:
326: if (this .field_8_parsed_expr != null) {
327: retval = getTotalPtgSize() + 26;
328: } else {
329: retval = all_data.length;
330: }
331: return retval;
332:
333: // return getTotalPtgSize() + 28;
334: }
335:
336: private int getTotalPtgSize() {
337: List list = getParsedExpression();
338: int retval = 0;
339:
340: for (int k = 0; k < list.size(); k++) {
341: Ptg ptg = (Ptg) list.get(k);
342:
343: retval += ptg.getSize();
344: }
345: return retval;
346: }
347:
348: public boolean isBefore(CellValueRecordInterface i) {
349: if (this .getRow() > i.getRow()) {
350: return false;
351: }
352: if ((this .getRow() == i.getRow())
353: && (this .getColumn() > i.getColumn())) {
354: return false;
355: }
356: if ((this .getRow() == i.getRow())
357: && (this .getColumn() == i.getColumn())) {
358: return false;
359: }
360: return true;
361: }
362:
363: public boolean isAfter(CellValueRecordInterface i) {
364: if (this .getRow() < i.getRow()) {
365: return false;
366: }
367: if ((this .getRow() == i.getRow())
368: && (this .getColumn() < i.getColumn())) {
369: return false;
370: }
371: if ((this .getRow() == i.getRow())
372: && (this .getColumn() == i.getColumn())) {
373: return false;
374: }
375: return true;
376: }
377:
378: public boolean isEqual(CellValueRecordInterface i) {
379: return ((this .getRow() == i.getRow()) && (this .getColumn() == i
380: .getColumn()));
381: }
382:
383: public boolean isInValueSection() {
384: return true;
385: }
386:
387: public boolean isValue() {
388: return true;
389: }
390:
391: public int compareTo(Object obj) {
392: CellValueRecordInterface loc = (CellValueRecordInterface) obj;
393:
394: if ((this .getRow() == loc.getRow())
395: && (this .getColumn() == loc.getColumn())) {
396: return 0;
397: }
398: if (this .getRow() < loc.getRow()) {
399: return -1;
400: }
401: if (this .getRow() > loc.getRow()) {
402: return 1;
403: }
404: if (this .getColumn() < loc.getColumn()) {
405: return -1;
406: }
407: if (this .getColumn() > loc.getColumn()) {
408: return 1;
409: }
410: return -1;
411: }
412:
413: public boolean equals(Object obj) {
414: if (!(obj instanceof CellValueRecordInterface)) {
415: return false;
416: }
417: CellValueRecordInterface loc = (CellValueRecordInterface) obj;
418:
419: if ((this .getRow() == loc.getRow())
420: && (this .getColumn() == loc.getColumn())) {
421: return true;
422: }
423: return false;
424: }
425:
426: public String toString() {
427: StringBuffer buffer = new StringBuffer();
428: buffer.append("[FORMULA]\n");
429: buffer.append(" .row = ").append(
430: Integer.toHexString(getRow())).append("\n");
431: buffer.append(" .column = ").append(
432: Integer.toHexString(getColumn())).append("\n");
433: buffer.append(" .xf = ").append(
434: Integer.toHexString(getXFIndex())).append("\n");
435: if (Double.isNaN(this .getValue()) && value_data != null)
436: buffer.append(" .value (NaN) = ").append(
437: org.apache.poi.util.HexDump.dump(value_data, 0, 0))
438: .append("\n");
439: else
440: buffer.append(" .value = ").append(getValue())
441: .append("\n");
442: buffer.append(" .options = ").append(getOptions())
443: .append("\n");
444: buffer.append(" .alwaysCalc = ").append(
445: alwaysCalc.isSet(getOptions())).append("\n");
446: buffer.append(" .calcOnLoad = ").append(
447: calcOnLoad.isSet(getOptions())).append("\n");
448: buffer.append(" .sharedFormula = ").append(
449: sharedFormula.isSet(getOptions())).append("\n");
450: buffer.append(" .zero = ").append(field_6_zero)
451: .append("\n");
452: buffer.append(" .expressionlength= ").append(
453: getExpressionLength()).append("\n");
454:
455: if (field_8_parsed_expr != null) {
456: buffer.append(" .numptgsinarray = ").append(
457: field_8_parsed_expr.size()).append("\n");
458:
459: for (int k = 0; k < field_8_parsed_expr.size(); k++) {
460: buffer.append(" Ptg(").append(k).append(")=")
461: .append(field_8_parsed_expr.get(k).toString())
462: .append("\n").append(
463: ((Ptg) field_8_parsed_expr.get(k))
464: .toDebugString()).append("\n");
465: }
466: } else {
467: buffer.append("Formula full data \n").append(
468: org.apache.poi.util.HexDump.dump(this .all_data, 0,
469: 0));
470: }
471:
472: buffer.append("[/FORMULA]\n");
473: return buffer.toString();
474: }
475:
476: public Object clone() {
477: FormulaRecord rec = new FormulaRecord();
478: rec.field_1_row = field_1_row;
479: rec.field_2_column = field_2_column;
480: rec.field_3_xf = field_3_xf;
481: rec.field_4_value = field_4_value;
482: rec.field_5_options = field_5_options;
483: rec.field_6_zero = field_6_zero;
484: rec.field_7_expression_len = field_7_expression_len;
485: rec.field_8_parsed_expr = new Stack();
486: int size = 0;
487: if (field_8_parsed_expr != null)
488: size = field_8_parsed_expr.size();
489: for (int i = 0; i < size; i++) {
490: Ptg ptg = (Ptg) ((Ptg) field_8_parsed_expr.get(i)).clone();
491: rec.field_8_parsed_expr.add(i, ptg);
492: }
493: rec.value_data = value_data;
494: rec.all_data = all_data;
495: return rec;
496: }
497:
498: }
|