From 27498ff5480b8910c589e976c41e210cc949f28b Mon Sep 17 00:00:00 2001 From: bigchimp Date: Tue, 8 Apr 2014 09:48:30 +0000 Subject: [PATCH] + Add support for more/easier RPN formulas in XLS - Allow relative cell references in RPN formulas - Simplify generation of RPN formulas by using nested function calls - Add writing of cell ranges for formulas in BIFF2/BIFF5 - Simplification of code e.g. by replacing the huge case instruction in TsSpreadBiffWriter.FormulaElementKindToExcelTokenID by a look-up table. Fixes mantis issue #25718; patch by wp; thanks a lot! git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2931 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpspreadsheet/fpspreadsheet.pas | 316 +++++++++++++++- components/fpspreadsheet/fpsutils.pas | 111 +++++- .../fpspreadsheet/tests/manualtests.pas | 2 +- .../fpspreadsheet/tests/testdbwriter.res | Bin 20636 -> 21080 bytes components/fpspreadsheet/xlsbiff2.pas | 107 +++--- components/fpspreadsheet/xlsbiff5.pas | 102 +++--- components/fpspreadsheet/xlsbiff8.pas | 60 +++- components/fpspreadsheet/xlscommon.pas | 336 +++++++++++++++--- 8 files changed, 857 insertions(+), 177 deletions(-) diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index d6e9677a0..3c33372a8 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -14,7 +14,7 @@ unit fpspreadsheet; interface uses - Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding; + Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding, fpsutils; type TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8, @@ -56,15 +56,51 @@ type DoubleValue: double; end; - {@@ Expanded formula. Used by backend modules. Provides more information then the text only } + {@@ Expanded formula. Used by backend modules. Provides more information than the text only + + See http://www.techonthenet.com/excel/formulas/ for an explanation of + meaning and parameters of each formula + + NOTE: When adding or rearranging items make sure to keep the TokenID table + in TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID, unit xlscommon, + in sync !!! + } TFEKind = ( { Basic operands } - fekCell, fekCellRange, fekNum, + fekCell, fekCellRef, fekCellRange, fekNum, fekString, fekBool, fekMissingArg, { Basic operations } - fekAdd, fekSub, fekDiv, fekMul, + fekAdd, fekSub, fekDiv, fekMul, fekPercent, fekPower, fekUMinus, fekUPlus, + fekConcat, // string concatenation + fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, { Built-in/Worksheet Functions} - fekABS, fekDATE, fekROUND, fekTIME, + // math + fekABS, fekACOS, fekACOSH, fekASIN, fekASINH, fekATAN, fekATANH, + fekCOS, fekCOSH, fekDEGREES, fekEXP, fekINT, fekLN, fekLOG, + fekLOG10, fekPI, fekRADIANS, fekRAND, fekROUND, + fekSIGN, fekSIN, fekSINH, fekSQRT, + fekTAN, fekTANH, + // date/time + fekDATE, fekDATEDIF, fekDATEVALUE, fekDAY, fekHOUR, fekMINUTE, fekMONTH, + fekNOW, fekSECOND, fekTIME, fekTIMEVALUE, fekTODAY, fekWEEKDAY, fekYEAR, + // statistical + fekAVEDEV, fekAVERAGE, fekBETADIST, fekBETAINV, fekBINOMDIST, fekCHIDIST, + fekCHIINV, fekCOUNT, fekCOUNTA, fekCOUNTBLANK, fekCOUNTIF, + fekMAX, fekMEDIAN, fekMIN, fekPERMUT, fekPOISSON, fekPRODUCT, + fekSTDEV, fekSTDEVP, fekSUM, fekSUMIF, fekSUMSQ, fekVAR, fekVARP, + // financial + fekFV, fekNPER, fekPV, fekPMT, fekRATE, + // logical + fekAND, fekFALSE, fekIF, fekNOT, fekOR, fekTRUE, + // string + fekCHAR, fekCODE, fekLEFT, fekLOWER, fekMID, fekPROPER, fekREPLACE, fekRIGHT, + fekSUBSTITUTE, fekTRIM, fekUPPER, + // lookup/reference + fekCOLUMN, fekCOLUMNS, fekROW, fekROWS, + // info + fekCELLINFO, fekINFO, fekIsBLANK, fekIsERR, fekIsERROR, + fekIsLOGICAL, fekIsNA, fekIsNONTEXT, fekIsNUMBER, fekIsRef, fekIsTEXT, + fekValue, { Other operations } fekOpSUM ); @@ -72,9 +108,13 @@ type TsFormulaElement = record ElementKind: TFEKind; Row, Row2: Word; // zero-based - Col, Col2: Byte; // zero-based + Col, Col2: Word; // zero-based Param1, Param2: Word; // Extra parameters DoubleValue: double; + IntValue: Word; + StringValue: String; + RelFlags: TsRelFlags; // store info on relative/absolute addresses + ParamsNum: Byte; end; TsExpandedFormula = array of TsFormulaElement; @@ -377,7 +417,55 @@ type WriterClass: TsSpreadWriterClass; Format: TsSpreadsheetFormat; end; - + + {@@ Helper for simplification of RPN formula creation } + PRPNItem = ^TRPNItem; + TRPNItem = record + FE: TsFormulaElement; + Next: PRPNItem; + end; + + {@@ + Simple creation an RPNFormula array to be used in fpspreadsheet. + For each formula element, use one of the RPNxxxx functions implemented here. + They are designed to be nested into each other. Terminate the chain by + using nil. + + Example: + The RPN formula for the string expression "$A1+2" can be created as follows: + + var + f: TsRPNFormula; + + f := CreateRPNFormula( + RPNCellValue('A1', + RPNNumber(2, + RPNFunc(fekAdd, + nil)))); + } + + function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula; + + function RPNBool(AValue: Boolean; + ANext: PRPNItem): PRPNItem; + function RPNCellValue(ACellAddress: String; + ANext: PRPNItem): PRPNItem; overload; + function RPNCellValue(ARow, ACol: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; overload; + function RPNCellRef(ACellAddress: String; + ANext: PRPNItem): PRPNItem; overload; + function RPNCellRef(ARow, ACol: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; overload; + function RPNCellRange(ACellRangeAddress: String; + ANext: PRPNItem): PRPNItem; overload; + function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; overload; + function RPNMissingArg(ANext: PRPNItem): PRPNItem; + function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem; + function RPNString(AValue: String; ANext: PRPNItem): PRPNItem; + function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem; overload; + function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem; overload; + var GsSpreadFormats: array of TsSpreadFormatData; @@ -1874,6 +1962,220 @@ begin end; +{ Simplified creation of RPN formulas } + +function NewRPNItem: PRPNItem; +begin + Result := GetMem(SizeOf(TRPNItem)); + FillChar(Result^.FE, SizeOf(Result^.FE), 0); + Result^.FE.StringValue := ''; +end; + +procedure DisposeRPNItem(AItem: PRPNItem); +begin + if AItem <> nil then + FreeMem(AItem, SizeOf(TRPNItem)); +end; + +{@@ + Creates a boolean value entry in the RPN array. +} +function RPNBool(AValue: Boolean; ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekBool; + if AValue then Result^.FE.DoubleValue := 1.0 else Result^.FE.DoubleValue := 0.0; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a cell value, specifed by its + address, e.g. 'A1'. Takes care of absolute and relative cell addresses. +} +function RPNCellValue(ACellAddress: String; ANext: PRPNItem): PRPNItem; +var + r,c: Integer; + flags: TsRelFlags; +begin + if not ParseCellString(ACellAddress, r, c, flags) then + raise Exception.CreateFmt('"%s" is not a valid cell address.', [ACellAddress]); + Result := RPNCellValue(r,c, flags, ANext); +end; + +{@@ + Creates an entry in the RPN array for a cell value, specifed by its + row and column index and a flag containing information on relative addresses. +} +function RPNCellValue(ARow, ACol: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekCell; + Result^.FE.Row := ARow; + Result^.FE.Col := ACol; + Result^.FE.RelFlags := AFlags; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a cell reference, specifed by its + address, e.g. 'A1'. Takes care of absolute and relative cell addresses. + "Cell reference" means that all properties of the cell can be handled. + Note that most Excel formulas with cells require the cell value only + (--> RPNCellValue) +} +function RPNCellRef(ACellAddress: String; ANext: PRPNItem): PRPNItem; +var + r,c: Integer; + flags: TsRelFlags; +begin + if not ParseCellString(ACellAddress, r, c, flags) then + raise Exception.CreateFmt('"%s" is not a valid cell address.', [ACellAddress]); + Result := RPNCellRef(r,c, flags, ANext); +end; + +{@@ + Creates an entry in the RPN array for a cell reference, specifed by its + row and column index and flags containing information on relative addresses. + "Cell reference" means that all properties of the cell can be handled. + Note that most Excel formulas with cells require the cell value only + (--> RPNCellValue) +} +function RPNCellRef(ARow, ACol: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekCellRef; + Result^.FE.Row := ARow; + Result^.FE.Col := ACol; + Result^.FE.RelFlags := AFlags; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a range of cells, specified by an + Excel-style address, e.g. A1:G5. As in Excel, use a $ sign to indicate + absolute addresses. +} +function RPNCellRange(ACellRangeAddress: String; ANext: PRPNItem): PRPNItem; +var + r1,c1, r2,c2: Integer; + flags: TsRelFlags; +begin + if not ParseCellRangeString(ACellRangeAddress, r1,c1, r2,c2, flags) then + raise Exception.CreateFmt('"%s" is not a valid cell range address.', [ACellRangeAddress]); + Result := RPNCellRange(r1,c1, r2,c2, flags, ANext); +end; + +{@@ + Creates an entry in the RPN array for a range of cells, specified by the + row/column indexes of the top/left and bottom/right corners of the block. + The flags indicate relative indexes. +} +function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekCellRange; + Result^.FE.Row := ARow; + Result^.FE.Col := ACol; + Result^.FE.Row2 := ARow2; + Result^.FE.Col2 := ACol2; + Result^.FE.RelFlags := AFlags; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a missing argument in of function call. +} +function RPNMissingArg(ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekMissingArg; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a number. Integers and floating-point + values can be used likewise. +} +function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekNum; + Result^.FE.DoubleValue := AValue; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for a string. +} +function RPNString(AValue: String; ANext: PRPNItem): PRPNItem; +begin + Result := NewRPNItem; + Result^.FE.ElementKind := fekString; + Result^.FE.StringValue := AValue; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for an Excel function or operation + specified by its TokenID (--> TFEKind). Note that array elements for all + needed parameters must have been created before. +} +function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem; +begin + if ord(AToken) < ord(fekAdd) then + raise Exception.Create('No basic tokens allowed here.'); + Result := NewRPNItem; + Result^.FE.ElementKind := AToken; + Result^.Next := ANext; +end; + +{@@ + Creates an entry in the RPN array for an Excel function or operation + specified by its TokenID (--> TFEKind). Specify the number of parameters used. + They must have been created before. +} +function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem; +begin + Result := RPNFunc(AToken, ANext); + Result^.FE.ParamsNum := ANumParams; +end; + +{@@ + Creates an RPN formula by a single call using nested RPN items. +} +function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula; +var + item: PRPNItem; + nextitem: PRPNItem; + n: Integer; +begin + // Determine count of RPN elements + n := 0; + item := AItem; + while item <> nil do begin + inc(n); + item := item^.Next; + end; + + // Set array length of TsRPNFormula result + SetLength(Result, n); + + // Copy FormulaElements to result and free temporary RPNItems + item := AItem; + n := 0; + while item <> nil do begin + nextitem := item^.Next; + Result[n] := item^.FE; + inc(n); + DisposeRPNItem(item); + item := nextitem; + end; +end; + + finalization SetLength(GsSpreadFormats, 0); diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index d7b2ffe71..b55909984 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -14,6 +14,9 @@ uses type TsSelectionDirection = (fpsVerticalSelection, fpsHorizontalSelection); + TsRelFlag = (rfRelRow, rfRelCol, rfRelRow2, rfRelCol2); + TsRelFlags = set of TsRelFlag; + const // Date formatting string for unambiguous date/time display as strings // Can be used for text output when date/time cell support is not available @@ -35,8 +38,13 @@ function WideStringLEToN(const AValue: WideString): WideString; function ParseIntervalString(const AStr: string; var AFirstCellRow, AFirstCellCol, ACount: Integer; var ADirection: TsSelectionDirection): Boolean; +function ParseCellRangeString(const AStr: string; + var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer; + var AFlags: TsRelFlags): Boolean; function ParseCellString(const AStr: string; - var ACellRow, ACellCol: Integer): Boolean; + var ACellRow, ACellCol: Integer; var AFlags: TsRelFlags): Boolean; overload; +function ParseCellString(const AStr: string; + var ACellRow, ACellCol: Integer): Boolean; overload; function ParseCellRowString(const AStr: string; var AResult: Integer): Boolean; function ParseCellColString(const AStr: string; @@ -148,11 +156,17 @@ function ParseIntervalString(const AStr: string; var AFirstCellRow, AFirstCellCol, ACount: Integer; var ADirection: TsSelectionDirection): Boolean; var - Cells: TStringList; + //Cells: TStringList; LastCellRow, LastCellCol: Integer; + p: Integer; + s1, s2: String; begin Result := True; + { Simpler: + use "pos" instead of the TStringList overhead. + And: the StringList is not free'ed here + // First get the cells Cells := TStringList.Create; ExtractStrings([':'],[], PChar(AStr), Cells); @@ -162,6 +176,19 @@ begin if not Result then Exit; Result := ParseCellString(Cells[1], LastCellRow, LastCellCol); if not Result then Exit; + } + + // First find the position of the colon and split into parts + p := pos(':', AStr); + if p = 0 then exit(false); + s1 := copy(AStr, 1, p-1); + s2 := copy(AStr, p+1, Length(AStr)); + + // Then parse each of them + Result := ParseCellString(s1, AFirstCellRow, AFirstCellCol); + if not Result then Exit; + Result := ParseCellString(s2, LastCellRow, LastCellCol); + if not Result then Exit; if AFirstCellRow = LastCellRow then begin @@ -176,6 +203,42 @@ begin else Exit(False); end; +{@@ + Parses strings like A5:C10 into a range selection information. + Return also information on relative/absolute cells. +} +function ParseCellRangeString(const AStr: string; + var AFirstCellRow, AFirstCellCol, ALastCellRow, ALastCellCol: Integer; + var AFlags: TsRelFlags): Boolean; +var + p: Integer; + s: String; +begin + Result := True; + + // First find the colon + p := pos(':', AStr); + if p = 0 then exit(false); + + // Analyze part after the colon + s := copy(AStr, p+1, Length(AStr)); + Result := ParseCellString(s, ALastCellRow, ALastCellCol, AFlags); + if not Result then exit; + if (rfRelRow in AFlags) then begin + Include(AFlags, rfRelRow2); + Exclude(AFlags, rfRelRow); + end; + if (rfRelCol in AFlags) then begin + Include(AFlags, rfRelCol2); + Exclude(AFlags, rfRelCol); + end; + + // Analyze part before the colon + s := copy(AStr, 1, p-1); + Result := ParseCellString(s, AFirstCellRow, AFirstCellCol, AFlags); +end; + + {@@ Parses a cell string, like 'A1' into zero-based column and row numbers @@ -184,13 +247,17 @@ end; 0 - Reading Column part 1 (necesserely needs a letter) 1 - Reading Column part 2, but could be the first number as well 2 - Reading Row + + 'AFlags' indicates relative addresses. } -function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer): Boolean; +function ParseCellString(const AStr: string; var ACellRow, ACellCol: Integer; + var AFlags: TsRelFlags): Boolean; var i: Integer; state: Integer; Col, Row: string; lChar: Char; + isAbs: Boolean; const cLetters = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'W', 'X', 'Y', 'Z']; @@ -201,11 +268,20 @@ begin state := 0; Col := ''; Row := ''; + AFlags := [rfRelCol, rfRelRow]; + isAbs := false; // Separates the string into a row and a col - for i := 0 to Length(AStr) - 1 do + for i := 1 to Length(AStr) do begin - lChar := AStr[i + 1]; + lChar := AStr[i]; + + if lChar = '$' then begin + if isAbs then + exit(false); + isAbs := true; + continue; + end; case state of @@ -214,6 +290,9 @@ begin if lChar in cLetters then begin Col := lChar; + if isAbs then + Exclude(AFlags, rfRelCol); + isAbs := false; state := 1; end else Exit(False); @@ -221,10 +300,14 @@ begin 1: begin - if lChar in cLetters then Col := Col + lChar + if lChar in cLetters then + Col := Col + lChar else if lChar in cDigits then begin Row := lChar; + if isAbs then + Exclude(AFlags, rfRelRow); + isAbs := false; state := 2; end else Exit(False); @@ -244,6 +327,16 @@ begin ParseCellColString(Col, ACellCol); end; +{ for compatibility with old version which does not return flags for relative + cell addresses } +function ParseCellString(const AStr: string; + var ACellRow, ACellCol: Integer): Boolean; +var + flags: TsRelFlags; +begin + ParseCellString(AStr, ACellRow, ACellCol, flags); +end; + function ParseCellRowString(const AStr: string; var AResult: Integer): Boolean; begin try @@ -267,6 +360,12 @@ begin AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS + Ord(AStr[2]) - Ord('A'); end + else if Length(AStr) = 3 then + begin + AResult := (Ord(AStr[1]) - Ord('A') + 1) * INT_NUM_LETTERS * INT_NUM_LETTERS + + (Ord(AStr[2]) - Ord('A') + 1) * INT_NUM_LETTERS + + Ord(AStr[3]) - Ord('A'); + end else Exit(False); Result := True; diff --git a/components/fpspreadsheet/tests/manualtests.pas b/components/fpspreadsheet/tests/manualtests.pas index 2224ad5af..4137caaa8 100644 --- a/components/fpspreadsheet/tests/manualtests.pas +++ b/components/fpspreadsheet/tests/manualtests.pas @@ -50,7 +50,7 @@ type procedure TearDown; override; published // Current fpspreadsheet does not yet have support for new RPN formulas - {.$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT} + {$DEFINE FPSPREAD_HAS_NEWRPNSUPPORT} {$IFDEF FPSPREAD_HAS_NEWRPNSUPPORT} // As described in bug 25718: Feature request & patch: Implementation of writing more functions // Writes all rpn formulas. Use Excel or Open/LibreOffice to check validity. diff --git a/components/fpspreadsheet/tests/testdbwriter.res b/components/fpspreadsheet/tests/testdbwriter.res index 8487027e1f04314d8be95d5308650c5814b56c9b..6d22782f5ebfaf2eb3e81988e27f4985ea112fd0 100644 GIT binary patch delta 3737 zcmb_fTWlNG5j9DP6jxk{k|;%#`B=Y1$)c9jgEnP~mRHM_v@&1aU8;7S_)$x8l_*jy z$u62Ws6P@EP2fQ4?E^L(J3&$;F;WDmf1JLo0tFi6Bj`ts+CdT+?MHzYc@=1zpg_}e zcllTrO8HZM%-)$hXYS10xpObCzG%7ovSppK)+-x3zh1{OEVGe+VB>jzJsn1f#n!g* zJI)4XnNC)Y+IE=xx#bXTaggn-SK6l;Htcx5u^@N-j`ZEla7c|UXycK zF)gU71YgUGhUu8`dqJPD2gtm=T^}99uEKlr z3%l1G;zxEGqRY{*Q!h~*oq;V@%<0Y=Za(GM4dquIKIDVr6e%MphgEJyP{b)g=^2d0 zdc!Pv&k^-*Wv3jeoq}^}FTp#PGWjr_5%T%8EDE$3 z?z6@B95qjw)=~>SU3VcxMM8$P#uT9Shj^LC@o@kvR@77OG*Xqh+wGkIR$=~Jub+#_!aUrA}0)A@29!IPHO0FGQv z<#Q@E0EC$5u}KC)CRz_*jf<@wvTEAgug$Hlt(;p*uB}j8q~EKr+%-rpd5`|T zjNa;^ef_>}w21__cfvPR6upqrlBjW5dtsJk=2w;M@aZDw37sJRsS{gY7OL1dSX?`lfAFkKy>e= z$D%K3Liq+?s)4vSNZU%qyT;*Wa(U=V4OC{hw+i)0j3NIX zUdQEHuDs%7vCQk0_*yK0CjnlXM{n?tCpyUHzUvsB*w5?F%;)w` zg1fVyp0u+EJmj`mkC7XrGx1TpbIFa#?c{bGZ(s6Od>oV+c~Dm!CRaut(nl3gyaf2m z2;SGQ_e)SdF$(%{;v4#S$YJz9yk$p^8xnGLvRFk^PR mE|fK|^qng1&)KjEC*{oCOF1*nmN$EUCO?21hh?^H+x8zHrL*7w delta 3111 zcmbtWeQaA-6`$jL`6hAF#CGiX>vf3}J9&Mvouo?Ktl}@5}>VgNE-zKCprvqB8V&Hez9H8Q27(yE{p zwY;JxV*>q~-3G>lS+=ziFCmG7BI}Bv%TpOekn{OWIw9-noT`x^-m-L}wblozHUPc0 z&p|@5-S|YlScbh?yB{|w?1S21_a2yXJfY+nX(A(Qno^;Dq%Mf3>pO9w&WOxnu&qmEkM$T(WQcq`TDbYU>YO}~g-e`7ILjFOs3)h=nqZCgI z$KavUoVIgTP-fB^t(J7sIG%&U^mP0!Zp2z#D7J*0dos_jNNJ-!%E+h1G_CA%Vz+k+ zmb$wweQ;U_d59USvl8NGt)0xgk1`}_$sVbAi+Iy2;zR2Mcc3o?4FVOe>O{Cgn87Dw0HAqB$y zdL8#_@N!oezok!;d(cT}dRJ(kjB<^yoRbz#33$Wh>?j%6=2zEN)|cXID^fa%J1z;{ zt}p(7Bu~48lq8CG+yMtWb^1EZC<$GlODvosr&pFAhsVx*mW<`&oISA~^x z0u7c>%no%aT%7cLn(*=w*bjg7#L?}2yw0G)74HGu_D0a|3nJzl##!HfeD{98g?wUV zdHt+#@(H}_>7ngp%QugdUoU%p%RkUhf~TeB)rC{>RX&7-xusJ~_wlpqT**hS#XtRj zxcZLpxfE&CE zAGll%YLViM_3E7DHC4lXW3UqJCovO#sT$N3DM7cQkHH#k!tWzh_jNux$RpT_Hdc*b zW5f>ku#e|pE4qM*ktY0N`0Ldo*%%2KaPi>R+`Er_d&a(UwhsDuIo6I|vu<1XXU#81 zyKv%&1OFMlh!@8sTGltlQs^9?$7IZf^W%AZM)6p3{Ch;}i@|zm3dasTLajE4lYVR+ zn&rrcV`B`7%dr;2b~pA6_Q!|t+5;9`d*BR}$MLcFEZ4mkw^AMl+9y=ZO^o9Ci98$4 z^!<&%;Teh<#4Cqe@#DjlwVpS4|1o)o%m0#pf>mhT=yo-P}%6w4LaJa$HFib$W$!DjqdETLe8P2--tha!QU7$kP$ROr?H>w=)5n zTdHj2N*40_)9k~Ps<1jeRF-}>JzSQON7-ReYXf)4F1efSsKWe3Gih@dOtf*wjNW1W$88bKv`N)Dm*veg6f_VTN7+ diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 550257583..f9547571b 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -57,9 +57,8 @@ type { TsSpreadBIFF2Writer } - TsSpreadBIFF2Writer = class(TsCustomSpreadWriter) + TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter) private - function FEKindToExcelID(AElement: TFEKind; var AParamsNum, AFuncNum: Byte): Byte; procedure WriteCellFormatting(AStream: TStream; ACell: PCell); public { General writing methods } @@ -86,8 +85,8 @@ const { Cell Addresses constants } MASK_EXCEL_ROW = $3FFF; - MASK_EXCEL_RELATIVE_ROW = $4000; - MASK_EXCEL_RELATIVE_COL = $8000; + 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; @@ -96,47 +95,6 @@ const { TsSpreadBIFF2Writer } -function TsSpreadBIFF2Writer.FEKindToExcelID(AElement: TFEKind; var AParamsNum, AFuncNum: Byte): Byte; -begin - AFuncNum := 0; - - case AElement of - { Operands } - fekCell: Result := INT_EXCEL_TOKEN_TREFV; - fekNum: Result := INT_EXCEL_TOKEN_TNUM; - { Operators } - fekAdd: Result := INT_EXCEL_TOKEN_TADD; - fekSub: Result := INT_EXCEL_TOKEN_TSUB; - fekDiv: Result := INT_EXCEL_TOKEN_TDIV; - fekMul: Result := INT_EXCEL_TOKEN_TMUL; - { Built-in/worksheet functions } - fekABS: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 1; - AFuncNum := INT_EXCEL_SHEET_FUNC_ABS; - end; - fekDATE: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 3; - AFuncNum := INT_EXCEL_SHEET_FUNC_DATE; - end; - fekROUND: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 2; - AFuncNum := INT_EXCEL_SHEET_FUNC_ROUND; - end; - fekTIME: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 3; - AFuncNum := INT_EXCEL_SHEET_FUNC_TIME; - end; - end; -end; - procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell); var BorderByte: Byte = 0; @@ -239,7 +197,10 @@ var i: Integer; RPNLength: Word; TokenArraySizePos, RecordSizePos, FinalPos: Cardinal; - FormulaKind, ParamsNum, ExtraInfo: Byte; + FormulaKind, ExtraInfo: Word; + r: Cardinal; + len: Integer; + s: ansistring; begin RPNLength := 0; FormulaResult := 0.0; @@ -258,7 +219,7 @@ begin AStream.WriteByte($0); AStream.WriteByte($0); - { Result of the formula in IEE 754 floating-point value } + { Result of the formula in IEEE 754 floating-point value } AStream.WriteBuffer(FormulaResult, 8); { 0 = Do not recalculate @@ -276,8 +237,9 @@ begin { Formula data (RPN token array) } for i := 0 to Length(AFormula) - 1 do begin + { Token identifier } - FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo); + FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo); AStream.WriteByte(FormulaKind); Inc(RPNLength); @@ -295,17 +257,60 @@ begin Inc(RPNLength, 8); end; + INT_EXCEL_TOKEN_TSTR: + begin + s := ansistring(AFormula[i].StringValue); + len := Length(s); + AStream.WriteByte(len); + AStream.WriteBuffer(s[1], len); + Inc(RPNLength, len + 1); + end; + + INT_EXCEL_TOKEN_TBOOL: + begin + AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0)); + inc(RPNLength, 1); + end; + INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: begin - AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW); + r := AFormula[i].Row and MASK_EXCEL_ROW; + if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(r); AStream.WriteByte(AFormula[i].Col); Inc(RPNLength, 3); end; + INT_EXCEL_TOKEN_TAREA_R: { fekCellRange } + begin + r := AFormula[i].Row and MASK_EXCEL_ROW; + if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(WordToLE(r)); + + r := AFormula[i].Row2 and MASK_EXCEL_ROW; + if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(WordToLE(r)); + + AStream.WriteByte(AFormula[i].Col); + AStream.WriteByte(AFormula[i].Col2); + Inc(RPNLength, 6); + end; + + INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: + begin + AStream.WriteByte(Lo(ExtraInfo)); + Inc(RPNLength, 1); + end; + INT_EXCEL_TOKEN_FUNCVAR_V: begin - AStream.WriteByte(ParamsNum); - AStream.WriteByte(ExtraInfo); + AStream.WriteByte(AFormula[i].ParamsNum); + AStream.WriteByte(Lo(ExtraInfo)); + // taking only the low-bytes, the high-bytes are needed for compatibility + // with other BIFF formats... Inc(RPNLength, 2); end; diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 521f7f3e3..be98d31d4 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -106,7 +106,6 @@ type TsSpreadBIFF5Writer = class(TsSpreadBIFFWriter) private WorkBookEncoding: TsEncoding; - function FEKindToExcelID(AElement: TFEKind; var AParamsNum: Byte; var AExtra: Word): Byte; public // constructor Create; // destructor Destroy; override; @@ -156,8 +155,8 @@ const { Cell Addresses constants } MASK_EXCEL_ROW = $3FFF; - MASK_EXCEL_RELATIVE_ROW = $4000; - MASK_EXCEL_RELATIVE_COL = $8000; + 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; @@ -260,48 +259,6 @@ const { TsSpreadBIFF5Writer } -function TsSpreadBIFF5Writer.FEKindToExcelID(AElement: TFEKind; - var AParamsNum: Byte; var AExtra: Word): Byte; -begin - AExtra := 0; - - case AElement of - { Operands } - fekCell: Result := INT_EXCEL_TOKEN_TREFV; - fekNum: Result := INT_EXCEL_TOKEN_TNUM; - { Operators } - fekAdd: Result := INT_EXCEL_TOKEN_TADD; - fekSub: Result := INT_EXCEL_TOKEN_TSUB; - fekDiv: Result := INT_EXCEL_TOKEN_TDIV; - fekMul: Result := INT_EXCEL_TOKEN_TMUL; - { Built-in/Worksheet Function } - fekABS: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 1; - AExtra := INT_EXCEL_SHEET_FUNC_ABS; - end; - fekDATE: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 3; - AExtra := INT_EXCEL_SHEET_FUNC_DATE; - end; - fekROUND: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 2; - AExtra := INT_EXCEL_SHEET_FUNC_ROUND; - end; - fekTIME: - begin - Result := INT_EXCEL_TOKEN_FUNCVAR_V; - AParamsNum := 3; - AExtra := INT_EXCEL_SHEET_FUNC_TIME; - end; - end; -end; - {******************************************************************* * TsSpreadBIFF5Writer.WriteToFile () * @@ -647,8 +604,11 @@ var i: Integer; RPNLength: Word; TokenArraySizePos, RecordSizePos, FinalPos: Int64; - FormulaKind, ParamsNum: Byte; + FormulaKind: Word; ExtraInfo: Word; + r: Cardinal; + len: Integer; + s: ansistring; begin RPNLength := 0; FormulaResult := 0.0; @@ -665,7 +625,7 @@ begin { Index to XF Record } AStream.WriteWord($0000); - { Result of the formula in IEE 754 floating-point value } + { Result of the formula in IEEE 754 floating-point value } AStream.WriteBuffer(FormulaResult, 8); { Options flags } @@ -686,7 +646,7 @@ begin for i := 0 to Length(AFormula) - 1 do begin { Token identifier } - FormulaKind := FEKindToExcelID(AFormula[i].ElementKind, ParamsNum, ExtraInfo); + FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo); AStream.WriteByte(FormulaKind); Inc(RPNLength); @@ -704,13 +664,48 @@ begin Inc(RPNLength, 8); end; + INT_EXCEL_TOKEN_TSTR: + begin + s := ansistring(AFormula[i].StringValue); + len := Length(s); + AStream.WriteByte(len); + AStream.WriteBuffer(s[1], len); + Inc(RPNLength, len + 1); + end; + + INT_EXCEL_TOKEN_TBOOL: { fekBool } + begin + AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0)); + inc(RPNLength, 1); + end; + INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: begin - AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW); + r := AFormula[i].Row and MASK_EXCEL_ROW; + if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(r); AStream.WriteByte(AFormula[i].Col); Inc(RPNLength, 3); end; + INT_EXCEL_TOKEN_TAREA_R: { fekCellRange } + begin + r := AFormula[i].Row and MASK_EXCEL_ROW; + if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(WordToLE(r)); + + r := AFormula[i].Row2 and MASK_EXCEL_ROW; + if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(WordToLE(r)); + + AStream.WriteByte(AFormula[i].Col); + AStream.WriteByte(AFormula[i].Col2); + Inc(RPNLength, 6); + end; + { sOffset Size Contents 0 1 22H (tFuncVarR), 42H (tFuncVarV), 62H (tFuncVarA) @@ -724,9 +719,16 @@ begin 14-0 7FFFH Index to a built-in sheet function (➜3.11) or a macro command 15 8000H 0 = Built-in function; 1 = Macro command } + // Functions + INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: + begin + AStream.WriteWord(WordToLE(ExtraInfo)); + Inc(RPNLength, 2); + end; + INT_EXCEL_TOKEN_FUNCVAR_V: begin - AStream.WriteByte(ParamsNum); + AStream.WriteByte(AFormula[i].ParamsNum); AStream.WriteWord(WordToLE(ExtraInfo)); Inc(RPNLength, 3); end; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 0d4631966..4faeb09da 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -176,6 +176,7 @@ const { Excel record IDs } INT_EXCEL_ID_BOF = $0809; INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs + INT_EXCEL_ID_COUNTRY = $008C; INT_EXCEL_ID_EOF = $000A; INT_EXCEL_ID_DIMENSIONS = $0200; INT_EXCEL_ID_FONT = $0031; @@ -196,12 +197,13 @@ const INT_EXCEL_ID_PALETTE = $0092; INT_EXCEL_ID_CODEPAGE = $0042; INT_EXCEL_ID_FORMAT = $041E; + INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3; { Cell Addresses constants } MASK_EXCEL_ROW = $3FFF; MASK_EXCEL_COL_BITS_BIFF8=$00FF; - MASK_EXCEL_RELATIVE_ROW = $4000; - MASK_EXCEL_RELATIVE_COL = $8000; + 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_BIFF8_VER = $0600; @@ -634,7 +636,7 @@ begin SetLength(Boundsheets, len + 1); Boundsheets[len] := WriteBoundsheet(AStream, AData.GetWorksheetByIndex(i).Name); end; - + WriteEOF(AStream); { Write each worksheet } @@ -872,6 +874,7 @@ begin AStream.WriteBuffer(WideStringToLE(WideFontName)[1], Len * Sizeof(WideChar)); end; + {******************************************************************* * TsSpreadBIFF8Writer.WriteFormula () * @@ -967,10 +970,13 @@ procedure TsSpreadBIFF8Writer.WriteRPNFormula(AStream: TStream; const ARow, var FormulaResult: double; i: Integer; + len: Integer; RPNLength: Word; TokenArraySizePos, RecordSizePos, FinalPos: Int64; - TokenID: Byte; + TokenID: Word; lSecondaryID: Word; + c: Cardinal; + wideStr: WideString; begin RPNLength := 0; FormulaResult := 0.0; @@ -988,7 +994,7 @@ begin //AStream.WriteWord(0); WriteXFIndex(AStream, ACell); - { Result of the formula in IEE 754 floating-point value } + { Result of the formula in IEEE 754 floating-point value } AStream.WriteBuffer(FormulaResult, 8); { Options flags } @@ -1019,7 +1025,10 @@ begin INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } begin AStream.WriteWord(AFormula[i].Row); - AStream.WriteWord(AFormula[i].Col and MASK_EXCEL_COL_BITS_BIFF8); + c := AFormula[i].Col and MASK_EXCEL_COL_BITS_BIFF8; + if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; + if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; + AStream.WriteWord(c); Inc(RPNLength, 4); end; @@ -1035,8 +1044,14 @@ begin } AStream.WriteWord(WordToLE(AFormula[i].Row)); AStream.WriteWord(WordToLE(AFormula[i].Row2)); - AStream.WriteWord(WordToLE(AFormula[i].Col)); - AStream.WriteWord(WordToLE(AFormula[i].Col2)); + c := AFormula[i].Col; + if (rfRelCol in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; + if (rfRelRow in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; + AStream.WriteWord(WordToLE(c)); + c := AFormula[i].Col2; + if (rfRelCol2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_COL; + if (rfRelRow2 in AFormula[i].RelFlags) then c := c or MASK_EXCEL_RELATIVE_ROW; + AStream.WriteWord(WordToLE(c)); Inc(RPNLength, 8); end; @@ -1046,6 +1061,23 @@ begin Inc(RPNLength, 8); end; + INT_EXCEL_TOKEN_TSTR: { fekString } + begin + // string constant is stored as widestring in BIFF8 + wideStr := AFormula[i].StringValue; + len := Length(wideStr); + AStream.WriteByte(len); // char count in 1 byte + AStream.WriteByte(1); // Widestring flags, 1=regular unicode LE string + AStream.WriteBuffer(WideStringToLE(wideStr)[1], len * Sizeof(WideChar)); + Inc(RPNLength, 1 + 1 + len*SizeOf(WideChar)); + end; + + INT_EXCEL_TOKEN_TBOOL: { fekBool } + begin + AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0)); + inc(RPNLength, 1); + end; + { binary operation tokens } INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL, INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end; @@ -1060,13 +1092,21 @@ begin Inc(RPNLength, 3); end; - // Functions + // Functions with fixed parameter count INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: begin - AStream.WriteWord(lSecondaryID); + AStream.WriteWord(WordToLE(lSecondaryID)); Inc(RPNLength, 2); end; + // Functions with variable parameter count + INT_EXCEL_TOKEN_FUNCVAR_V: + begin + AStream.WriteByte(AFormula[i].ParamsNum); + AStream.WriteWord(WordToLE(lSecondaryID)); + Inc(RPNLength, 3); + end; + else end; end; diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index db8cdc105..bb5ed3695 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -26,19 +26,23 @@ const INT_EXCEL_TOKEN_TSUB = $04; INT_EXCEL_TOKEN_TMUL = $05; INT_EXCEL_TOKEN_TDIV = $06; - INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation - INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation - INT_EXCEL_TOKEN_TLT = $09; // Less than - INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal - INT_EXCEL_TOKEN_TEQ = $0B; // Equal - INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal - INT_EXCEL_TOKEN_TGT = $0D; // Greater than - INT_EXCEL_TOKEN_TNE = $0E; // Not equal + INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^ + INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation & + INT_EXCEL_TOKEN_TLT = $09; // Less than < + INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <= + INT_EXCEL_TOKEN_TEQ = $0B; // Equal = + INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >= + INT_EXCEL_TOKEN_TGT = $0D; // Greater than > + INT_EXCEL_TOKEN_TNE = $0E; // Not equal <> INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection INT_EXCEL_TOKEN_TLIST = $10; // Cell range list INT_EXCEL_TOKEN_TRANGE = $11; // Cell range + INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus + + INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus + + INT_EXCEL_TOKEN_TPERCENT= $14; // Percent (%, divides operand by 100) { Constant Operand Tokens, 3.8} + INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand INT_EXCEL_TOKEN_TSTR = $17; //string INT_EXCEL_TOKEN_TBOOL = $1D; //boolean INT_EXCEL_TOKEN_TINT = $1E; //integer @@ -49,6 +53,9 @@ const INT_EXCEL_TOKEN_TREFR = $24; INT_EXCEL_TOKEN_TREFV = $44; INT_EXCEL_TOKEN_TREFA = $64; + INT_EXCEL_TOKEN_TAREA_R = $25; + INT_EXCEL_TOKEN_TAREA_V = $45; + INT_EXCEL_TOKEN_TAREA_A = $65; { Function Tokens } // _R: reference; _V: value; _A: array @@ -61,13 +68,109 @@ const INT_EXCEL_TOKEN_FUNCVAR_R = $22; INT_EXCEL_TOKEN_FUNCVAR_V = $42; INT_EXCEL_TOKEN_FUNCVAR_A = $62; - INT_EXCEL_TOKEN_TAREA_R = $25; { Built-in/worksheet functions } - INT_EXCEL_SHEET_FUNC_ABS = 24; // $18 - INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B - INT_EXCEL_SHEET_FUNC_DATE = 65; // $41 - INT_EXCEL_SHEET_FUNC_TIME = 66; // $42 + INT_EXCEL_SHEET_FUNC_COUNT = 0; + INT_EXCEL_SHEET_FUNC_IF = 1; + INT_EXCEL_SHEET_FUNC_ISNA = 2; + INT_EXCEL_SHEET_FUNC_ISERROR = 3; + INT_EXCEL_SHEET_FUNC_SUM = 4; + INT_EXCEL_SHEET_FUNC_AVERAGE = 5; + INT_EXCEL_SHEET_FUNC_MIN = 6; + INT_EXCEL_SHEET_FUNC_MAX = 7; + INT_EXCEL_SHEET_FUNC_ROW = 8; + INT_EXCEL_SHEET_FUNC_COLUMN = 9; + INT_EXCEL_SHEET_FUNC_STDEV = 12; + INT_EXCEL_SHEET_FUNC_SIN = 15; + INT_EXCEL_SHEET_FUNC_COS = 16; + INT_EXCEL_SHEET_FUNC_TAN = 17; + INT_EXCEL_SHEET_FUNC_ATAN = 18; + INT_EXCEL_SHEET_FUNC_PI = 19; + INT_EXCEL_SHEET_FUNC_SQRT = 20; + INT_EXCEL_SHEET_FUNC_EXP = 21; + INT_EXCEL_SHEET_FUNC_LN = 22; + INT_EXCEL_SHEET_FUNC_LOG10 = 23; + INT_EXCEL_SHEET_FUNC_ABS = 24; // $18 + INT_EXCEL_SHEET_FUNC_INT = 25; + INT_EXCEL_SHEET_FUNC_SIGN = 26; + INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B + INT_EXCEL_SHEET_FUNC_MID = 31; + INT_EXCEL_SHEET_FUNC_VALUE = 33; + INT_EXCEL_SHEET_FUNC_TRUE = 34; + INT_EXCEL_SHEET_FUNC_FALSE = 35; + INT_EXCEL_SHEET_FUNC_AND = 36; + INT_EXCEL_SHEET_FUNC_OR = 37; + INT_EXCEL_SHEET_FUNC_NOT = 38; + INT_EXCEL_SHEET_FUNC_VAR = 46; + INT_EXCEL_SHEET_FUNC_PV = 56; + INT_EXCEL_SHEET_FUNC_FV = 57; + INT_EXCEL_SHEET_FUNC_NPER = 58; + INT_EXCEL_SHEET_FUNC_PMT = 59; + INT_EXCEL_SHEET_FUNC_RATE = 60; + INT_EXCEL_SHEET_FUNC_RAND = 63; + INT_EXCEL_SHEET_FUNC_DATE = 65; // $41 + INT_EXCEL_SHEET_FUNC_TIME = 66; // $42 + INT_EXCEL_SHEET_FUNC_DAY = 67; + INT_EXCEL_SHEET_FUNC_MONTH = 68; + INT_EXCEL_SHEET_FUNC_YEAR = 69; + INT_EXCEL_SHEET_FUNC_WEEKDAY = 70; + INT_EXCEL_SHEET_FUNC_HOUR = 71; + INT_EXCEL_SHEET_FUNC_MINUTE = 72; + INT_EXCEL_SHEET_FUNC_SECOND = 73; + INT_EXCEL_SHEET_FUNC_NOW = 74; + INT_EXCEL_SHEET_FUNC_ROWS = 76; + INT_EXCEL_SHEET_FUNC_COLUMNS = 77; + INT_EXCEL_SHEET_FUNC_ASIN = 98; + INT_EXCEL_SHEET_FUNC_ACOS = 99; + INT_EXCEL_SHEET_FUNC_ISREF = 105; + INT_EXCEL_SHEET_FUNC_LOG = 109; + INT_EXCEL_SHEET_FUNC_CHAR = 111; + INT_EXCEL_SHEET_FUNC_LOWER = 112; + INT_EXCEL_SHEET_FUNC_UPPER = 113; + INT_EXCEL_SHEET_FUNC_PROPER = 114; + INT_EXCEL_SHEET_FUNC_LEFT = 115; + INT_EXCEL_SHEET_FUNC_RIGHT = 116; + INT_EXCEL_SHEET_FUNC_TRIM = 118; + INT_EXCEL_SHEET_FUNC_REPLACE = 119; + INT_EXCEL_SHEET_FUNC_SUBSTITUTE = 120; + INT_EXCEL_SHEET_FUNC_CODE = 121; + INT_EXCEL_SHEET_FUNC_CELL = 125; + INT_EXCEL_SHEET_FUNC_ISERR = 126; + INT_EXCEL_SHEET_FUNC_ISTEXT = 127; + INT_EXCEL_SHEET_FUNC_ISNUMBER = 128; + INT_EXCEL_SHEET_FUNC_ISBLANK = 129; + INT_EXCEL_SHEET_FUNC_DATEVALUE = 140; + INT_EXCEL_SHEET_FUNC_TIMEVALUE = 141; + INT_EXCEL_SHEET_FUNC_COUNTA = 169; + INT_EXCEL_SHEET_FUNC_PRODUCT = 183; + INT_EXCEL_SHEET_FUNC_ISNONTEXT = 190; + INT_EXCEL_SHEET_FUNC_STDEVP = 193; + INT_EXCEL_SHEET_FUNC_VARP = 194; + INT_EXCEL_SHEET_FUNC_ISLOGICAL = 198; + INT_EXCEL_SHEET_FUNC_TODAY = 221; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_MEDIAN = 227; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_SINH = 229; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_COSH = 230; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_TANH = 231; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_ASINH = 232; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_ACOSH = 233; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_ATANH = 234; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_INFO = 244; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_AVEDEV = 269; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_BETADIST = 270; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_BETAINV = 272; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_BINOMDIST = 273; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_CHIDIST = 274; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_CHIINV = 275; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_PERMUT = 299; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_POISSON = 300; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_SUMSQ = 321; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_RADIANS = 342; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_DEGREES = 343; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_SUMIF = 345; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_COUNTIF = 346; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_COUNTBLANK = 347; // not available in BIFF2 + INT_EXCEL_SHEET_FUNC_DATEDIF = 351; // not available in BIFF2 { Control Tokens, Special Tokens } // 01H tExp Matrix formula or shared formula @@ -200,7 +303,7 @@ type function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; procedure GetLastColCallback(ACell: PCell; AStream: TStream); function GetLastColIndex(AWorksheet: TsWorksheet): Word; - function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Byte; + function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word; // Other records which didn't change // Workbook Globals records // Write out used codepage for character encoding @@ -408,45 +511,174 @@ begin end; function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( - AElementKind: TFEKind; out ASecondaryID: Word): Byte; -begin - ASecondaryID := 0; + 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} - case AElementKind of - { Operand Tokens } - fekCell: Result := INT_EXCEL_TOKEN_TREFR; - fekCellRange: Result := INT_EXCEL_TOKEN_TAREA_R; - fekNum: Result := INT_EXCEL_TOKEN_TNUM; - { Basic operations } - fekAdd: Result := INT_EXCEL_TOKEN_TADD; - fekSub: Result := INT_EXCEL_TOKEN_TSUB; - fekDiv: Result := INT_EXCEL_TOKEN_TDIV; - fekMul: Result := INT_EXCEL_TOKEN_TMUL; - { Built-in Functions} - fekABS: - begin - Result := INT_EXCEL_TOKEN_FUNC_V; - ASecondaryID := INT_EXCEL_SHEET_FUNC_ABS; - end; - fekDATE: - begin - Result := INT_EXCEL_TOKEN_FUNC_V; - ASecondaryID := INT_EXCEL_SHEET_FUNC_DATE; - end; - fekROUND: - begin - Result := INT_EXCEL_TOKEN_FUNC_V; - ASecondaryID := INT_EXCEL_SHEET_FUNC_ROUND; - end; - fekTIME: - begin - Result := INT_EXCEL_TOKEN_FUNC_V; - ASecondaryID := INT_EXCEL_SHEET_FUNC_TIME; - end; - { Other operations } - fekOpSUM: Result := INT_EXCEL_TOKEN_TATTR; - else - Result := 0; + // 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 + Result := TokenIDs[AElementKind, 1]; + ASecondaryID := 0; + end; + 1: begin + Result := INT_EXCEL_TOKEN_FUNC_V; + ASecondaryID := TokenIDs[AElementKind, 1] + end; + 2: begin + Result := INT_EXCEL_TOKEN_FUNCVAR_V; + ASecondaryID := TokenIDs[AElementKind, 1] + end; end; end;