diff --git a/components/fpspreadsheet/examples/excel8demo/excel8read.lpr b/components/fpspreadsheet/examples/excel8demo/excel8read.lpr index 61228d310..0f6dc053a 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8read.lpr +++ b/components/fpspreadsheet/examples/excel8demo/excel8read.lpr @@ -31,6 +31,8 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; + MyWorkbook.ReadFormulas := true; + MyWorkbook.ReadFromFile(InputFilename, sfExcel8); MyWorksheet := MyWorkbook.GetFirstWorksheet; diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 8083c9ca2..6914b67c6 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -68,7 +68,8 @@ type TFEKind = ( { Basic operands } - fekCell, fekCellRef, fekCellRange, fekNum, fekString, fekBool, fekMissingArg, + fekCell, fekCellRef, fekCellRange, fekNum, fekInteger, fekString, fekBool, + fekErr, fekMissingArg, { Basic operations } fekAdd, fekSub, fekDiv, fekMul, fekPercent, fekPower, fekUMinus, fekUPlus, fekConcat, // string concatenation @@ -138,7 +139,9 @@ type errIllegalRef, // #REF! errWrongName, // #NAME? errOverflow, // #NUM! - errArgNotAvail // #N/A + errArgNotAvail, // #N/A + // --- no Excel errors -- + errFormulaNotSupported ); {@@ List of possible formatting fields } @@ -402,7 +405,8 @@ type procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean); procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); - procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); + procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); overload; + procedure WriteErrorValue(ACell: PCell; AValue: TErrorValue); overload; procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2; @@ -487,6 +491,7 @@ type FFontList: TFPList; FBuiltinFontCount: Integer; FPalette: array of TsColorValue; + FReadFormulas: Boolean; { Internal methods } procedure RemoveWorksheetsCallback(data, arg: pointer); public @@ -537,6 +542,7 @@ type and support a single encoding for the whole document, like Excel 2 to 5 } property Encoding: TsEncoding read FEncoding write FEncoding; property FileFormat: TsSpreadsheetFormat read FFormat; + property ReadFormulas: Boolean read FReadFormulas write FReadFormulas; end; @@ -708,6 +714,7 @@ type } function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula; + procedure DestroyRPNFormula(AItem: PRPNItem); function RPNBool(AValue: Boolean; ANext: PRPNItem): PRPNItem; @@ -723,6 +730,8 @@ type ANext: PRPNItem): PRPNItem; overload; function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; + function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem; + function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNMissingArg(ANext: PRPNItem): PRPNItem; function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem; function RPNString(AValue: String; ANext: PRPNItem): PRPNItem; @@ -767,6 +776,7 @@ resourcestring lpErrWrongName = '#NAME?'; lpErrOverflow = '#NUM!'; lpErrArgNotAvail = '#N/A'; + lpErrFormulaNotSupported = ''; var {@@ @@ -1310,13 +1320,14 @@ begin Result := IfThen(BoolValue, lpTRUE, lpFALSE); cctError: case TErrorValue(StatusValue and $0F) of - errEmptyIntersection: Result := lpErrEmptyIntersection; - errDivideByZero : Result := lpErrDivideByZero; - errWrongType : Result := lpErrWrongType; - errIllegalRef : Result := lpErrIllegalRef; - errWrongName : Result := lpErrWrongName; - errOverflow : Result := lpErrOverflow; - errArgNotAvail : Result := lpErrArgNotAvail; + errEmptyIntersection : Result := lpErrEmptyIntersection; + errDivideByZero : Result := lpErrDivideByZero; + errWrongType : Result := lpErrWrongType; + errIllegalRef : Result := lpErrIllegalRef; + errWrongName : Result := lpErrWrongName; + errOverflow : Result := lpErrOverflow; + errArgNotAvail : Result := lpErrArgNotAvail; + errFormulaNotSupported: Result := lpErrFormulaNotSupported; end; else Result := ''; @@ -1609,13 +1620,17 @@ end; @param AValue The error code value } procedure TsWorksheet.WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); -var - ACell: PCell; begin - ACell := GetCell(ARow, ACol); - ACell^.ContentType := cctError; - ACell^.StatusValue := (ACell^.StatusValue and $F0) or ord(AValue); - ChangedCell(ARow, ACol); + WriteErrorValue(GetCell(ARow, ACol), AValue); +end; + +procedure TsWorksheet.WriteErrorValue(ACell: PCell; AValue: TErrorValue); +begin + if ACell <> nil then begin + ACell^.ContentType := cctError; + ACell^.StatusValue := (ACell^.StatusValue and $F0) or ord(AValue); + ChangedCell(ACell^.Row, ACell^.Col); + end; end; {@@ @@ -3533,6 +3548,28 @@ begin Result^.Next := ANext; end; +{@@ + Creates an entry in the RPN array with an error value. +} +function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekErr; + Result^.FE.IntValue := AErrCode; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a 2-byte unsigned integer +} +function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekInteger; + Result^.FE.IntValue := AValue; + Result^.Next := ANext; +end; + {@@ Creates an entry in the RPN array for a missing argument in of function call. } @@ -3623,6 +3660,17 @@ begin end; end; +procedure DestroyRPNFormula(AItem: PRPNItem); +var + nextitem: PRPNItem; +begin + while AItem <> nil do begin + nextitem := AItem^.Next; + DisposeRPNItem(AItem); + AItem := nextitem; + end; +end; + initialization MakeLEPalette(@DEFAULT_PALETTE, Length(DEFAULT_PALETTE)); diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index f3623161c..cace0eeb2 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -73,6 +73,8 @@ type procedure ReadNumber(AStream: TStream); override; procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override; procedure ReadRowInfo(AStream: TStream); override; + function ReadRPNFunc(AStream: TStream): Word; override; + function ReadRPNTokenArraySize(AStream: TStream): Word; override; procedure ReadStringRecord(AStream: TStream); override; procedure ReadWindow2(AStream: TStream); override; procedure ReadXF(AStream: TStream); @@ -156,11 +158,6 @@ const INT_EXCEL_ID_IXFE = $0044; INT_EXCEL_ID_FONTCOLOR = $0045; - { Cell Addresses constants } - MASK_EXCEL_ROW = $3FFF; - MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation, - MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation! - { BOF record constants } INT_EXCEL_SHEET = $0010; INT_EXCEL_CHART = $0020; @@ -647,6 +644,23 @@ begin end; end; +{ Reads the identifier for an RPN function with fixed argument count. + Valid for BIFF2-BIFF3. } +function TsSpreadBIFF2Reader.ReadRPNFunc(AStream: TStream): Word; +var + b: Byte; +begin + b := AStream.ReadByte; + Result := b; +end; + +{ Helper funtion for reading of the size of the token array of an RPN formula. + Is overridden because BIFF2 uses 1 byte only. } +function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word; +begin + Result := AStream.ReadByte; +end; + { Reads a STRING record which contains the result of string formula. } procedure TsSpreadBIFF2Reader.ReadStringRecord(AStream: TStream); var diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index a627fdb0a..824798539 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -219,11 +219,6 @@ const { Excel record IDs } // see: in xlscommon - { Cell Addresses constants } - MASK_EXCEL_ROW = $3FFF; - MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation, - MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation! - { BOF record constants } INT_BOF_BIFF5_VER = $0500; INT_BOF_WORKBOOK_GLOBALS= $0005; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 4152b34a2..0a5c79953 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -86,7 +86,12 @@ type procedure ReadLabelSST(const AStream: TStream); // procedure ReadNumber() --> xlscommon procedure ReadRichString(const AStream: TStream); + procedure ReadRPNCellAddress(AStream: TStream; var ARow, ACol: Cardinal; + var AFlags: TsRelFlags); override; + procedure ReadRPNCellRangeAddress(AStream: TStream; + var ARow1, ACol1, ARow2, ACol2: Cardinal; var AFlags: TsRelFlags); override; procedure ReadSST(const AStream: TStream); + function ReadString_8bitLen(AStream: TStream): String; override; procedure ReadStringRecord(AStream: TStream); override; procedure ReadXF(const AStream: TStream); public @@ -219,7 +224,6 @@ const INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3; { Cell Addresses constants } - MASK_EXCEL_ROW = $3FFF; MASK_EXCEL_COL_BITS_BIFF8=$00FF; MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation, MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation! @@ -1658,6 +1662,53 @@ begin ApplyCellFormatting(ARow, ACol, XF); end; +{ Reads the cell address used in an RPN formula element. Evaluates the corresponding + bits to distinguish between absolute and relative addresses. + Overriding the implementation in xlscommon. } +procedure TsSpreadBIFF8Reader.ReadRPNCellAddress(AStream: TStream; + var ARow, ACol: Cardinal; var AFlags: TsRelFlags); +var + c: word; +begin + // Read row index (2 bytes) + ARow := WordLEToN(AStream.ReadWord); + // Read column index; it contains info on absolute/relative address + c := WordLEToN(AStream.ReadWord); + // Extract column index + ACol := c and MASK_EXCEL_COL_BITS_BIFF8; + // Extract info on absolute/relative addresses. + AFlags := []; + if (c and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol); + if (c and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow); +end; + +{ Reads a cell range address used in an RPN formula element. + Evaluates the corresponding bits to distinguish between absolute and + relative addresses. + Overriding the implementation in xlscommon. } +procedure TsSpreadBIFF8Reader.ReadRPNCellRangeAddress(AStream: TStream; + var ARow1, ACol1, ARow2, ACol2: Cardinal; var AFlags: TsRelFlags); +var + c1, c2: word; +begin + // Read row index of first and last rows (2 bytes, each) + ARow1 := WordLEToN(AStream.ReadWord); + ARow2 := WordLEToN(AStream.ReadWord); + // Read column index of first and last columns; they contain info on + // absolute/relative address + c1 := WordLEToN(AStream.ReadWord); + c2 := WordLEToN(AStream.ReadWord); + // Extract column index of rist and last columns + ACol1 := c1 and MASK_EXCEL_COL_BITS_BIFF8; + ACol2 := c2 and MASK_EXCEL_COL_BITS_BIFF8; + // Extract info on absolute/relative addresses. + AFlags := []; + if (c1 and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol); + if (c1 and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow); + if (c2 and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol2); + if (c2 and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow2); +end; + procedure TsSpreadBIFF8Reader.ReadSST(const AStream: TStream); var Items: DWORD; @@ -1742,6 +1793,15 @@ begin ApplyCellFormatting(ARow, ACol, XF); end; +{ Helper function for reading a string with 8-bit length. } +function TsSpreadBIFF8Reader.ReadString_8bitLen(AStream: TStream): String; +var + s: widestring; +begin + s := ReadWideString(AStream, true); + Result := s; +end; + procedure TsSpreadBIFF8Reader.ReadStringRecord(AStream: TStream); var s: String; diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index baedac5b6..e3f20753e 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -83,8 +83,9 @@ const { Constant Operand Tokens, 3.8} INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand INT_EXCEL_TOKEN_TSTR = $17; //string + INT_EXCEL_TOKEN_TERR = $1C; //error value INT_EXCEL_TOKEN_TBOOL = $1D; //boolean - INT_EXCEL_TOKEN_TINT = $1E; //integer + INT_EXCEL_TOKEN_TINT = $1E; //(unsigned) integer INT_EXCEL_TOKEN_TNUM = $1F; //floating-point { Operand Tokens } @@ -99,9 +100,9 @@ const { Function Tokens } // _R: reference; _V: value; _A: array // Offset 0: token; offset 1: index to a built-in sheet function ( ➜ 3.111) - INT_EXCEL_TOKEN_FUNC_R = $21; - INT_EXCEL_TOKEN_FUNC_V = $41; - INT_EXCEL_TOKEN_FUNC_A = $61; + INT_EXCEL_TOKEN_FUNC_R = $21; + INT_EXCEL_TOKEN_FUNC_V = $41; + INT_EXCEL_TOKEN_FUNC_A = $61; //VAR: variable number of arguments: INT_EXCEL_TOKEN_FUNCVAR_R = $22; @@ -309,6 +310,13 @@ const MASK_XF_VERT_ALIGN_BOTTOM = $20; MASK_XF_VERT_ALIGN_JUSTIFIED = $30; + { Cell Addresses constants, valid for BIFF2-BIFF5 } + MASK_EXCEL_ROW = $3FFF; + MASK_EXCEL_RELATIVE_COL = $4000; + MASK_EXCEL_RELATIVE_ROW = $8000; + { Note: The assignment of the RELATIVE_COL and _ROW masks is according to + Microsoft's documentation, but opposite to the OpenOffice documentation. } + { Error codes } ERR_INTERSECTION_EMPTY = $00; // #NULL! ERR_DIVIDE_BY_ZERO = $07; // #DIV/0! @@ -399,6 +407,17 @@ type procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); virtual; // Read row info procedure ReadRowInfo(AStream: TStream); virtual; + // Read the array of RPN tokens of a formula + procedure ReadRPNCellAddress(AStream: TStream; var ARow, ACol: Cardinal; + var AFlags: TsRelFlags); virtual; + procedure ReadRPNCellRangeAddress(AStream: TStream; + var ARow1, ACol1, ARow2, ACol2: Cardinal; var AFlags: TsRelFlags); virtual; + function ReadRPNFunc(AStream: TStream): Word; virtual; + function ReadRPNTokenArray(AStream: TStream; var AFormula: TsRPNFormula): Boolean; + function ReadRPNTokenArraySize(AStream: TStream): word; virtual; + + // Helper function for reading a string with 8-bit length + function ReadString_8bitLen(AStream: TStream): String; virtual; // Read STRING record (result of string formula) procedure ReadStringRecord(AStream: TStream); virtual; // Read WINDOW2 record (gridlines, sheet headers) @@ -478,6 +497,164 @@ implementation uses StrUtils, fpsNumFormatParser; +{ Helper table for rpn formulas: + Assignment of FormulaElementKinds (fekXXXX) to EXCEL_TOKEN IDs. The table + contains additional inforation in the first column: + 0 --> primary token (basic operands and operations) + 1 --> secondary token of a function with a fixed parameter count + 2 --> secondary token of a function with a variable parameter count } +const + TokenIDs: array[fekCell..fekOpSum, 0..1] of Word = ( + // Basic operands + (0, INT_EXCEL_TOKEN_TREFV), {fekCell} + (0, INT_EXCEL_TOKEN_TREFR), {fekCellRef} + (0, INT_EXCEL_TOKEN_TAREA_R), {fekCellRange} + (0, INT_EXCEL_TOKEN_TNUM), {fekNum} + (0, INT_EXCEL_TOKEN_TINT), {fekInteger} + (0, INT_EXCEL_TOKEN_TSTR), {fekString} + (0, INT_EXCEL_TOKEN_TBOOL), {fekBool} + (0, INT_EXCEL_TOKEN_TERR), {fekErr} + (0, INT_EXCEL_TOKEN_TMISSARG), {fekMissArg, missing argument} + + // Basic operations + (0, INT_EXCEL_TOKEN_TADD), {fekAdd, +} + (0, INT_EXCEL_TOKEN_TSUB), {fekSub, -} + (0, INT_EXCEL_TOKEN_TDIV), {fekDiv, /} + (0, INT_EXCEL_TOKEN_TMUL), {fekMul, *} + (0, INT_EXCEL_TOKEN_TPERCENT), {fekPercent, %} + (0, INT_EXCEL_TOKEN_TPOWER), {fekPower, ^} + (0, INT_EXCEL_TOKEN_TUMINUS), {fekUMinus, -} + (0, INT_EXCEL_TOKEN_TUPLUS), {fekUPlus, +} + (0, INT_EXCEL_TOKEN_TCONCAT), {fekConcat, &, for strings} + (0, INT_EXCEL_TOKEN_TEQ), {fekEqual, =} + (0, INT_EXCEL_TOKEN_TGT), {fekGreater, >} + (0, INT_EXCEL_TOKEN_TGE), {fekGreaterEqual, >=} + (0, INT_EXCEL_TOKEN_TLT), {fekLess <} + (0, INT_EXCEL_TOKEN_TLE), {fekLessEqual, <=} + (0, INT_EXCEL_TOKEN_TNE), {fekNotEqual, <>} + + // Math functions + (1, INT_EXCEL_SHEET_FUNC_ABS), {fekABS} + (1, INT_EXCEL_SHEET_FUNC_ACOS), {fekACOS} + (1, INT_EXCEL_SHEET_FUNC_ACOSH), {fekACOSH} + (1, INT_EXCEL_SHEET_FUNC_ASIN), {fekASIN} + (1, INT_EXCEL_SHEET_FUNC_ASINH), {fekASINH} + (1, INT_EXCEL_SHEET_FUNC_ATAN), {fekATAN} + (1, INT_EXCEL_SHEET_FUNC_ATANH), {fekATANH} + (1, INT_EXCEL_SHEET_FUNC_COS), {fekCOS} + (1, INT_EXCEL_SHEET_FUNC_COSH), {fekCOSH} + (1, INT_EXCEL_SHEET_FUNC_DEGREES), {fekDEGREES} + (1, INT_EXCEL_SHEET_FUNC_EXP), {fekEXP} + (1, INT_EXCEL_SHEET_FUNC_INT), {fekINT} + (1, INT_EXCEL_SHEET_FUNC_LN), {fekLN} + (1, INT_EXCEL_SHEET_FUNC_LOG), {fekLOG} + (1, INT_EXCEL_SHEET_FUNC_LOG10), {fekLOG10} + (1, INT_EXCEL_SHEET_FUNC_PI), {fekPI} + (1, INT_EXCEL_SHEET_FUNC_RADIANS), {fekRADIANS} + (1, INT_EXCEL_SHEET_FUNC_RAND), {fekRAND} + (1, INT_EXCEL_SHEET_FUNC_ROUND), {fekROUND} + (1, INT_EXCEL_SHEET_FUNC_SIGN), {fekSIGN} + (1, INT_EXCEL_SHEET_FUNC_SIN), {fekSIN} + (1, INT_EXCEL_SHEET_FUNC_SINH), {fekSINH} + (1, INT_EXCEL_SHEET_FUNC_SQRT), {fekSQRT} + (1, INT_EXCEL_SHEET_FUNC_TAN), {fekTAN} + (1, INT_EXCEL_SHEET_FUNC_TANH), {fekTANH} + + // Date/time functions + (1, INT_EXCEL_SHEET_FUNC_DATE), {fekDATE} + (1, INT_EXCEL_SHEET_FUNC_DATEDIF), {fekDATEDIF} + (1, INT_EXCEL_SHEET_FUNC_DATEVALUE), {fekDATEVALUE} + (1, INT_EXCEL_SHEET_FUNC_DAY), {fekDAY} + (1, INT_EXCEL_SHEET_FUNC_HOUR), {fekHOUR} + (1, INT_EXCEL_SHEET_FUNC_MINUTE), {fekMINUTE} + (1, INT_EXCEL_SHEET_FUNC_MONTH), {fekMONTH} + (1, INT_EXCEL_SHEET_FUNC_NOW), {fekNOW} + (1, INT_EXCEL_SHEET_FUNC_SECOND), {fekSECOND} + (1, INT_EXCEL_SHEET_FUNC_TIME), {fekTIME} + (1, INT_EXCEL_SHEET_FUNC_TIMEVALUE), {fekTIMEVALUE} + (1, INT_EXCEL_SHEET_FUNC_TODAY), {fekTODAY} + (2, INT_EXCEL_SHEET_FUNC_WEEKDAY), {fekWEEKDAY} + (1, INT_EXCEL_SHEET_FUNC_YEAR), {fekYEAR} + + // Statistical functions + (2, INT_EXCEL_SHEET_FUNC_AVEDEV), {fekAVEDEV} + (2, INT_EXCEL_SHEET_FUNC_AVERAGE), {fekAVERAGE} + (2, INT_EXCEL_SHEET_FUNC_BETADIST), {fekBETADIST} + (2, INT_EXCEL_SHEET_FUNC_BETAINV), {fekBETAINV} + (1, INT_EXCEL_SHEET_FUNC_BINOMDIST), {fekBINOMDIST} + (1, INT_EXCEL_SHEET_FUNC_CHIDIST), {fekCHIDIST} + (1, INT_EXCEL_SHEET_FUNC_CHIINV), {fekCHIINV} + (2, INT_EXCEL_SHEET_FUNC_COUNT), {fekCOUNT} + (2, INT_EXCEL_SHEET_FUNC_COUNTA), {fekCOUNTA} + (1, INT_EXCEL_SHEET_FUNC_COUNTBLANK),{fekCOUNTBLANK} + (2, INT_EXCEL_SHEET_FUNC_COUNTIF), {fekCOUNTIF} + (2, INT_EXCEL_SHEET_FUNC_MAX), {fekMAX} + (2, INT_EXCEL_SHEET_FUNC_MEDIAN), {fekMEDIAN} + (2, INT_EXCEL_SHEET_FUNC_MIN), {fekMIN} + (1, INT_EXCEL_SHEET_FUNC_PERMUT), {fekPERMUT} + (1, INT_EXCEL_SHEET_FUNC_POISSON), {fekPOISSON} + (2, INT_EXCEL_SHEET_FUNC_PRODUCT), {fekPRODUCT} + (2, INT_EXCEL_SHEET_FUNC_STDEV), {fekSTDEV} + (2, INT_EXCEL_SHEET_FUNC_STDEVP), {fekSTDEVP} + (2, INT_EXCEL_SHEET_FUNC_SUM), {fekSUM} + (2, INT_EXCEL_SHEET_FUNC_SUMIF), {fekSUMIF} + (2, INT_EXCEL_SHEET_FUNC_SUMSQ), {fekSUMSQ} + (2, INT_EXCEL_SHEET_FUNC_VAR), {fekVAR} + (2, INT_EXCEL_SHEET_FUNC_VARP), {fekVARP} + + // Financial functions + (2, INT_EXCEL_SHEET_FUNC_FV), {fekFV} + (2, INT_EXCEL_SHEET_FUNC_NPER), {fekNPER} + (2, INT_EXCEL_SHEET_FUNC_PV), {fekPV} + (2, INT_EXCEL_SHEET_FUNC_PMT), {fekPMT} + (2, INT_EXCEL_SHEET_FUNC_RATE), {fekRATE} + + // Logical functions + (2, INT_EXCEL_SHEET_FUNC_AND), {fekAND} + (1, INT_EXCEL_SHEET_FUNC_FALSE), {fekFALSE} + (2, INT_EXCEL_SHEET_FUNC_IF), {fekIF} + (1, INT_EXCEL_SHEET_FUNC_NOT), {fekNOT} + (2, INT_EXCEL_SHEET_FUNC_OR), {fekOR} + (1, INT_EXCEL_SHEET_FUNC_TRUE), {fekTRUE} + + // String functions + (1, INT_EXCEL_SHEET_FUNC_CHAR), {fekCHAR} + (1, INT_EXCEL_SHEET_FUNC_CODE), {fekCODE} + (2, INT_EXCEL_SHEET_FUNC_LEFT), {fekLEFT} + (1, INT_EXCEL_SHEET_FUNC_LOWER), {fekLOWER} + (1, INT_EXCEL_SHEET_FUNC_MID), {fekMID} + (1, INT_EXCEL_SHEET_FUNC_PROPER), {fekPROPER} + (1, INT_EXCEL_SHEET_FUNC_REPLACE), {fekREPLACE} + (2, INT_EXCEL_SHEET_FUNC_RIGHT), {fekRIGHT} + (2, INT_EXCEL_SHEET_FUNC_SUBSTITUTE),{fekSUBSTITUTE} + (1, INT_EXCEL_SHEET_FUNC_TRIM), {fekTRIM} + (1, INT_EXCEL_SHEET_FUNC_UPPER), {fekUPPER} + + // lookup/reference functions + (2, INT_EXCEL_SHEET_FUNC_COLUMN), {fekCOLUMN} + (1, INT_EXCEL_SHEET_FUNC_COLUMNS), {fekCOLUMNS} + (2, INT_EXCEL_SHEET_FUNC_ROW), {fekROW} + (1, INT_EXCEL_SHEET_FUNC_ROWS), {fekROWS} + + // Info functions + (2, INT_EXCEL_SHEET_FUNC_CELL), {fekCELLINFO} + (1, INT_EXCEL_SHEET_FUNC_INFO), {fekINFO} + (1, INT_EXCEL_SHEET_FUNC_ISBLANK), {fekIsBLANK} + (1, INT_EXCEL_SHEET_FUNC_ISERR), {fekIsERR} + (1, INT_EXCEL_SHEET_FUNC_ISERROR), {fekIsERROR} + (1, INT_EXCEL_SHEET_FUNC_ISLOGICAL), {fekIsLOGICAL} + (1, INT_EXCEL_SHEET_FUNC_ISNA), {fekIsNA} + (1, INT_EXCEL_SHEET_FUNC_ISNONTEXT), {fekIsNONTEXT} + (1, INT_EXCEL_SHEET_FUNC_ISNUMBER), {fekIsNUMBER} + (1, INT_EXCEL_SHEET_FUNC_ISREF), {fekIsREF} + (1, INT_EXCEL_SHEET_FUNC_ISTEXT), {fekIsTEXT} + (1, INT_EXCEL_SHEET_FUNC_VALUE), {fekValue} + + // Other operations + (0, INT_EXCEL_TOKEN_TATTR) {fekOpSum} + ); + + function ConvertExcelDateTimeToDateTime( const AExcelDateNum: Double; ADateMode: TDateMode): TDateTime; begin @@ -876,9 +1053,10 @@ var ARow, ACol: Cardinal; XF: WORD; ResultFormula: Double; + RPNFormula: TsRPNFormula; Data: array [0..7] of BYTE; Flags: WORD; - FormulaSize: BYTE; +// FormulaSize: BYTE; i: Integer; dt: TDateTime; nf: TsNumberFormat; @@ -887,6 +1065,8 @@ var nfs: String; resultStr: String; err: TErrorValue; + ok: Boolean; + cell: PCell; begin { BIFF Record header } @@ -904,6 +1084,7 @@ begin AStream.ReadDWord; { Formula size } + (* FormulaSize := WordLEtoN(AStream.ReadWord); { Formula data, output as debug info } @@ -914,6 +1095,7 @@ begin //RPN data not used by now AStream.Position := AStream.Position + FormulaSize; + *) // Now determine the type of the formula result if (Data[6] = $FF) and (Data[7] = $FF) then @@ -953,6 +1135,13 @@ begin FWorksheet.WriteNumber(ARow, ACol, ResultFormula, nf, nd, ncs); end; + { Formula token array } + if FWorkbook.ReadFormulas then begin + cell := FWorksheet.FindCell(ARow, ACol); + ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue); + if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); + end; + {Add attributes} ApplyCellFormatting(ARow, ACol, XF); end; @@ -1168,6 +1357,230 @@ begin // changed manually. end; +{ Reads the cell address used in an RPN formula element. Evaluates the corresponding + bits to distinguish between absolute and relative addresses. + Implemented here for BIFF2-BIFF5. BIFF8 must be overridden. } +procedure TsSpreadBIFFReader.ReadRPNCellAddress(AStream: TStream; + var ARow, ACol: Cardinal; var AFlags: TsRelFlags); +var + r: word; +begin + // 2 bytes for row (including absolute/relative info) + r := WordLEToN(AStream.ReadWord); + // 1 byte for column index + ACol := AStream.ReadByte; + // Extract row index + ARow := r and MASK_EXCEL_ROW; + // Extract absolute/relative flags + AFlags := []; + if (r and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol); + if (r and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow); +end; + +{ Reads the cell address used in an RPN formula element. Evaluates the corresponding + bits to distinguish between absolute and relative addresses. + Implemented here for BIFF2-BIFF5. BIFF8 must be overridden. } +procedure TsSpreadBIFFReader.ReadRPNCellRangeAddress(AStream: TStream; + var ARow1, ACol1, ARow2, ACol2: Cardinal; var AFlags: TsRelFlags); +var + r1, r2: word; +begin + // 2 bytes, each, for first and last row (including absolute/relative info) + r1 := WordLEToN(AStream.ReadWord); + r2 := WordLEToN(AStream.ReadWord); + // 1 byte each for fist and last column index + ACol1 := AStream.ReadByte; + ACol2 := AStream.ReadByte; + // Extract row index of first and last row + ARow1 := r1 and MASK_EXCEL_ROW; + ARow2 := r2 and MASK_EXCEL_ROW; + // Extract absolute/relative flags + AFlags := []; + if (r1 and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol); + if (r2 and MASK_EXCEL_RELATIVE_COL = 1) then Include(AFlags, rfRelCol2); + if (r1 and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow); + if (r2 and MASK_EXCEL_RELATIVE_ROW = 1) then Include(AFlags, rfRelRow2); +end; + +{ Reads the identifier for an RPN function with fixed argument count. + Valid for BIFF4-BIFF8. Override in BIFF2-BIFF3 } +function TsSpreadBIFFReader.ReadRPNFunc(AStream: TStream): Word; +begin + Result := WordLEToN(AStream.ReadWord); +end; + +function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream; + var AFormula: TsRPNFormula): Boolean; +var + n: Word; + p0: Int64; + token: Byte; + rpnItem: PRPNItem; + supported: boolean; + wordVal: Word; // 2 byte unsigned integer + dblVal: Double; // IEEE 8 byte floating point number + flags: TsRelFlags; + r, c, r2, c2: Cardinal; + fek: TFEKind; + func: Word; + b: Byte; + found: Boolean; +begin + rpnItem := nil; + n := ReadRPNTokenArraySize(AStream); + p0 := AStream.Position; + supported := true; + while (AStream.Position < p0 + n) and supported do begin + token := AStream.ReadByte; + case token of + INT_EXCEL_TOKEN_TREFV: + begin; + ReadRPNCellAddress(AStream, r, c, flags); + rpnItem := RPNCellValue(r, c, flags, rpnItem); + end; + INT_EXCEL_TOKEN_TREFR: + begin + ReadRPNCellAddress(AStream, r, c, flags); + rpnItem := RPNCellRef(r, c, flags, rpnItem); + end; + INT_EXCEL_TOKEN_TREFA: + begin + ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags); + rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem); + end; + INT_EXCEL_TOKEN_TMISSARG: + rpnItem := RPNMissingArg(rpnItem); + INT_EXCEL_TOKEN_TSTR: + rpnItem := RPNString(ReadString_8BitLen(AStream), rpnItem); + INT_EXCEL_TOKEN_TERR: + rpnItem := RPNErr(AStream.ReadByte, rpnItem); + INT_EXCEL_TOKEN_TBOOL: + rpnItem := RPNBool(AStream.ReadByte=1, rpnItem); + INT_EXCEL_TOKEN_TINT: + rpnItem := RPNInteger(WordLEToN(AStream.ReadWord), rpnItem); + INT_EXCEL_TOKEN_TNUM: + begin + AStream.ReadBuffer(dblVal, 8); + rpnItem := RPNNumber(dblVal, rpnItem); + end; + + INT_EXCEL_TOKEN_FUNC_R, + INT_EXCEL_TOKEN_FUNC_V, + INT_EXCEL_TOKEN_FUNC_A: + // functions with fixed argument count + begin + func := ReadRPNFunc(AStream); + found := false; + for fek in TFEKind do begin + if (TokenIDs[fek, 1] = func) and (TokenIDs[fek, 0] = 1) then begin + rpnItem := RPNFunc(fek, rpnItem); + found := true; + break; + end; + end; + if not found then + supported := false; + end; + + INT_EXCEL_TOKEN_FUNCVAR_R, + INT_EXCEL_TOKEN_FUNCVAR_V, + INT_EXCEL_TOKEN_FUNCVAR_A: + // functions with variable argument count + begin + b := AStream.ReadByte; + func := ReadRPNFunc(AStream); + found := false; + for fek in TFEKind do + if (TokenIDs[fek, 1] = func) and (TokenIDs[fek, 0] = 2) then begin + rpnItem := RPNFunc(fek, b, rpnItem); + found := true; + break; + end; + if not found then + supported := false; + end + + else + found := false; + for fek in TFEKind do + if (TokenIDs[fek, 1] = token) and (TokenIDs[fek, 0] = 0) then begin + rpnItem := RPNFunc(fek, rpnItem); + found := true; + break; + end; + if not found then + supported := false; +(* + // binary tokens + INT_EXCEL_TOKEN_TADD: + rpnItem := RPNFunc(fekAdd, rpnItem); + INT_EXCEL_TOKEN_TSUB: + rpnItem := RPNFunc(fekSub, rpnItem); + INT_EXCEL_TOKEN_TMUL: + rpnItem := RPNFunc(fekMul, rpnItem); + INT_EXCEL_TOKEN_TDIV: + rpnItem := RPNFunc(fekDiv, rpnItem); + INT_EXCEL_TOKEN_TPOWER: + rpnItem := RPNFunc(fekPower, rpnItem); + INT_EXCEL_TOKEN_TCONCAT: + rpnItem := RPNFunc(fekConcat, rpnItem); + INT_EXCEL_TOKEN_TLT: + rpnItem := RPNFunc(fekLess, rpnItem); + INT_EXCEL_TOKEN_TLE: + rpnItem := RPNFunc(fekLessEqual, rpnItem); + INT_EXCEL_TOKEN_TEQ: + rpnItem := RPNFunc(fekEqual, rpnItem); + INT_EXCEL_TOKEN_TGE: + rpnItem := RPNFunc(fekGreaterEqual, rpnItem); + INT_EXCEL_TOKEN_TGT: + rpnItem := RPNFunc(fekGreater, rpnItem); + INT_EXCEL_TOKEN_TNE: + rpnItem := RPNFunc(fekNotEqual, rpnItem); + // Unary operations + INT_EXCEL_TOKEN_TUPLUS: + rpnItem := RPNFunc(fekUPlus, rpnItem); + INT_EXCEL_TOKEN_TUMINUS: + rpnItem := RPNFunc(fekUMinus, rpnItem); + INT_EXCEL_TOKEN_TPERCENT: + rpnItem := RPNFunc(fekPercent, rpnItem); + // Operands (--> 3.8) + else + supported := false; *) + end; + end; + if not supported then begin + DestroyRPNFormula(rpnItem); + SetLength(AFormula, 0); + Result := false; + end + else begin + AFormula := CreateRPNFormula(rpnItem); + Result := true; + end; +end; + +{ Helper funtion for reading of the size of the token array of an RPN formula. + Is implemented here for BIFF3-BIFF8 where the size is a 2-byte value. + Needs to be rewritten for BIFF2 using a 1-byte size. } +function TsSpreadBIFFReader.ReadRPNTokenArraySize(AStream: TStream): Word; +begin + Result := WordLEToN(AStream.ReadWord); +end; + +{ Helper function for reading a string with 8-bit length. Here, we implement the + version for ansistrings since it is valid for all BIFF versions except BIFF8 + where it has to overridden. } +function TsSpreadBIFFReader.ReadString_8bitLen(AStream: TStream): String; +var + len: Byte; + s: ansistring; +begin + len := AStream.ReadByte; + SetLength(s, len); + AStream.ReadBuffer(s[1], len); + Result := s; +end; + { Reads a STRING record. It immediately precedes a FORMULA record which has a string result. The read value is applied to the FIncompleteCell. Must be overridden because the implementation depends on BIFF version. } @@ -1256,159 +1669,6 @@ end; function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( AElementKind: TFEKind; out ASecondaryID: Word): Word; -const - { Explanation of first index: - 0 --> primary token (basic operands and operations) - 1 --> secondary token of a function with a fixed parameter count - 2 --> secondary token of a function with a variable parameter count } - TokenIDs: array[fekCell..fekOpSum, 0..1] of Word = ( - // Basic operands - (0, INT_EXCEL_TOKEN_TREFV), {fekCell} - (0, INT_EXCEL_TOKEN_TREFR), {fekCellRef} - (0, INT_EXCEL_TOKEN_TAREA_R), {fekCellRange} - (0, INT_EXCEL_TOKEN_TNUM), {fekNum} - (0, INT_EXCEL_TOKEN_TSTR), {fekString} - (0, INT_EXCEL_TOKEN_TBOOL), {fekBool} - (0, INT_EXCEL_TOKEN_TMISSARG), {fekMissArg, missing argument} - - // Basic operations - (0, INT_EXCEL_TOKEN_TADD), {fekAdd, +} - (0, INT_EXCEL_TOKEN_TSUB), {fekSub, -} - (0, INT_EXCEL_TOKEN_TDIV), {fekDiv, /} - (0, INT_EXCEL_TOKEN_TMUL), {fekMul, *} - (0, INT_EXCEL_TOKEN_TPERCENT), {fekPercent, %} - (0, INT_EXCEL_TOKEN_TPOWER), {fekPower, ^} - (0, INT_EXCEL_TOKEN_TUMINUS), {fekUMinus, -} - (0, INT_EXCEL_TOKEN_TUPLUS), {fekUPlus, +} - (0, INT_EXCEL_TOKEN_TCONCAT), {fekConcat, &, for strings} - (0, INT_EXCEL_TOKEN_TEQ), {fekEqual, =} - (0, INT_EXCEL_TOKEN_TGT), {fekGreater, >} - (0, INT_EXCEL_TOKEN_TGE), {fekGreaterEqual, >=} - (0, INT_EXCEL_TOKEN_TLT), {fekLess <} - (0, INT_EXCEL_TOKEN_TLE), {fekLessEqual, <=} - (0, INT_EXCEL_TOKEN_TNE), {fekNotEqual, <>} - - // Math functions - (1, INT_EXCEL_SHEET_FUNC_ABS), {fekABS} - (1, INT_EXCEL_SHEET_FUNC_ACOS), {fekACOS} - (1, INT_EXCEL_SHEET_FUNC_ACOSH), {fekACOSH} - (1, INT_EXCEL_SHEET_FUNC_ASIN), {fekASIN} - (1, INT_EXCEL_SHEET_FUNC_ASINH), {fekASINH} - (1, INT_EXCEL_SHEET_FUNC_ATAN), {fekATAN} - (1, INT_EXCEL_SHEET_FUNC_ATANH), {fekATANH} - (1, INT_EXCEL_SHEET_FUNC_COS), {fekCOS} - (1, INT_EXCEL_SHEET_FUNC_COSH), {fekCOSH} - (1, INT_EXCEL_SHEET_FUNC_DEGREES), {fekDEGREES} - (1, INT_EXCEL_SHEET_FUNC_EXP), {fekEXP} - (1, INT_EXCEL_SHEET_FUNC_INT), {fekINT} - (1, INT_EXCEL_SHEET_FUNC_LN), {fekLN} - (1, INT_EXCEL_SHEET_FUNC_LOG), {fekLOG} - (1, INT_EXCEL_SHEET_FUNC_LOG10), {fekLOG10} - (1, INT_EXCEL_SHEET_FUNC_PI), {fekPI} - (1, INT_EXCEL_SHEET_FUNC_RADIANS), {fekRADIANS} - (1, INT_EXCEL_SHEET_FUNC_RAND), {fekRAND} - (1, INT_EXCEL_SHEET_FUNC_ROUND), {fekROUND} - (1, INT_EXCEL_SHEET_FUNC_SIGN), {fekSIGN} - (1, INT_EXCEL_SHEET_FUNC_SIN), {fekSIN} - (1, INT_EXCEL_SHEET_FUNC_SINH), {fekSINH} - (1, INT_EXCEL_SHEET_FUNC_SQRT), {fekSQRT} - (1, INT_EXCEL_SHEET_FUNC_TAN), {fekTAN} - (1, INT_EXCEL_SHEET_FUNC_TANH), {fekTANH} - - // Date/time functions - (1, INT_EXCEL_SHEET_FUNC_DATE), {fekDATE} - (1, INT_EXCEL_SHEET_FUNC_DATEDIF), {fekDATEDIF} - (1, INT_EXCEL_SHEET_FUNC_DATEVALUE), {fekDATEVALUE} - (1, INT_EXCEL_SHEET_FUNC_DAY), {fekDAY} - (1, INT_EXCEL_SHEET_FUNC_HOUR), {fekHOUR} - (1, INT_EXCEL_SHEET_FUNC_MINUTE), {fekMINUTE} - (1, INT_EXCEL_SHEET_FUNC_MONTH), {fekMONTH} - (1, INT_EXCEL_SHEET_FUNC_NOW), {fekNOW} - (1, INT_EXCEL_SHEET_FUNC_SECOND), {fekSECOND} - (1, INT_EXCEL_SHEET_FUNC_TIME), {fekTIME} - (1, INT_EXCEL_SHEET_FUNC_TIMEVALUE), {fekTIMEVALUE} - (1, INT_EXCEL_SHEET_FUNC_TODAY), {fekTODAY} - (2, INT_EXCEL_SHEET_FUNC_WEEKDAY), {fekWEEKDAY} - (1, INT_EXCEL_SHEET_FUNC_YEAR), {fekYEAR} - - // Statistical functions - (2, INT_EXCEL_SHEET_FUNC_AVEDEV), {fekAVEDEV} - (2, INT_EXCEL_SHEET_FUNC_AVERAGE), {fekAVERAGE} - (2, INT_EXCEL_SHEET_FUNC_BETADIST), {fekBETADIST} - (2, INT_EXCEL_SHEET_FUNC_BETAINV), {fekBETAINV} - (1, INT_EXCEL_SHEET_FUNC_BINOMDIST), {fekBINOMDIST} - (1, INT_EXCEL_SHEET_FUNC_CHIDIST), {fekCHIDIST} - (1, INT_EXCEL_SHEET_FUNC_CHIINV), {fekCHIINV} - (2, INT_EXCEL_SHEET_FUNC_COUNT), {fekCOUNT} - (2, INT_EXCEL_SHEET_FUNC_COUNTA), {fekCOUNTA} - (1, INT_EXCEL_SHEET_FUNC_COUNTBLANK),{fekCOUNTBLANK} - (2, INT_EXCEL_SHEET_FUNC_COUNTIF), {fekCOUNTIF} - (2, INT_EXCEL_SHEET_FUNC_MAX), {fekMAX} - (2, INT_EXCEL_SHEET_FUNC_MEDIAN), {fekMEDIAN} - (2, INT_EXCEL_SHEET_FUNC_MIN), {fekMIN} - (1, INT_EXCEL_SHEET_FUNC_PERMUT), {fekPERMUT} - (1, INT_EXCEL_SHEET_FUNC_POISSON), {fekPOISSON} - (2, INT_EXCEL_SHEET_FUNC_PRODUCT), {fekPRODUCT} - (2, INT_EXCEL_SHEET_FUNC_STDEV), {fekSTDEV} - (2, INT_EXCEL_SHEET_FUNC_STDEVP), {fekSTDEVP} - (2, INT_EXCEL_SHEET_FUNC_SUM), {fekSUM} - (2, INT_EXCEL_SHEET_FUNC_SUMIF), {fekSUMIF} - (2, INT_EXCEL_SHEET_FUNC_SUMSQ), {fekSUMSQ} - (2, INT_EXCEL_SHEET_FUNC_VAR), {fekVAR} - (2, INT_EXCEL_SHEET_FUNC_VARP), {fekVARP} - - // Financial functions - (2, INT_EXCEL_SHEET_FUNC_FV), {fekFV} - (2, INT_EXCEL_SHEET_FUNC_NPER), {fekNPER} - (2, INT_EXCEL_SHEET_FUNC_PV), {fekPV} - (2, INT_EXCEL_SHEET_FUNC_PMT), {fekPMT} - (2, INT_EXCEL_SHEET_FUNC_RATE), {fekRATE} - - // Logical functions - (2, INT_EXCEL_SHEET_FUNC_AND), {fekAND} - (1, INT_EXCEL_SHEET_FUNC_FALSE), {fekFALSE} - (2, INT_EXCEL_SHEET_FUNC_IF), {fekIF} - (1, INT_EXCEL_SHEET_FUNC_NOT), {fekNOT} - (2, INT_EXCEL_SHEET_FUNC_OR), {fekOR} - (1, INT_EXCEL_SHEET_FUNC_TRUE), {fekTRUE} - - // String functions - (1, INT_EXCEL_SHEET_FUNC_CHAR), {fekCHAR} - (1, INT_EXCEL_SHEET_FUNC_CODE), {fekCODE} - (2, INT_EXCEL_SHEET_FUNC_LEFT), {fekLEFT} - (1, INT_EXCEL_SHEET_FUNC_LOWER), {fekLOWER} - (1, INT_EXCEL_SHEET_FUNC_MID), {fekMID} - (1, INT_EXCEL_SHEET_FUNC_PROPER), {fekPROPER} - (1, INT_EXCEL_SHEET_FUNC_REPLACE), {fekREPLACE} - (2, INT_EXCEL_SHEET_FUNC_RIGHT), {fekRIGHT} - (2, INT_EXCEL_SHEET_FUNC_SUBSTITUTE),{fekSUBSTITUTE} - (1, INT_EXCEL_SHEET_FUNC_TRIM), {fekTRIM} - (1, INT_EXCEL_SHEET_FUNC_UPPER), {fekUPPER} - - // lookup/reference functions - (2, INT_EXCEL_SHEET_FUNC_COLUMN), {fekCOLUMN} - (1, INT_EXCEL_SHEET_FUNC_COLUMNS), {fekCOLUMNS} - (2, INT_EXCEL_SHEET_FUNC_ROW), {fekROW} - (1, INT_EXCEL_SHEET_FUNC_ROWS), {fekROWS} - - // Info functions - (2, INT_EXCEL_SHEET_FUNC_CELL), {fekCELLINFO} - (1, INT_EXCEL_SHEET_FUNC_INFO), {fekINFO} - (1, INT_EXCEL_SHEET_FUNC_ISBLANK), {fekIsBLANK} - (1, INT_EXCEL_SHEET_FUNC_ISERR), {fekIsERR} - (1, INT_EXCEL_SHEET_FUNC_ISERROR), {fekIsERROR} - (1, INT_EXCEL_SHEET_FUNC_ISLOGICAL), {fekIsLOGICAL} - (1, INT_EXCEL_SHEET_FUNC_ISNA), {fekIsNA} - (1, INT_EXCEL_SHEET_FUNC_ISNONTEXT), {fekIsNONTEXT} - (1, INT_EXCEL_SHEET_FUNC_ISNUMBER), {fekIsNUMBER} - (1, INT_EXCEL_SHEET_FUNC_ISREF), {fekIsREF} - (1, INT_EXCEL_SHEET_FUNC_ISTEXT), {fekIsTEXT} - (1, INT_EXCEL_SHEET_FUNC_VALUE), {fekValue} - - // Other operations - (0, INT_EXCEL_TOKEN_TATTR) {fekOpSum} - ); - begin case TokenIDs[AElementKind, 0] of 0: begin