/* ==================================================================== Licensed to the Apache Software Foundation (ASF) Under one or more contributor license agreements. See the NOTICE file distributed with this work for Additional inFormation regarding copyright ownership. The ASF licenses this file to You Under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed Under the License is distributed on an "AS Is" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations Under the License. ==================================================================== */ /* * FormulaRecord.java * * Created on October 28, 2001, 5:44 PM */ namespace HH.WMS.Utils.NPOI.HSSF.Record { using System; using System.Text; using HH.WMS.Utils.NPOI.SS.Formula.Eval; using HH.WMS.Utils.NPOI.SS.Formula.PTG; using HH.WMS.Utils.NPOI.Util; /** * Manages the cached formula result values of other types besides numeric. * Excel encodes the same 8 bytes that would be field_4_value with various NaN * values that are decoded/encoded by this class. */ internal class SpecialCachedValue { /** deliberately chosen by Excel in order to encode other values within Double NaNs */ private const long BIT_MARKER = unchecked((long)0xFFFF000000000000L); private const int VARIABLE_DATA_LENGTH = 6; private const int DATA_INDEX = 2; public const int STRING = 0; public const int BOOLEAN = 1; public const int ERROR_CODE = 2; public const int EMPTY = 3; private byte[] _variableData; private SpecialCachedValue(byte[] data) { _variableData = data; } public int GetTypeCode() { return _variableData[0]; } /** * @return null if the double value encoded by valueLongBits * is a normal (non NaN) double value. */ public static SpecialCachedValue Create(long valueLongBits) { if ((BIT_MARKER & valueLongBits) != BIT_MARKER) { return null; } byte[] result = new byte[VARIABLE_DATA_LENGTH]; long x = valueLongBits; for (int i = 0; i < VARIABLE_DATA_LENGTH; i++) { result[i] = (byte)x; x >>= 8; } switch (result[0]) { case STRING: case BOOLEAN: case ERROR_CODE: case EMPTY: break; default: throw new RecordFormatException("Bad special value code (" + result[0] + ")"); } return new SpecialCachedValue(result); } //public void Serialize(byte[] data, int offset) //{ // System.Array.Copy(_variableData, 0, data, offset, VARIABLE_DATA_LENGTH); // LittleEndian.PutUShort(data, offset + VARIABLE_DATA_LENGTH, 0xFFFF); //} public void Serialize(ILittleEndianOutput out1) { out1.Write(_variableData); out1.WriteShort(0xFFFF); } public String FormatDebugString { get { return FormatValue + ' ' + HexDump.ToHex(_variableData); } } private String FormatValue { get { int typeCode = GetTypeCode(); switch (typeCode) { case STRING: return ""; case BOOLEAN: return DataValue == 0 ? "FALSE" : "TRUE"; case ERROR_CODE: return ErrorEval.GetText(DataValue); case EMPTY: return ""; } return "#error(type=" + typeCode + ")#"; } } private int DataValue { get { return _variableData[DATA_INDEX]; } } public static SpecialCachedValue CreateCachedEmptyValue() { return Create(EMPTY, 0); } public static SpecialCachedValue CreateForString() { return Create(STRING, 0); } public static SpecialCachedValue CreateCachedBoolean(bool b) { return Create(BOOLEAN, b ? 1 : 0); } public static SpecialCachedValue CreateCachedErrorCode(int errorCode) { return Create(ERROR_CODE, errorCode); } private static SpecialCachedValue Create(int code, int data) { byte[] vd = { (byte) code, 0, (byte) data, 0, 0, 0, }; return new SpecialCachedValue(vd); } public override String ToString() { StringBuilder sb = new StringBuilder(64); sb.Append(GetType().Name); sb.Append('[').Append(FormatValue).Append(']'); return sb.ToString(); } public HH.WMS.Utils.NPOI.SS.UserModel.CellType GetValueType() { int typeCode = GetTypeCode(); switch (typeCode) { case STRING: return HH.WMS.Utils.NPOI.SS.UserModel.CellType.STRING; case BOOLEAN: return HH.WMS.Utils.NPOI.SS.UserModel.CellType.BOOLEAN; case ERROR_CODE: return HH.WMS.Utils.NPOI.SS.UserModel.CellType.ERROR; case EMPTY: return HH.WMS.Utils.NPOI.SS.UserModel.CellType.STRING; // is this correct? } throw new InvalidOperationException("Unexpected type id (" + typeCode + ")"); } public bool GetBooleanValue() { if (GetTypeCode() != BOOLEAN) { throw new InvalidOperationException("Not a bool cached value - " + FormatValue); } return DataValue != 0; } public int GetErrorValue() { if (GetTypeCode() != ERROR_CODE) { throw new InvalidOperationException("Not an error cached value - " + FormatValue); } return DataValue; } } /** * Formula Record. * REFERENCE: PG 317/444 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2) * @author Andrew C. Oliver (acoliver at apache dot org) * @author Jason Height (jheight at chariot dot net dot au) * @version 2.0-pre */ [Serializable] public class FormulaRecord : CellRecord { public const short sid = 0x06; // docs say 406...because of a bug Microsoft support site article #Q184647) private static int FIXED_SIZE = 14; private double field_4_value; private short field_5_options; private BitField alwaysCalc = BitFieldFactory.GetInstance(0x0001); private BitField calcOnLoad = BitFieldFactory.GetInstance(0x0002); private BitField sharedFormula = BitFieldFactory.GetInstance(0x0008); private int field_6_zero; [NonSerialized] private HH.WMS.Utils.NPOI.SS.Formula.Formula field_8_parsed_expr; /** * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly */ [NonSerialized] private SpecialCachedValue specialCachedValue; /* * Since the NaN support seems sketchy (different constants) we'll store and spit it out directly */ // fix warning CS0169 "never used": private byte[] value_data; // fix warning CS0169 "never used": private byte[] all_data; //if formula support is not enabled then //we'll just store/reSerialize /** Creates new FormulaRecord */ public FormulaRecord() { field_8_parsed_expr = HH.WMS.Utils.NPOI.SS.Formula.Formula.Create(Ptg.EMPTY_PTG_ARRAY); } /** * Constructs a Formula record and Sets its fields appropriately. * Note - id must be 0x06 (NOT 0x406 see MSKB #Q184647 for an * "explanation of this bug in the documentation) or an exception * will be throw upon validation * * @param in the RecordInputstream to Read the record from */ public FormulaRecord(RecordInputStream in1):base(in1) { long valueLongBits = in1.ReadLong(); field_5_options = in1.ReadShort(); specialCachedValue = SpecialCachedValue.Create(valueLongBits); if (specialCachedValue == null) { field_4_value = BitConverter.Int64BitsToDouble(valueLongBits); } field_6_zero = in1.ReadInt(); int field_7_expression_len = in1.ReadShort(); field_8_parsed_expr = HH.WMS.Utils.NPOI.SS.Formula.Formula.Read(field_7_expression_len, in1,in1.Available()); } /** * @return true if this {@link FormulaRecord} is followed by a * {@link StringRecord} representing the cached text result of the formula * evaluation. */ public bool HasCachedResultString { get { if (specialCachedValue == null) { return false; } return specialCachedValue.GetTypeCode() == SpecialCachedValue.STRING; } } public void SetParsedExpression(Ptg[] ptgs) { field_8_parsed_expr = HH.WMS.Utils.NPOI.SS.Formula.Formula.Create(ptgs); } public void SetSharedFormula(bool flag) { field_5_options = sharedFormula.SetShortBoolean(field_5_options, flag); } /** * Get the calculated value of the formula * * @return calculated value */ public double Value { get { return field_4_value; } set { field_4_value = value; specialCachedValue = null; } } /** * Get the option flags * * @return bitmask */ public short Options { get { return field_5_options; } set { field_5_options = value; } } public bool IsSharedFormula { get { return sharedFormula.IsSet(field_5_options); } set { field_5_options = sharedFormula.SetShortBoolean(field_5_options, value); } } public bool IsAlwaysCalc { get { return alwaysCalc.IsSet(field_5_options); } set { field_5_options = alwaysCalc.SetShortBoolean(field_5_options, value); } } public bool IsCalcOnLoad { get { return calcOnLoad.IsSet(field_5_options); } set { field_5_options = calcOnLoad.SetShortBoolean(field_5_options, value); } } /** * Get the stack as a list * * @return list of tokens (casts stack to a list and returns it!) * this method can return null Is we are Unable to Create Ptgs from * existing excel file * callers should Check for null! */ public Ptg[] ParsedExpression { get { return (Ptg[])field_8_parsed_expr.Tokens; } set { field_8_parsed_expr = HH.WMS.Utils.NPOI.SS.Formula.Formula.Create(value); } } public HH.WMS.Utils.NPOI.SS.Formula.Formula Formula { get { return field_8_parsed_expr; } } protected override String RecordName { get { return "FORMULA"; } } protected override int ValueDataSize { get { return FIXED_SIZE + field_8_parsed_expr.EncodedSize; } } public override short Sid { get { return sid; } } public void SetCachedResultTypeEmptyString() { specialCachedValue = SpecialCachedValue.CreateCachedEmptyValue(); } public void SetCachedResultTypeString() { specialCachedValue = SpecialCachedValue.CreateForString(); } public void SetCachedResultErrorCode(int errorCode) { specialCachedValue = SpecialCachedValue.CreateCachedErrorCode(errorCode); } public void SetCachedResultBoolean(bool value) { specialCachedValue = SpecialCachedValue.CreateCachedBoolean(value); } public bool CachedBooleanValue { get { return specialCachedValue.GetBooleanValue(); } } public int CachedErrorValue { get { return specialCachedValue.GetErrorValue(); } } public HH.WMS.Utils.NPOI.SS.UserModel.CellType CachedResultType { get { if (specialCachedValue == null) { return HH.WMS.Utils.NPOI.SS.UserModel.CellType.NUMERIC; } return (HH.WMS.Utils.NPOI.SS.UserModel.CellType)specialCachedValue.GetValueType(); } } public override bool Equals(Object obj) { if (!(obj is CellValueRecordInterface)) { return false; } CellValueRecordInterface loc = (CellValueRecordInterface)obj; if ((this.Row == loc.Row) && (this.Column == loc.Column)) { return true; } return false; } public override int GetHashCode () { return Row ^ Column; } protected override void SerializeValue(ILittleEndianOutput out1) { if (specialCachedValue == null) { out1.WriteDouble(field_4_value); } else { specialCachedValue.Serialize(out1); } out1.WriteShort(Options); out1.WriteInt(field_6_zero); // may as well write original data back so as to minimise differences from original field_8_parsed_expr.Serialize(out1); } protected override void AppendValueText(StringBuilder buffer) { buffer.Append(" .value = "); if (specialCachedValue == null) { buffer.Append(field_4_value).Append("\n"); } else { buffer.Append(specialCachedValue.FormatDebugString).Append("\n"); } buffer.Append(" .options = ").Append(Options).Append("\n"); buffer.Append(" .alwaysCalc = ").Append(alwaysCalc.IsSet(Options)).Append("\n"); buffer.Append(" .CalcOnLoad = ").Append(calcOnLoad.IsSet(Options)).Append("\n"); buffer.Append(" .sharedFormula = ").Append(sharedFormula.IsSet(Options)).Append("\n"); buffer.Append(" .zero = ").Append(field_6_zero).Append("\n"); Ptg[] ptgs = field_8_parsed_expr.Tokens; for (int k = 0; k < ptgs.Length; k++) { buffer.Append(" Ptg[").Append(k).Append("]="); Ptg ptg = ptgs[k]; buffer.Append(ptg.ToString()).Append(ptg.RVAType).Append("\n"); } } public override Object Clone() { FormulaRecord rec = new FormulaRecord(); CopyBaseFields(rec); rec.field_4_value = field_4_value; rec.field_5_options = field_5_options; rec.field_6_zero = field_6_zero; rec.field_8_parsed_expr = field_8_parsed_expr.Copy(); rec.specialCachedValue = specialCachedValue; return rec; } } }