diff --git a/components/fpspreadsheet/examples/db_import_export/db_export_import.lpi b/components/fpspreadsheet/examples/db_import_export/db_export_import.lpi index a00db4c28..f364ff07f 100644 --- a/components/fpspreadsheet/examples/db_import_export/db_export_import.lpi +++ b/components/fpspreadsheet/examples/db_import_export/db_export_import.lpi @@ -27,10 +27,13 @@ - + - + + + + @@ -43,6 +46,7 @@ + diff --git a/components/fpspreadsheet/examples/excel2demo/excel2read.lpi b/components/fpspreadsheet/examples/excel2demo/excel2read.lpi index 044140276..7e6fa76f2 100644 --- a/components/fpspreadsheet/examples/excel2demo/excel2read.lpi +++ b/components/fpspreadsheet/examples/excel2demo/excel2read.lpi @@ -32,7 +32,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel2demo/excel2read.lpr b/components/fpspreadsheet/examples/excel2demo/excel2read.lpr index 9bfa7b7ac..f3be0df21 100644 --- a/components/fpspreadsheet/examples/excel2demo/excel2read.lpr +++ b/components/fpspreadsheet/examples/excel2demo/excel2read.lpr @@ -32,6 +32,8 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; + + MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas, boAutoCalc]; MyWorkbook.ReadFromFile(InputFilename, sfExcel2); MyWorksheet := MyWorkbook.GetFirstWorksheet; @@ -44,9 +46,12 @@ begin CurCell := MyWorkSheet.GetFirstCell(); for i := 0 to MyWorksheet.GetCellCount - 1 do begin - WriteLn('Row: ', CurCell^.Row, ' Col: ', CurCell^.Col, ' Value: ', - UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col)) - ); + Write('Row: ', CurCell^.Row, ' Col: ', CurCell^.Col, ' Value: ', + UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col)) + ); + if HasFormula(CurCell) then + Write(' (Formula ', CurCell^.FormulaValue, ')'); + WriteLn; CurCell := MyWorkSheet.GetNextCell(); end; diff --git a/components/fpspreadsheet/examples/excel2demo/excel2write.lpi b/components/fpspreadsheet/examples/excel2demo/excel2write.lpi index ec66770e0..85d2183eb 100644 --- a/components/fpspreadsheet/examples/excel2demo/excel2write.lpi +++ b/components/fpspreadsheet/examples/excel2demo/excel2write.lpi @@ -32,7 +32,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel2demo/excel2write.lpr b/components/fpspreadsheet/examples/excel2demo/excel2write.lpr index d30c9df58..89fbc88fe 100644 --- a/components/fpspreadsheet/examples/excel2demo/excel2write.lpr +++ b/components/fpspreadsheet/examples/excel2demo/excel2write.lpr @@ -51,30 +51,31 @@ begin MyWorksheet.WriteNumber(0, 2, 3.0); MyWorksheet.WriteNumber(0, 3, 4.0); - // Write the formula E1 = ABS(A1) + // Write the formula E1 = ABS(A1) as rpn token array SetLength(MyRPNFormula, 2); MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].Col := 0; MyRPNFormula[0].Row := 0; - MyRPNFormula[1].ElementKind := fekABS; + MyRPNFormula[1].ElementKind := fekFUNC; + MyRPNFormula[1].FuncName := 'ABS'; MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula); - // Write the formula F1 = ROUND(A1, 0) + // Write the formula F1 = ROUND(A1, 0) as rpn token array SetLength(MyRPNFormula, 3); MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].Col := 0; MyRPNFormula[0].Row := 0; MyRPNFormula[1].ElementKind := fekNum; MyRPNFormula[1].DoubleValue := 0.0; - MyRPNFormula[2].ElementKind := fekROUND; + MyRPNFormula[2].ElementKind := fekFUNC; + MyRPNFormula[2].FuncName := 'ROUND'; MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); // Write a string formula to G1 = "A" & "B" - MyWorksheet.WriteRPNFormula(0, 6, CreateRPNFormula( - RPNString('A', - RPNSTring('B', - RPNFunc(fekConcat, - nil))))); + MyWorksheet.WriteFormula(0, 6, '="A"&"B"'); + + // Write string formula to H1 = sin(A1+B1) + MyWorksheet.WriteFormula(0, 7, '=SIN(A1+B1)'); // Write some string cells MyWorksheet.WriteUTF8Text(1, 0, 'First'); diff --git a/components/fpspreadsheet/examples/excel5demo/excel5read.lpr b/components/fpspreadsheet/examples/excel5demo/excel5read.lpr index 4c8880725..43103b1fb 100644 --- a/components/fpspreadsheet/examples/excel5demo/excel5read.lpr +++ b/components/fpspreadsheet/examples/excel5demo/excel5read.lpr @@ -31,6 +31,7 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; + MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas]; MyWorkbook.ReadFromFile(InputFilename, sfExcel5); MyWorksheet := MyWorkbook.GetFirstWorksheet; @@ -43,11 +44,12 @@ begin CurCell := MyWorkSheet.GetFirstCell(); for i := 0 to MyWorksheet.GetCellCount - 1 do begin - WriteLn('Row: ', CurCell^.Row, + Write('Row: ', CurCell^.Row, ' Col: ', CurCell^.Col, ' Value: ', - UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, - CurCell^.Col)) - ); + UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col))); + if HasFormula(CurCell) then + Write(' - Formula: ', CurCell^.FormulaValue); + WriteLn; CurCell := MyWorkSheet.GetNextCell(); end; diff --git a/components/fpspreadsheet/examples/excel5demo/excel5write.lpi b/components/fpspreadsheet/examples/excel5demo/excel5write.lpi index f7ff59409..a98d85c43 100644 --- a/components/fpspreadsheet/examples/excel5demo/excel5write.lpi +++ b/components/fpspreadsheet/examples/excel5demo/excel5write.lpi @@ -32,7 +32,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel5demo/excel5write.lpr b/components/fpspreadsheet/examples/excel5demo/excel5write.lpr index 4dc09f2e6..58179cfb3 100644 --- a/components/fpspreadsheet/examples/excel5demo/excel5write.lpr +++ b/components/fpspreadsheet/examples/excel5demo/excel5write.lpr @@ -35,7 +35,9 @@ begin MyWorkbook := TsWorkbook.Create; MyWorksheet := MyWorkbook.AddWorksheet(UTF8ToAnsi(Str_Worksheet1)); + MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving]; MyWorksheet.Options := MyWorksheet.Options + [soHasFrozenPanes]; + MyWorksheet.LeftPaneWidth := 1; MyWorksheet.TopPaneHeight := 2; @@ -139,7 +141,7 @@ begin end; } - // Write the formula E1 = A1 + B1 + // Write the formula E1 = A1 + B1 as rpn roken array SetLength(MyRPNFormula, 3); MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].Col := 0; @@ -150,15 +152,22 @@ begin MyRPNFormula[2].ElementKind := fekAdd; MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula); - // Write the formula F1 = ABS(A1) + // Write the formula F1 = ABS(A1) as rpn token array SetLength(MyRPNFormula, 2); MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].Col := 0; MyRPNFormula[0].Row := 0; - MyRPNFormula[1].ElementKind := fekABS; + MyRPNFormula[1].ElementKind := fekFunc; + MyRPNFormula[1].FuncName := 'ABS'; MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); - r:= 10; + // Write formula G1 = "A"&"B" as string formula + MyWorksheet.WriteFormula(0, 6, '="A"&"B"'); + + // Write formula H1 = sin(A1+B1) as string formula + Myworksheet.WriteFormula(0, 7, '=SIN(A1+B1)'); + + r := 10; // Write current date/time to cells B11:B16 MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDate'); MyWorksheet.WriteDateTime(r, 1, now, nfShortDate); diff --git a/components/fpspreadsheet/examples/excel8demo/excel8read.lpi b/components/fpspreadsheet/examples/excel8demo/excel8read.lpi index 9525d913e..3dd5974f3 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8read.lpi +++ b/components/fpspreadsheet/examples/excel8demo/excel8read.lpi @@ -31,7 +31,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel8demo/excel8read.lpr b/components/fpspreadsheet/examples/excel8demo/excel8read.lpr index a2065c1dd..41ec0f596 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8read.lpr +++ b/components/fpspreadsheet/examples/excel8demo/excel8read.lpr @@ -36,7 +36,7 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; - MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas, boAutoCalc]; + MyWorkbook.ReadFormulas := true; MyWorkbook.ReadFromFile(InputFilename, sfExcel8); @@ -55,8 +55,8 @@ begin UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col)) ); - if Length(CurCell^.RPNFormulaValue) > 0 then - WriteLn(' Formula: ', MyWorkSheet.ReadRPNFormulaAsString(CurCell)) + if HasFormula(CurCell) then + WriteLn(' Formula: ', MyWorkSheet.ReadFormulaAsString(CurCell)) else WriteLn; CurCell := MyWorkSheet.GetNextCell(); diff --git a/components/fpspreadsheet/examples/excel8demo/excel8write.lpi b/components/fpspreadsheet/examples/excel8demo/excel8write.lpi index b7f0924fa..8ebe4184e 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8write.lpi +++ b/components/fpspreadsheet/examples/excel8demo/excel8write.lpi @@ -15,64 +15,8 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -88,7 +32,7 @@ - + @@ -101,8 +45,12 @@ + + + - + + @@ -111,7 +59,7 @@ - + diff --git a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr index e0836b025..9e9daf9db 100644 --- a/components/fpspreadsheet/examples/excel8demo/excel8write.lpr +++ b/components/fpspreadsheet/examples/excel8demo/excel8write.lpr @@ -10,8 +10,7 @@ program excel8write; {$mode delphi}{$H+} uses - Classes, SysUtils, fpspreadsheet, xlsbiff8, - laz_fpspreadsheet; + Classes, SysUtils, fpspreadsheet, xlsbiff8; const Str_First = 'First'; @@ -41,6 +40,7 @@ begin MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); MyWorkbook.FormatSettings.CurrencyFormat := 2; MyWorkbook.FormatSettings.NegCurrFormat := 14; + MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving]; MyWorksheet := MyWorkbook.AddWorksheet(Str_Worksheet1); MyWorksheet.Options := MyWorksheet.Options - [soShowGridLines]; @@ -147,28 +147,27 @@ begin end; } - // Write the formula E1 = A1 + B1 - SetLength(MyRPNFormula, 3); - MyRPNFormula[0].ElementKind := fekCell; - MyRPNFormula[0].Col := 0; - MyRPNFormula[0].Row := 0; - MyRPNFormula[1].ElementKind := fekCell; - MyRPNFormula[1].Col := 1; - MyRPNFormula[1].Row := 0; - MyRPNFormula[2].ElementKind := fekAdd; - MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula); - MyWorksheet.WriteFont(0, 4, 'Arial', 10, [fssUnderline], scBlack); + // Write the string formula E1 = A1 + B1 ... + MyWorksheet.WriteFormula(0, 4, 'A1+B1'); + // ... and the rpn formula E2 = A1 + B1 + MyWorksheet.WriteRPNFormula(1, 4, CreateRPNFormula( + RPNCellValue('A1', + RPNCellValue('B1', + RPNFunc(fekAdd, + nil))))); - // Write the formula F1 = ABS(A1) - SetLength(MyRPNFormula, 2); - MyRPNFormula[0].ElementKind := fekCell; - MyRPNFormula[0].Col := 0; - MyRPNFormula[0].Row := 0; - MyRPNFormula[1].ElementKind := fekABS; - MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); + // Write the formula F1 = ABS(A1) as string formula ... + MyWorksheet.WriteFormula(0, 5, 'ABS(A1)'); + // ... and F2 = ABS(A1) as rpn formula + MyWorksheet.WriteRPNFormula(1, 5, CreateRPNFormula( + RPNCellValue('A1', + RPNFunc('ABS', + nil)))); - // Write a string formula to G1 = "A" & "B" - MyWorksheet.WriteRPNFormula(0, 6, CreateRPNFormula( + // Write a string formula to G1 = "A" & "B" ... + MyWorksheet.WriteFormula(0, 6, '"A"&"B"'); + // ... and again as rpn formula + MyWorksheet.WriteRPNFormula(1, 6, CreateRPNFormula( RPNString('A', RPNSTring('B', RPNFunc(fekConcat, diff --git a/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpi b/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpi index 22e3446e0..d970e3ad0 100644 --- a/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpi +++ b/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpi @@ -56,7 +56,6 @@ - diff --git a/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpr b/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpr index 0fd7e448a..ee79836f5 100644 --- a/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpr +++ b/components/fpspreadsheet/examples/ooxmldemo/ooxmlread.lpr @@ -35,6 +35,7 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; + MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas]; MyWorkbook.ReadFromFile(InputFilename, sfOOXML); MyWorksheet := MyWorkbook.GetFirstWorksheet; diff --git a/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpi b/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpi index eff9a9a72..676406870 100644 --- a/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpi +++ b/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpi @@ -57,10 +57,5 @@ - - - - - diff --git a/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpr b/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpr index dec327388..b11a7c3b1 100644 --- a/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpr +++ b/components/fpspreadsheet/examples/ooxmldemo/ooxmlwrite.lpr @@ -40,6 +40,11 @@ begin MyWorksheet.WriteColWidth(0, 20); MyWorksheet.WriteRowHeight(0, 4); + // Write some formulas + Myworksheet.WriteFormula(0, 5, '=A1-B1'); + Myworksheet.WriteFormula(0, 6, '=SUM(A1:D1)'); + MyWorksheet.WriteFormula(0, 7, '=SIN(A1+B1)'); + // Uncomment this to test large XLS files for i := 2 to 2{20} do begin diff --git a/components/fpspreadsheet/examples/opendocdemo/opendocread.lpi b/components/fpspreadsheet/examples/opendocdemo/opendocread.lpi index d161ba808..2b383bdaf 100644 --- a/components/fpspreadsheet/examples/opendocdemo/opendocread.lpi +++ b/components/fpspreadsheet/examples/opendocdemo/opendocread.lpi @@ -32,7 +32,7 @@ - + @@ -51,16 +51,12 @@ + - - - - - diff --git a/components/fpspreadsheet/examples/opendocdemo/opendocread.lpr b/components/fpspreadsheet/examples/opendocdemo/opendocread.lpr index 89b673173..eb4af43fd 100644 --- a/components/fpspreadsheet/examples/opendocdemo/opendocread.lpr +++ b/components/fpspreadsheet/examples/opendocdemo/opendocread.lpr @@ -11,7 +11,8 @@ program opendocread; {$mode delphi}{$H+} uses - Classes, SysUtils, fpspreadsheet, fpsallformats; + Classes, SysUtils, fpspreadsheet, fpsallformats, + laz_fpspreadsheet; var MyWorkbook: TsWorkbook; diff --git a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpi b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpi index ce2de4812..49ad37280 100644 --- a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpi +++ b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpi @@ -32,7 +32,7 @@ - + @@ -45,22 +45,14 @@ - - - - - + + - - - - - diff --git a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr index 1fa50146e..cd5b83481 100644 --- a/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr +++ b/components/fpspreadsheet/examples/opendocdemo/opendocwrite.lpr @@ -10,7 +10,8 @@ program opendocwrite; {$mode delphi}{$H+} uses - Classes, SysUtils, fpspreadsheet, fpsallformats; + Classes, SysUtils, fpspreadsheet, fpsallformats, + laz_fpspreadsheet; var MyWorkbook: TsWorkbook; diff --git a/components/fpspreadsheet/examples/other/demo_expression_parser.pas b/components/fpspreadsheet/examples/other/demo_expression_parser.pas index 9730fe7fd..2f0c64d60 100644 --- a/components/fpspreadsheet/examples/other/demo_expression_parser.pas +++ b/components/fpspreadsheet/examples/other/demo_expression_parser.pas @@ -10,14 +10,6 @@ uses { you can add units after this }, TypInfo, fpSpreadsheet, fpsUtils, fpsExprParser; -function Prepare(AFormula: String): String; -begin - if (AFormula <> '') and (AFormula[1] = '=') then - Result := Copy(AFormula, 2, Length(AFormula)-1) - else - Result := AFormula; -end; - var workbook: TsWorkbook; worksheet: TsWorksheet; @@ -26,55 +18,89 @@ var res: TsExpressionResult; formula: TsRPNFormula; i: Integer; + s: String; begin workbook := TsWorkbook.Create; try worksheet := workbook.AddWorksheet('Test'); - { - worksheet.WriteNumber(0, 0, 2); // A1 + + worksheet.WriteNumber(0, 0, 1); // A1 worksheet.WriteNumber(0, 1, 2.5); // B1 - } + + { worksheet.WriteUTF8Text(0, 0, 'Hallo'); // A1 worksheet.WriteUTF8Text(0, 1, 'World'); // B1 + } + //cell := worksheet.WriteFormula(1, 0, '=4+5'); // A2 + //cell := worksheet.WriteFormula(1, 0, 'AND(TRUE(), TRUE(), TRUE())'); + //cell := worksheet.WriteFormula(1, 0, 'SIN(A1+B1)'); + //cell := worksheet.WriteFormula(1, 0, '=TRUE()'); + //cell := worksheet.WriteFormula(1, 0, '=1-(4/2)^2*2-1'); // A2 + //cell := Worksheet.WriteFormula(1, 0, 'datedif(today(),Date(2014,1,1),"D")'); + //cell := Worksheet.WriteFormula(1, 0, 'Day(Date(2014, 1, 12))'); + //cell := Worksheet.WriteFormula(1, 0, 'SUM(1,2,3)'); + //cell := Worksheet.WriteFormula(1, 0, 'CELL("address",A1)'); + cell := Worksheet.WriteFormula(1, 0, 'ISBLANK(A1)'); - //cell := worksheet.WriteFormula(1, 0, '=(A1+2)*3'); // A2 - cell := worksheet.WriteFormula(1, 0, 'A1&" "&B1'); + WriteLn('A1: ', worksheet.ReadAsUTF8Text(0, 0)); + WriteLn('B1: ', worksheet.ReadAsUTF8Text(0, 1)); - WriteLn('A1 = ', worksheet.ReadAsUTF8Text(0, 0)); - WriteLn('B1 = ', worksheet.ReadAsUTF8Text(0, 1)); - - parser := TsExpressionParser.Create(worksheet); + parser := TsSpreadsheetParser.Create(worksheet); try - parser.Builtins := [bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, bcData, - bcVaria, bcUser]; - parser.Expression := Prepare(cell^.FormulaValue.FormulaStr); - res := parser.Evaluate; + try + parser.Expression := cell^.FormulaValue; + res := parser.Evaluate; - Write('A2 = ', Prepare(cell^.FormulaValue.FormulaStr), ' = '); - case res.ResultType of - rtBoolean : WriteLn(BoolToStr(res.ResBoolean)); - rtFloat : WriteLn(FloatToStr(res.ResFloat)); - rtInteger : WriteLn(IntToStr(res.ResInteger)); - rtDateTime : WriteLn(FormatDateTime('c', res.ResDateTime)); - rtString : WriteLn(res.ResString); - end; - - WriteLn('Reconstructed string formula: ', parser.BuildFormula); - - WriteLn('RPN formula:'); - formula := parser.BuildRPNFormula; - for i:=0 to Length(formula)-1 do begin - Write(' Item ', i, ': token ', GetEnumName(TypeInfo(TFEKind), ord(formula[i].ElementKind))); - case formula[i].ElementKind of - fekCell : Write(' / cell: ' +GetCellString(formula[i].Row, formula[i].Col, formula[i].RelFlags)); - fekNum : Write(' / number value: ', FloatToStr(formula[i].DoubleValue)); - fekString : Write(' / string value: "', formula[i].StringValue, '"'); - fekBool : Write(' / boolean value: ', BoolToStr(formula[i].DoubleValue <> 0)); + WriteLn('A2: ', parser.Expression); + Write('Result: '); + case res.ResultType of + rtEmpty : WriteLn('--- empty ---'); + rtBoolean : WriteLn(BoolToStr(res.ResBoolean, true)); + rtFloat : WriteLn(FloatToStr(res.ResFloat)); + rtInteger : WriteLn(IntToStr(res.ResInteger)); + rtDateTime : WriteLn(FormatDateTime('c', res.ResDateTime)); + rtString : WriteLn(res.ResString); + rtError : WriteLn(GetErrorValueStr(res.ResError)); end; - WriteLn; + + WriteLn('Reconstructed string formula: ', parser.BuildStringFormula); + formula := parser.RPNFormula; + + for i:=0 to Length(formula)-1 do begin + Write(' Item ', i, ': token ', GetEnumName(TypeInfo(TFEKind), ord(formula[i].ElementKind)), ' ', formula[i].FuncName); + case formula[i].ElementKind of + fekCell : Write(' / cell: ' +GetCellString(formula[i].Row, formula[i].Col, formula[i].RelFlags)); + fekNum : Write(' / float value: ', FloatToStr(formula[i].DoubleValue)); + fekInteger : Write(' / integer value: ', IntToStr(formula[i].IntValue)); + fekString : Write(' / string value: "', formula[i].StringValue, '"'); + fekBool : Write(' / boolean value: ', BoolToStr(formula[i].DoubleValue <> 0, true)); + end; + WriteLn; + end; + finally + parser.Free; end; + except on E:Exception do + begin + WriteLn('Parser/calculation error: ', E.Message); + raise; + end; + end; + + parser := TsSpreadsheetParser.Create(worksheet); + try + try + parser.RPNFormula := formula; + s := parser.BuildStringFormula; + WriteLn('String formula, reconstructed from RPN formula: ', s); + except on E:Exception do + begin + WriteLn('RPN/string formula conversion error: ', E.Message); + raise; + end; + end; finally parser.Free; end; @@ -82,5 +108,6 @@ begin finally workbook.Free; end; + end. diff --git a/components/fpspreadsheet/examples/other/demo_formula_func.lpi b/components/fpspreadsheet/examples/other/demo_formula_func.lpi index 87ec5202c..613b1a53d 100644 --- a/components/fpspreadsheet/examples/other/demo_formula_func.lpi +++ b/components/fpspreadsheet/examples/other/demo_formula_func.lpi @@ -46,6 +46,7 @@ + diff --git a/components/fpspreadsheet/examples/other/demo_formula_func.pas b/components/fpspreadsheet/examples/other/demo_formula_func.pas index de09e0dad..156be53a2 100644 --- a/components/fpspreadsheet/examples/other/demo_formula_func.pas +++ b/components/fpspreadsheet/examples/other/demo_formula_func.pas @@ -8,7 +8,7 @@ - PMT() (payment) - NPER() (number of payment periods) - The demo writes an xls file which uses these formulas and then displays + The demo writes a spreadsheet file which uses these formulas and then displays the result in a console window. (Open the generated file in Excel or Open/LibreOffice and compare). } @@ -24,117 +24,118 @@ uses {$ENDIF} {$ENDIF} Classes, SysUtils, - math, fpspreadsheet, xlsbiff8, fpsfunc, financemath; + math, fpspreadsheet, fpsallformats, fpsexprparser, financemath; +{ Base data used in this demonstration } +const + INTEREST_RATE = 0.03; // interest rate per period + NUMBER_PAYMENTS = 10; // number of payment periods + REG_PAYMENT = 1000; // regular payment per period + PRESENT_VALUE = 10000.0; // present value of investment + PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made {------------------------------------------------------------------------------} { Adaption of financial functions to usage by fpspreadsheet } { The functions are implemented in the unit "financemath.pas". } {------------------------------------------------------------------------------} -function fpsFV(Args: TsArgumentStack; NumArgs: Integer): TsArgument; -var - data: TsArgNumberArray; +procedure fpsFV(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - // Pop the argument from the stack. This can be done by means of PopNumberValues - // which brings the values back in the right order and reports an error - // in case of non-numerical values. - if Args.PopNumberValues(NumArgs, false, data, Result) then - // Call the FutureValue function with the NumberValues of the arguments. - Result := CreateNumberArg(FutureValue( - data[0], // interest rate - round(data[1]), // number of payments - data[2], // payment - data[3], // present value - TPaymentTime(round(data[4])) // payment type - )); + Result.ResFloat := FutureValue( + ArgToFloat(Args[0]), // interest rate + ArgToInt(Args[1]), // number of payments + ArgToFloat(Args[2]), // payment + ArgToFloat(Args[3]), // present value + TPaymentTime(ArgToInt(Args[4])) // payment type + ); end; -function fpsPMT(Args: TsArgumentStack; NumArgs: Integer): TsArgument; -var - data: TsArgNumberArray; +procedure fpsPMT(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - if Args.PopNumberValues(NumArgs, false, data, Result) then - Result := CreateNumberArg(Payment( - data[0], // interest rate - round(data[1]), // number of payments - data[2], // present value - data[3], // future value - TPaymentTime(round(data[4])) // payment type - )); + Result.ResFloat := Payment( + ArgToFloat(Args[0]), // interest rate + ArgToInt(Args[1]), // number of payments + ArgToFloat(Args[2]), // present value + ArgToFloat(Args[3]), // future value + TPaymentTime(ArgToInt(Args[4])) // payment type + ); end; -function fpsPV(Args: TsArgumentStack; NumArgs: Integer): TsArgument; -// Present value -var - data: TsArgNumberArray; +procedure fpsPV(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - if Args.PopNumberValues(NumArgs, false, data, Result) then - Result := CreateNumberArg(PresentValue( - data[0], // interest rate - round(data[1]), // number of payments - data[2], // payment - data[3], // future value - TPaymentTime(round(data[4])) // payment type - )); + Result.ResFloat := PresentValue( + ArgToFloat(Args[0]), // interest rate + ArgToInt(Args[1]), // number of payments + ArgToFloat(Args[2]), // payment + ArgToFloat(Args[3]), // future value + TPaymentTime(ArgToInt(Args[4])) // payment type + ); end; -function fpsNPER(Args: TsArgumentStack; NumArgs: Integer): TsArgument; -var - data: TsArgNumberArray; +procedure fpsNPER(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - if Args.PopNumberValues(NumArgs, false, data, Result) then - Result := CreateNumberArg(NumberOfPeriods( - data[0], // interest rate - data[1], // payment - data[2], // present value - data[3], // future value - TPaymentTime(round(data[4])) // payment type - )); + Result.ResFloat := NumberOfPeriods( + ArgToFloat(Args[0]), // interest rate + ArgToFloat(Args[1]), // payment + ArgToFloat(Args[2]), // present value + ArgToFloat(Args[3]), // future value + TPaymentTime(ArgToInt(Args[4])) // payment type + ); end; -function fpsRATE(Args: TsArgumentStack; NumArgs: Integer): TsArgument; -var - data: TsArgNumberArray; +procedure fpsRATE(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - if Args.PopNumberValues(NumArgs, false, data, Result) then - Result := CreateNumberArg(InterestRate( - round(data[0]), // number of payment periods - data[1], // payment - data[2], // present value - data[3], // future value - TPaymentTime(round(data[4])) // payment type - )); + Result.ResFloat := InterestRate( + ArgToInt(Args[0]), // number of payments + ArgToFloat(Args[1]), // payment + ArgToFloat(Args[2]), // present value + ArgToFloat(Args[3]), // future value + TPaymentTime(ArgToInt(Args[4])) // payment type + ); end; + {------------------------------------------------------------------------------} { Write xls file comparing our own calculations with Excel result } {------------------------------------------------------------------------------} procedure WriteFile(AFileName: String); const - INTEREST_RATE = 0.03; // interest rate per period - NUMBER_PAYMENTS = 10; // number of payment periods - REG_PAYMENT = 1000; // regular payment per period - PRESENT_VALUE = 10000; // present value of investment - PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made + 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; var workbook: TsWorkbook; worksheet: TsWorksheet; fval, pval, pmtval, nperval, rateval: Double; + formula: String; + fs: TFormatSettings; + begin { We have to register our financial functions in fpspreadsheet. Otherwise an error code would be displayed in the reading part of this demo for these - formula cells. } - RegisterFormulaFunc(fekFV, @fpsFV); - RegisterFormulaFunc(fekPMT, @fpsPMT); - RegisterFormulaFunc(fekPV, @fpsPV); - RegisterFormulaFunc(fekNPER, @fpsNPER); - RegisterFormulaFunc(fekRATE, @fpsRATE); + formula cells. + The 1st parameter is the data type of the function result ('F'=float) + The 2nd parameter shows the data types of the arguments ('F=float, 'I'=integer) + The 3rd parameter is the Excel ID needed when writing to xls files. (see + "OpenOffice Documentation of Microsoft Excel File Format", section 3.11) + The 4th parameter is the address of the function to be used for calculation. } + + RegisterFunction('FV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_FV, @fpsFV); + RegisterFunction('PMT', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PMT, @fpsPMT); + RegisterFunction('PV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PV, @fpsPV); + RegisterFunction('NPER', 'F', 'FFFFI', INT_EXCEL_SHEET_FUNC_NPER, @fpsNPER); + RegisterFunction('RATE', 'F', 'IFFFI', INT_EXCEL_SHEET_FUNC_RATE, @fpsRATE); + + // The formula parser requires a point as decimals separator. + fs := DefaultFormatSettings; + fs.DecimalSeparator := '.'; workbook := TsWorkbook.Create; try - workbook.Options := workbook.Options + [boCalcBeforeSaving]; + //workbook.Options := workbook.Options + [boCalcBeforeSaving]; worksheet := workbook.AddWorksheet('Financial'); worksheet.WriteColWidth(0, 40); @@ -167,24 +168,14 @@ begin worksheet.WriteUTF8Text(9, 0, 'Worksheet calculation using constants'); worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(9, 1, CreateRPNFormula( - RPNNumber(INTEREST_RATE, - RPNNumber(NUMBER_PAYMENTS, - RPNNumber(REG_PAYMENT, - RPNNumber(PRESENT_VALUE, - RPNNumber(ord(PAYMENT_WHEN), - RPNFunc(fekFV, 5, - nil)))))))); + worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$'); + formula := Format('FV(%f,%d,%f,%f,%d)', + [1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, ord(PAYMENT_WHEN)], fs + ); + worksheet.WriteFormula(9, 1, formula); worksheet.WriteUTF8Text(10, 0, 'Worksheet calculation using cell values'); worksheet.WriteNumberFormat(10, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(10, 1, CreateRPNFormula( - RPNCellValue('B2', // interest rate - RPNCellValue('B3', // number of periods - RPNCellValue('B4', // payment - RPNCellValue('B5', // present value - RPNCellValue('B6', // payment at end or at start - RPNFunc(fekFV, 5, // Call Excel's FV formula - nil)))))))); + worksheet.WriteFormula(10, 1, 'FV(B2,B3,B4,B5,B6)'); // present value calculation pval := PresentValue(INTEREST_RATE, NUMBER_PAYMENTS, REG_PAYMENT, fval, PAYMENT_WHEN); @@ -194,25 +185,14 @@ begin worksheet.WriteCurrency(13, 1, pval, nfCurrency, 2, '$'); worksheet.WriteUTF8Text(14, 0, 'Worksheet calculation using constants'); + formula := Format('PV(%f,%d,%f,%f,%d)', + [1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, fval, ord(PAYMENT_WHEN)], fs + ); worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(14, 1, CreateRPNFormula( - RPNNumber(INTEREST_RATE, - RPNNumber(NUMBER_PAYMENTS, - RPNNumber(REG_PAYMENT, - RPNNumber(fval, - RPNNumber(ord(PAYMENT_WHEN), - RPNFunc(fekPV, 5, - nil)))))))); + worksheet.WriteFormula(14, 1, formula); Worksheet.WriteUTF8Text(15, 0, 'Worksheet calculation using cell values'); worksheet.WriteNumberFormat(15, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(15, 1, CreateRPNFormula( - RPNCellValue('B2', // interest rate - RPNCellValue('B3', // number of periods - RPNCellValue('B4', // payment - RPNCellValue('B11', // future value - RPNCellValue('B6', // payment at end or at start - RPNFunc(fekPV, 5, // Call Excel's PV formula - nil)))))))); + worksheet.WriteFormula(15, 1, 'PV(B2,B3,B4,B11,B6)'); // payments calculation pmtval := Payment(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN); @@ -223,24 +203,13 @@ begin worksheet.WriteUTF8Text(19, 0, 'Worksheet calculation using constants'); worksheet.WriteNumberFormat(19, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(19, 1, CreateRPNFormula( - RPNNumber(INTEREST_RATE, - RPNNumber(NUMBER_PAYMENTS, - RPNNumber(PRESENT_VALUE, - RPNNumber(fval, - RPNNumber(ord(PAYMENT_WHEN), - RPNFunc(fekPMT, 5, - nil)))))))); + formula := Format('PMT(%g,%d,%g,%g,%d)', + [INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs + ); + worksheet.WriteFormula(19, 1, formula); Worksheet.WriteUTF8Text(20, 0, 'Worksheet calculation using cell values'); worksheet.WriteNumberFormat(20, 1, nfCurrency, 2, '$'); - worksheet.WriteRPNFormula(20, 1, CreateRPNFormula( - RPNCellValue('B2', // interest rate - RPNCellValue('B3', // number of periods - RPNCellValue('B5', // present value - RPNCellValue('B11', // future value - RPNCellValue('B6', // payment at end or at start - RPNFunc(fekPMT, 5, // Call Excel's PMT formula - nil)))))))); + worksheet.WriteFormula(20, 1, 'PMT(B2,B3,B5,B11,B6)'); // number of periods calculation nperval := NumberOfPeriods(INTEREST_RATE, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN); @@ -251,24 +220,13 @@ begin worksheet.WriteUTF8Text(24, 0, 'Worksheet calculation using constants'); worksheet.WriteNumberFormat(24, 1, nfFixed, 2); - worksheet.WriteRPNFormula(24, 1, CreateRPNFormula( - RPNNumber(INTEREST_RATE, - RPNNumber(REG_PAYMENT, - RPNNumber(PRESENT_VALUE, - RPNNumber(fval, - RPNNumber(ord(PAYMENT_WHEN), - RPNFunc(fekNPER, 5, - nil)))))))); + formula := Format('NPER(%g,%g,%g,%g,%d)', + [1.0*INTEREST_RATE, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs + ); + worksheet.WriteFormula(24, 1, formula); Worksheet.WriteUTF8Text(25, 0, 'Worksheet calculation using cell values'); worksheet.WriteNumberFormat(25, 1, nfFixed, 2); - worksheet.WriteRPNFormula(25, 1, CreateRPNFormula( - RPNCellValue('B2', // interest rate - RPNCellValue('B4', // payment - RPNCellValue('B5', // present value - RPNCellValue('B11', // future value - RPNCellValue('B6', // payment at end or at start - RPNFunc(fekNPER, 5, // Call Excel's PMT formula - nil)))))))); + worksheet.WriteFormula(25, 1, 'NPER(B2,B4,B5,B11,B6)'); // interest rate calculation rateval := InterestRate(NUMBER_PAYMENTS, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN); @@ -279,26 +237,15 @@ begin worksheet.WriteUTF8Text(29, 0, 'Worksheet calculation using constants'); worksheet.WriteNumberFormat(29, 1, nfPercentage, 2); - worksheet.WriteRPNFormula(29, 1, CreateRPNFormula( - RPNNumber(NUMBER_PAYMENTS, - RPNNumber(REG_PAYMENT, - RPNNumber(PRESENT_VALUE, - RPNNumber(fval, - RPNNumber(ord(PAYMENT_WHEN), - RPNFunc(fekRATE, 5, - nil)))))))); + formula := Format('RATE(%d,%g,%g,%g,%d)', + [NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs + ); + worksheet.WriteFormula(29, 1, formula); Worksheet.WriteUTF8Text(30, 0, 'Worksheet calculation using cell values'); worksheet.WriteNumberFormat(30, 1, nfPercentage, 2); - worksheet.WriteRPNFormula(30, 1, CreateRPNFormula( - RPNCellValue('B3', // number of payments - RPNCellValue('B4', // payment - RPNCellValue('B5', // present value - RPNCellValue('B11', // future value - RPNCellValue('B6', // payment at end or at start - RPNFunc(fekRATE, 5, // Call Excel's PMT formula - nil)))))))); + worksheet.WriteFormula(30, 1, 'RATE(B3,B4,B5,B11,B6)'); - workbook.WriteToFile(AFileName, sfExcel8, true); + workbook.WriteToFile(AFileName, true); finally workbook.Free; @@ -317,9 +264,8 @@ var begin workbook := TsWorkbook.Create; try - workbook.Options := workbook.Options + [boReadFormulas]; - workbook.ReadFromFile(AFilename, sfExcel8); - + workbook.Options := workbook.Options + [boReadFormulas, boAutoCalc]; + workbook.ReadFromFile(AFilename); worksheet := workbook.GetFirstWorksheet; // Write all cells with contents to the console @@ -340,19 +286,27 @@ begin WriteLn(s1+': ':50, s2); end; - WriteLn; - WriteLn('Press [ENTER] to close...'); - ReadLn; finally workbook.Free; end; end; const - TestFile='test_fv.xls'; + TestFile='test_user_formula.xlsx'; // Format depends on extension selected + // !!!! ods not working yet !!!! + begin + WriteLn('This demo registers user-defined functions for financial calculations'); + WriteLn('and writes and reads the corresponding spreadsheet file.'); + WriteLn; + WriteFile(TestFile); ReadFile(TestFile); + + WriteLn; + WriteLn('Open the file in Excel or OpenOffice/LibreOffice.'); + WriteLn('Press [ENTER] to close...'); + ReadLn; end. diff --git a/components/fpspreadsheet/examples/other/demo_recursive_calc.pas b/components/fpspreadsheet/examples/other/demo_recursive_calc.pas index 2ccad431e..f1035306f 100644 --- a/components/fpspreadsheet/examples/other/demo_recursive_calc.pas +++ b/components/fpspreadsheet/examples/other/demo_recursive_calc.pas @@ -21,7 +21,7 @@ var workbook: TsWorkbook; worksheet: TsWorksheet; const - OutputFile='test_calc.xls'; + OutputFile='test_recursive.xls'; begin writeln('Starting program.'); @@ -35,26 +35,33 @@ begin // A1 worksheet.WriteUTF8Text(0, 0, '=B2+1'); // B1 + worksheet.WriteFormula(0, 1, 'B2+1'); + { worksheet.WriteRPNFormula(0, 1, CreateRPNFormula( RPNCellValue('B2', - RPNNumber(1, + RPNInteger(1, RPNFunc(fekAdd, nil))))); - + } // A2 worksheet.WriteUTF8Text(1, 0, '=B3+1'); // B2 + worksheet.WriteFormula(1, 1, 'B3+1'); + { worksheet.WriteRPNFormula(1, 1, CreateRPNFormula( RPNCellValue('B3', - RPNNumber(1, + RPNInteger(1, RPNFunc(fekAdd, nil))))); - + } // A3 worksheet.WriteUTF8Text(2, 0, '(not dependent)'); // B3 worksheet.WriteNumber(2, 1, 1); workbook.WriteToFile(OutputFile, sfExcel8, true); - writeln('Finished. Please open "'+OutputFile+'" in your spreadsheet program.'); + writeln('Finished.'); + writeln; + writeln('Please open "'+OutputFile+'" in "fpsgrid".'); + writeLn('It should show calculation results in cells B1 and B2.'); finally workbook.Free; end; diff --git a/components/fpspreadsheet/examples/other/demo_virtualmode_write.lpr b/components/fpspreadsheet/examples/other/demo_virtualmode_write.lpr index 6b1f80a44..ca5e30d1e 100644 --- a/components/fpspreadsheet/examples/other/demo_virtualmode_write.lpr +++ b/components/fpspreadsheet/examples/other/demo_virtualmode_write.lpr @@ -97,9 +97,9 @@ begin { In case of a database, you would open the dataset before calling this: } t := Now; - workbook.WriteToFile('test_virtual.ods', sfOpenDocument, true); + //workbook.WriteToFile('test_virtual.ods', sfOpenDocument, true); //workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true); - //workbook.WriteToFile('test_virtual.xls', sfExcel8, true); + workbook.WriteToFile('test_virtual.xls', sfExcel8, true); //workbook.WriteToFile('test_virtual.xls', sfExcel5, true); //workbook.WriteToFile('test_virtual.xls', sfExcel2, true); t := Now - t; diff --git a/components/fpspreadsheet/examples/other/demo_write_formula.pas b/components/fpspreadsheet/examples/other/demo_write_formula.pas index 65b7dc3cf..38fa366bd 100644 --- a/components/fpspreadsheet/examples/other/demo_write_formula.pas +++ b/components/fpspreadsheet/examples/other/demo_write_formula.pas @@ -21,7 +21,7 @@ var procedure WriteFirstWorksheet(); var - MyFormula: TsFormula; + MyFormula: String; MyRPNFormula: TsRPNFormula; MyCell: PCell; begin @@ -37,40 +37,41 @@ begin Myworksheet.WriteNumber(3, 4, 300); // E4 MyWorksheet.WriteNumber(4, 4, 250); // E5 - // =Sum(E2:e5) - MyWorksheet.WriteUTF8Text(1, 0, '=Sum(E2:e5)'); // A2 - // - MyFormula.FormulaStr := '=Sum(E2:e5)'; - MyFormula.DoubleValue := 0.0; + // =Sum(E2:E5) + MyWorksheet.WriteUTF8Text(1, 0, '=Sum(E2:E5)'); // A2 + MyFormula := '=Sum(E2:E5)'; MyWorksheet.WriteFormula(1, 1, MyFormula); // B2 - // MyWorksheet.WriteRPNFormula(1, 2, CreateRPNFormula( // C2 RPNCellRange('E2:E5', - RPNFunc(fekSum, 1, nil)))); + RPNFunc('SUM', 1, + nil)))); // Write the formula =ABS(E1) MyWorksheet.WriteUTF8Text(2, 0, '=ABS(E1)'); // A3 - // + MyWorksheet.WriteFormula(2, 1, 'ABS(E1)'); // B3 MyWorksheet.WriteRPNFormula(2, 2, CreateRPNFormula( // C3 RPNCellValue('E1', - RPNFunc(fekAbs, nil)))); + RPNFunc('ABS', + nil)))); // Write the formula =4+5 MyWorksheet.WriteUTF8Text(3, 0, '=4+5'); // A4 - // + MyWorksheet.WriteFormula(3, 1, '=4+5'); // B4 MyWorksheet.WriteRPNFormula(3, 2, CreateRPNFormula( //C4 RPNNumber(4.0, RPNNumber(5.0, - RPNFunc(fekAdd, nil))))); - + RPNFunc(fekAdd, + nil))))); + (* // Write a shared formula "=E1+100" to the cell range F1:F5 // Please note that shared formulas are not written by sfOOXML and sfOpenDocument formats. MyCell := MyWorksheet.WriteRPNFormula(0, 5, CreateRPNFormula( RPNCellOffset(0, -1, [rfRelRow, rfRelCol], RPNNumber(100, - RPNFunc(fekAdd, nil))))); + RPNFunc(fekAdd, + nil))))); MyWorksheet.UseSharedFormula('F1:F5', MyCell); - + *) end; procedure WriteSecondWorksheet(); @@ -98,15 +99,17 @@ begin // Create the spreadsheet MyWorkbook := TsWorkbook.Create; + try + WriteFirstWorksheet(); + WriteSecondWorksheet(); - WriteFirstWorksheet(); + // Save the spreadsheet to a file + MyWorkbook.WriteToFile(MyDir + TestFile, sfExcel8, True); - WriteSecondWorksheet(); + finally + MyWorkbook.Free; + end; - // Save the spreadsheet to a file - MyWorkbook.WriteToFile(MyDir + TestFile, sfExcel8, True); -// MyWorkbook.WriteToFile(MyDir + 'test_formula.odt', sfOpenDocument, False); - MyWorkbook.Free; writeln('Finished. Please open "'+Testfile+'" in your spreadsheet program.'); end. diff --git a/components/fpspreadsheet/examples/spready/mainform.pas b/components/fpspreadsheet/examples/spready/mainform.pas index f874f04a0..b349be2f3 100644 --- a/components/fpspreadsheet/examples/spready/mainform.pas +++ b/components/fpspreadsheet/examples/spready/mainform.pas @@ -727,10 +727,15 @@ end; procedure TForm1.EdFormulaEditingDone(Sender: TObject); var r, c: Cardinal; + s: String; begin r := WorksheetGrid.GetWorksheetRow(WorksheetGrid.Row); c := WorksheetGrid.GetWorksheetCol(WorksheetGrid.Col); - WorksheetGrid.Worksheet.WriteCellValueAsString(r, c, EdFormula.Text); + s := EdFormula.Text; + if (s <> '') and (s[1] = '=') then + WorksheetGrid.Worksheet.WriteFormula(r, c, Copy(s, 2, Length(s))) + else + WorksheetGrid.Worksheet.WriteCellValueAsString(r, c, EdFormula.Text); end; procedure TForm1.EdFrozenColsChange(Sender: TObject); @@ -895,8 +900,10 @@ begin cell := WorksheetGrid.Worksheet.FindCell(r, c); if cell <> nil then begin s := WorksheetGrid.Worksheet.ReadFormulaAsString(cell); - if s <> '' then - EdFormula.Text := s + if s <> '' then begin + if s[1] <> '=' then s := '=' + s; + EdFormula.Text := s; + end else case cell^.ContentType of cctNumber: @@ -993,12 +1000,9 @@ begin then Strings.Add('ErrorValue=') else Strings.Add(Format('ErrorValue=%s', [ GetEnumName(TypeInfo(TsErrorValue), ord(ACell^.ErrorValue)) ])); - if (ACell=nil) or (Length(ACell^.RPNFormulaValue) = 0) - then Strings.Add('RPNFormulaValue=') - else Strings.Add(Format('RPNFormulaValue=(%d tokens)', [Length(ACell^.RPNFormulaValue)])); - if (ACell=nil) or (Length(ACell^.FormulaValue.FormulaStr)=0) - then Strings.Add('FormulaValue.FormulaStr=') - else Strings.Add(Format('FormulaValue.FormulaStr="%s"', [ACell^.FormulaValue.FormulaStr])); + if (ACell=nil) or (Length(ACell^.FormulaValue)=0) + then Strings.Add('FormulaValue=') + else Strings.Add(Format('FormulaValue="%s"', [ACell^.FormulaValue])); if (ACell=nil) or (ACell^.SharedFormulaBase=nil) then Strings.Add('SharedFormulaBase=') else Strings.Add(Format('SharedFormulaBase=%s', [GetCellString( diff --git a/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpi b/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpi index 7bf90ebc9..f129d363c 100644 --- a/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpi +++ b/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpi @@ -31,7 +31,7 @@ - + @@ -44,23 +44,13 @@ - - - - - - - - - - diff --git a/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpr b/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpr index 5a85fbad4..b05cd02d0 100644 --- a/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpr +++ b/components/fpspreadsheet/examples/wikitabledemo/wikitableread.lpr @@ -11,7 +11,7 @@ program wikitableread; uses Classes, SysUtils, fpspreadsheet, wikitable, - fpsutils; + laz_fpspreadsheet, fpsutils; var MyWorkbook: TsWorkbook; diff --git a/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpi b/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpi index a02f90e05..815f73380 100644 --- a/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpi +++ b/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpi @@ -32,7 +32,7 @@ - + @@ -45,22 +45,14 @@ - - - - - + + - - - - - diff --git a/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpr b/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpr index e4eb7aac0..567a6d7fc 100644 --- a/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpr +++ b/components/fpspreadsheet/examples/wikitabledemo/wikitablewrite.lpr @@ -10,7 +10,8 @@ program wikitablewrite; {$mode delphi}{$H+} uses - Classes, SysUtils, fpspreadsheet, wikitable; + Classes, SysUtils, fpspreadsheet, wikitable, + laz_fpspreadsheet; const Str_First = 'First'; diff --git a/components/fpspreadsheet/fpsexprparser.pas b/components/fpspreadsheet/fpsexprparser.pas index 825d39670..8d31582c4 100644 --- a/components/fpspreadsheet/fpsexprparser.pas +++ b/components/fpspreadsheet/fpsexprparser.pas @@ -12,6 +12,8 @@ but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +-------------------------------------------------------------------------------- + Modified for integration into fpspreadsheet by Werner Pamler: - Original file name: fpexprpars.pp - Rename identifiers to avoid naming conflicts with the original @@ -24,11 +26,22 @@ - TsPercentExprNode and token "%" to handle Excel's percent operation - TsParenthesisExprNode to handle the parenthesis token in RPN formulas - TsConcatExprNode and token "&" to handle string concatenation + - TsUPlusExprNode for unary plus symbol - remove and modifiy built-in function such that the parser is compatible - with Excel syntax (and OpenOffice - which is the same). + with Excel syntax (and Open/LibreOffice - which is the same). - use double quotes for strings (instead of single quotes) + - add boolean constants "TRUE" and "FALSE". + - add property RPNFormula to interface the parser to RPN formulas of xls files. + - accept funtions with zero parameters ******************************************************************************} + +// To do: +// Remove exceptions, use error message strings instead +// Cell reference not working (--> formula CELL!) +// Missing arguments +// Keep spaces in formula + {$mode objfpc} {$h+} unit fpsExprParser; @@ -50,15 +63,15 @@ type fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, fekParen, *) - TTokenType = ( + TsTokenType = ( + ttCell, ttCellRange, ttNumber, ttString, ttIdentifier, ttPlus, ttMinus, ttMul, ttDiv, ttConcat, ttPercent, ttPower, ttLeft, ttRight, ttLessThan, ttLargerThan, ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual, - ttNumber, ttString, ttIdentifier, ttCell, ttCellRange, - ttComma, ttAnd, ttOr, ttXor, ttTrue, ttFalse, ttNot, ttIf, - ttEOF + ttComma, ttTrue, ttFalse, ttEOF ); - TExprFloat = Double; + TsExprFloat = Double; + TsExprFloatArray = array of TsExprFloat; const ttDelimiters = [ @@ -74,55 +87,26 @@ type TsExpressionParser = class; TsBuiltInExpressionManager = class; - { TsExpressionScanner } - TsExpressionScanner = class(TObject) - FSource : String; - LSource, - FPos: Integer; - FChar: PChar; - FToken: String; - FTokenType: TTokenType; - private - function GetCurrentChar: Char; - procedure ScanError(Msg: String); - protected - procedure SetSource(const AValue: String); virtual; - function DoIdentifier: TTokenType; - function DoNumber: TTokenType; - function DoDelimiter: TTokenType; - function DoString: TTokenType; - function NextPos: Char; // inline; - procedure SkipWhiteSpace; // inline; - function IsWordDelim(C: Char): Boolean; // inline; - function IsDelim(C: Char): Boolean; // inline; - function IsDigit(C: Char): Boolean; // inline; - function IsAlpha(C: Char): Boolean; // inline; - public - constructor Create; - function GetToken: TTokenType; - property Token: String read FToken; - property TokenType: TTokenType read FTokenType; - property Source: String read FSource write SetSource; - property Pos: Integer read FPos; - property CurrentChar: Char read GetCurrentChar; - end; - - EExprScanner = class(Exception); - - TsResultType = (rtBoolean, rtInteger, rtFloat, rtDateTime, rtString); + TsResultType = (rtEmpty, rtBoolean, rtInteger, rtFloat, rtDateTime, rtString, + rtCell, rtCellRange, rtError, rtAny); TsResultTypes = set of TsResultType; TsExpressionResult = record - ResString : String; + Worksheet : TsWorksheet; + ResString : String; case ResultType : TsResultType of - rtBoolean : (ResBoolean : Boolean); - rtInteger : (ResInteger : Int64); - rtFloat : (ResFloat : TExprFloat); - rtDateTime : (ResDateTime : TDatetime); - rtString : (); + rtEmpty : (); + rtError : (ResError : TsErrorValue); + rtBoolean : (ResBoolean : Boolean); + rtInteger : (ResInteger : Int64); + rtFloat : (ResFloat : TsExprFloat); + rtDateTime : (ResDateTime : TDatetime); + rtCell : (ResRow, ResCol : Cardinal); + rtCellRange : (ResCellRange : TsCellRange); + rtString : (); end; PsExpressionResult = ^TsExpressionResult; - TExprParameterArray = array of TsExpressionResult; + TsExprParameterArray = array of TsExpressionResult; { TsExprNode } TsExprNode = class(TObject) @@ -139,7 +123,7 @@ type function NodeValue: TsExpressionResult; end; - TExprArgumentArray = array of TsExprNode; + TsExprArgumentArray = array of TsExprNode; { TsBinaryOperationExprNode } TsBinaryOperationExprNode = class(TsExprNode) @@ -147,7 +131,7 @@ type FLeft: TsExprNode; FRight: TsExprNode; protected - procedure CheckSameNodeTypes; + procedure CheckSameNodeTypes; virtual; public constructor Create(ALeft, ARight: TsExprNode); destructor Destroy; override; @@ -164,35 +148,10 @@ type function NodeType: TsResultType; override; end; - { TsBinaryAndExprNode } - TsBinaryAndExprNode = class(TsBooleanOperationExprNode) - protected - procedure GetNodeValue(var Result: TsExpressionResult); override; - public - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - end; - - { TsBinaryOrExprNode } - TsBinaryOrExprNode = class(TsBooleanOperationExprNode) - protected - procedure GetNodeValue(var Result: TsExpressionResult); override; - public - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - end; - - { TsBinaryXorExprNode } - TsBinaryXorExprNode = class(TsBooleanOperationExprNode) - protected - procedure GetNodeValue(var Result: TsExpressionResult); override; - public - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - end; - { TsBooleanResultExprNode } TsBooleanResultExprNode = class(TsBinaryOperationExprNode) + protected + procedure CheckSameNodeTypes; override; public procedure Check; override; function NodeType: TsResultType; override; @@ -219,11 +178,14 @@ type { TsOrderingExprNode } TsOrderingExprNode = class(TsBooleanResultExprNode) + protected + procedure CheckSameNodeTypes; override; + public procedure Check; override; end; - { TsLessThanExprNode } - TsLessThanExprNode = class(TsOrderingExprNode) + { TsLessExprNode } + TsLessExprNode = class(TsOrderingExprNode) protected procedure GetNodeValue(var Result: TsExpressionResult); override; public @@ -231,8 +193,8 @@ type function AsString: string; override; end; - { TsGreaterThanExprNode } - TsGreaterThanExprNode = class(TsOrderingExprNode) + { TsGreaterExprNode } + TsGreaterExprNode = class(TsOrderingExprNode) protected procedure GetNodeValue(var Result: TsExpressionResult); override; public @@ -241,7 +203,7 @@ type end; { TsLessEqualExprNode } - TsLessEqualExprNode = class(TsGreaterThanExprNode) + TsLessEqualExprNode = class(TsGreaterExprNode) protected procedure GetNodeValue(var Result: TsExpressionResult); override; public @@ -250,7 +212,7 @@ type end; { TsGreaterEqualExprNode } - TsGreaterEqualExprNode = class(TsLessThanExprNode) + TsGreaterEqualExprNode = class(TsLessExprNode) protected procedure GetNodeValue(var Result: TsExpressionResult); override; public @@ -258,36 +220,23 @@ type function AsString: string; override; end; - { TIfExprNode } - TIfExprNode = class(TsBinaryOperationExprNode) - private - FCondition: TsExprNode; - protected - procedure GetNodeValue(var Result: TsExpressionResult); override; - procedure Check; override; - function NodeType: TsResultType; override; - public - constructor Create(ACondition, ALeft, ARight: TsExprNode); - destructor Destroy; override; - function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - function AsString: string; override; - property Condition: TsExprNode read FCondition; - end; - { TsConcatExprNode } TsConcatExprNode = class(TsBinaryOperationExprNode) protected - procedure Check; override; + procedure CheckSameNodeTypes; override; procedure GetNodeValue(var Result: TsExpressionResult); override; - function NodeType: TsResultType; override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: string ; override; + procedure Check; override; + function NodeType: TsResultType; override; end; { TsMathOperationExprNode } TsMathOperationExprNode = class(TsBinaryOperationExprNode) protected + procedure CheckSameNodeTypes; override; + public procedure Check; override; function NodeType: TsResultType; override; end; @@ -304,7 +253,6 @@ type { TsSubtractExprNode } TsSubtractExprNode = class(TsMathOperationExprNode) protected - procedure Check; override; procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; @@ -314,21 +262,29 @@ type { TsMultiplyExprNode } TsMultiplyExprNode = class(TsMathOperationExprNode) protected - procedure check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: string ; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsDivideExprNode } TsDivideExprNode = class(TsMathOperationExprNode) protected - Procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: string ; override; + function NodeType: TsResultType; override; + end; + + { TsPowerExprNode } + TsPowerExprNode = class(TsMathOperationExprNode) + protected procedure GetNodeValue(var Result: TsExpressionResult); override; + public + function AsRPNItem(ANext: PRPNItem): PRPNItem; override; + function AsString: string ; override; function NodeType: TsResultType; override; end; @@ -336,10 +292,11 @@ type TsUnaryOperationExprNode = class(TsExprNode) private FOperand: TsExprNode; + protected + procedure Check; override; public constructor Create(AOperand: TsExprNode); destructor Destroy; override; - procedure Check; override; property Operand: TsExprNode read FOperand; end; @@ -353,85 +310,103 @@ type TsNotExprNode = class(TsUnaryOperationExprNode) protected procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsConvertToIntExprNode } TsConvertToIntExprNode = class(TsConvertExprNode) - protected + public procedure Check; override; end; { TsIntToFloatExprNode } TsIntToFloatExprNode = class(TsConvertToIntExprNode) + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsIntToDateTimeExprNode } TsIntToDateTimeExprNode = class(TsConvertToIntExprNode) + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsFloatToDateTimeExprNode } TsFloatToDateTimeExprNode = class(TsConvertExprNode) protected procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; - { TsNegateExprNode } - TsNegateExprNode = class(TsUnaryOperationExprNode) + { TsUPlusExprNode } + TsUPlusExprNode = class(TsUnaryOperationExprNode) + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; procedure Check; override; function NodeType: TsResultType; override; + end; + + { TsUMinusExprNode } + TsUMinusExprNode = class(TsUnaryOperationExprNode) + protected procedure GetNodeValue(var Result: TsExpressionResult); override; + public + function AsRPNItem(ANext: PRPNItem): PRPNItem; override; + function AsString: String; override; + procedure Check; override; + function NodeType: TsResultType; override; end; { TsPercentExprNode } TsPercentExprNode = class(TsUnaryOperationExprNode) + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; procedure Check; override; function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsParenthesisExprNode } TsParenthesisExprNode = class(TsUnaryOperationExprNode) + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; end; { TsConstExprNode } - TsConstExprNode = Class(TsExprNode) + TsConstExprNode = class(TsExprNode) private FValue: TsExpressionResult; + protected + procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor CreateString(AValue: String); constructor CreateInteger(AValue: Int64); constructor CreateDateTime(AValue: TDateTime); - constructor CreateFloat(AValue: TExprFloat); + constructor CreateFloat(AValue: TsExprFloat); constructor CreateBoolean(AValue: Boolean); + constructor CreateError(AValue: TsErrorValue); function AsString: string; override; function AsRPNItem(ANext: PRPNItem): PRPNItem; override; - procedure Check; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; function NodeType : TsResultType; override; // For inspection property ConstValue: TsExpressionResult read FValue; @@ -440,10 +415,10 @@ type TsExprIdentifierType = (itVariable, itFunctionCallBack, itFunctionHandler); TsExprFunctionCallBack = procedure (var Result: TsExpressionResult; - const Args: TExprParameterArray); + const Args: TsExprParameterArray); TsExprFunctionEvent = procedure (var Result: TsExpressionResult; - const Args: TExprParameterArray) of object; + const Args: TsExprParameterArray) of object; { TsExprIdentifierDef } TsExprIdentifierDef = class(TCollectionItem) @@ -453,11 +428,13 @@ type FArgumentTypes: String; FIDType: TsExprIdentifierType; FName: ShortString; + FExcelCode: Integer; + FVariableArgumentCount: Boolean; FOnGetValue: TsExprFunctionEvent; FOnGetValueCB: TsExprFunctionCallBack; function GetAsBoolean: Boolean; function GetAsDateTime: TDateTime; - function GetAsFloat: TExprFloat; + function GetAsFloat: TsExprFloat; function GetAsInteger: Int64; function GetAsString: String; function GetResultType: TsResultType; @@ -465,7 +442,7 @@ type procedure SetArgumentTypes(const AValue: String); procedure SetAsBoolean(const AValue: Boolean); procedure SetAsDateTime(const AValue: TDateTime); - procedure SetAsFloat(const AValue: TExprFloat); + procedure SetAsFloat(const AValue: TsExprFloat); procedure SetAsInteger(const AValue: Int64); procedure SetAsString(const AValue: String); procedure SetName(const AValue: ShortString); @@ -477,11 +454,13 @@ type public function ArgumentCount: Integer; procedure Assign(Source: TPersistent); override; - property AsFloat: TExprFloat Read GetAsFloat Write SetAsFloat; + property AsFloat: TsExprFloat Read GetAsFloat Write SetAsFloat; property AsInteger: Int64 Read GetAsInteger Write SetAsInteger; property AsString: String Read GetAsString Write SetAsString; property AsBoolean: Boolean Read GetAsBoolean Write SetAsBoolean; property AsDateTime: TDateTime Read GetAsDateTime Write SetAsDateTime; + function HasFixedArgumentCount: Boolean; + function IsOptionalArgument(AIndex: Integer): Boolean; property OnGetFunctionValueCallBack: TsExprFunctionCallBack read FOnGetValueCB write FOnGetValueCB; published property IdentifierType: TsExprIdentifierType read FIDType write FIDType; @@ -489,11 +468,13 @@ type property Value: String read GetValue write SetValue; property ParameterTypes: String read FArgumentTypes write SetArgumentTypes; property ResultType: TsResultType read GetResultType write SetResultType; + property ExcelCode: Integer read FExcelCode write FExcelCode; + property VariableArgumentCount: Boolean read FVariableArgumentCount write FVariableArgumentCount; property OnGetFunctionValue: TsExprFunctionEvent read FOnGetValue write FOnGetValue; end; - TsBuiltInExprCategory = (bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, - bcData, bcVaria, bcUser); + TsBuiltInExprCategory = (bcMath, bcStatistics, bcStrings, bcLogical, bcDateTime, + bcLookup, bcInfo, bcUser); TsBuiltInExprCategories = set of TsBuiltInExprCategory; @@ -517,9 +498,11 @@ type procedure Update(Item: TCollectionItem); override; property Parser: TsExpressionParser read FParser; public - function IndexOfIdentifier(const AName: ShortString): Integer; function FindIdentifier(const AName: ShortString): TsExprIdentifierDef; + function IdentifierByExcelCode(const AExcelCode: Integer): TsExprIdentifierDef; function IdentifierByName(const AName: ShortString): TsExprIdentifierDef; + function IndexOfIdentifier(const AName: ShortString): Integer; overload; + function IndexOfIdentifier(const AExcelCode: Integer): Integer; overload; function AddVariable(const AName: ShortString; AResultType: TsResultType; AValue: String): TsExprIdentifierDef; function AddBooleanVariable(const AName: ShortString; @@ -527,15 +510,17 @@ type function AddIntegerVariable(const AName: ShortString; AValue: Integer): TsExprIdentifierDef; function AddFloatVariable(const AName: ShortString; - AValue: TExprFloat): TsExprIdentifierDef; + AValue: TsExprFloat): TsExprIdentifierDef; function AddStringVariable(const AName: ShortString; AValue: String): TsExprIdentifierDef; function AddDateTimeVariable(const AName: ShortString; AValue: TDateTime): TsExprIdentifierDef; function AddFunction(const AName: ShortString; const AResultType: Char; - const AParamTypes: String; ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef; + const AParamTypes: String; const AExcelCode: Integer; + ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef; function AddFunction(const AName: ShortString; const AResultType: Char; - const AParamTypes: String; ACallBack: TsExprFunctionEvent): TsExprIdentifierDef; + const AParamTypes: String; const AExcelCode: Integer; + ACallBack: TsExprFunctionEvent): TsExprIdentifierDef; property Identifiers[AIndex: Integer]: TsExprIdentifierDef read GetI write SetI; default; end; @@ -545,57 +530,60 @@ type FID: TsExprIdentifierDef; PResult: PsExpressionResult; FResultType: TsResultType; + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor CreateIdentifier(AID: TsExprIdentifierDef); function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; property Identifier: TsExprIdentifierDef read FID; end; - { TFPExprVariable } - TFPExprVariable = class(TsIdentifierExprNode) + { TsVariableExprNode } + TsVariableExprNode = class(TsIdentifierExprNode) procedure Check; override; function AsString: string; override; Function AsRPNItem(ANext: PRPNItem): PRPNItem; override; end; - { TFPExprFunction } - TFPExprFunction = class(TsIdentifierExprNode) + { TsFunctionExprNode } + TsFunctionExprNode = class(TsIdentifierExprNode) private - FArgumentNodes: TExprArgumentArray; - FargumentParams: TExprParameterArray; + FArgumentNodes: TsExprArgumentArray; + FargumentParams: TsExprParameterArray; protected procedure CalcParams; - procedure Check; override; public constructor CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); virtual; + const Args: TsExprArgumentArray); virtual; destructor Destroy; override; function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: String; override; - property ArgumentNodes: TExprArgumentArray read FArgumentNodes; - property ArgumentParams: TExprParameterArray read FArgumentParams; + procedure Check; override; + property ArgumentNodes: TsExprArgumentArray read FArgumentNodes; + property ArgumentParams: TsExprParameterArray read FArgumentParams; end; - { TFPFunctionCallBack } - TFPFunctionCallBack = class(TFPExprFunction) + { TsFunctionCallBackExprNode } + TsFunctionCallBackExprNode = class(TsFunctionExprNode) private FCallBack: TsExprFunctionCallBack; + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); override; - procedure GetNodeValue(var Result: TsExpressionResult); override; + const Args: TsExprArgumentArray); override; property CallBack: TsExprFunctionCallBack read FCallBack; end; - { TFPFunctionEventHandler } - TFPFunctionEventHandler = class(TFPExprFunction) + { TFPFunctionEventHandlerExprNode } + TFPFunctionEventHandlerExprNode = class(TsFunctionExprNode) private FCallBack: TsExprFunctionEvent; + protected + procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); override; - procedure GetNodeValue(var Result: TsExpressionResult); override; + const Args: TsExprArgumentArray); override; property CallBack: TsExprFunctionEvent read FCallBack; end; @@ -603,18 +591,78 @@ type TsCellExprNode = class(TsExprNode) private FWorksheet: TsWorksheet; - FCell: PCell; + FRow, FCol: Cardinal; FFlags: TsRelFlags; + FCell: PCell; + FIsRef: Boolean; + protected + procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; public constructor Create(AWorksheet: TsWorksheet; ACellString: String); overload; - constructor Create(AWorksheet: TsWorksheet; ACell: PCell; AFlags: TsRelFlags); overload; + constructor Create(AWorksheet: TsWorksheet; ARow, ACol: Cardinal; + AFlags: TsRelFlags); overload; function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsString: string; override; - procedure Check; override; function NodeType: TsResultType; override; - procedure GetNodeValue(var Result: TsExpressionResult); override; + property Worksheet: TsWorksheet read FWorksheet; end; + { TsCellRangeExprNode } + TsCellRangeExprNode = class(TsExprNode) + private + FWorksheet: TsWorksheet; + FRow1, FRow2: Cardinal; + FCol1, FCol2: Cardinal; + FFlags: TsRelFlags; + protected + procedure Check; override; + procedure GetNodeValue(var Result: TsExpressionResult); override; + public + constructor Create(AWorksheet: TsWorksheet; ACellRangeString: String); overload; + constructor Create(AWorksheet: TsWorksheet; ARow1,ACol1, ARow2,ACol2: Cardinal; + AFlags: TsRelFlags); overload; + function AsRPNItem(ANext: PRPNItem): PRPNItem; override; + function AsString: String; override; + function NodeType: TsResultType; override; + property Worksheet: TsWorksheet read FWorksheet; + end; + + { TsExpressionScanner } + TsExpressionScanner = class(TObject) + FSource : String; + LSource, + FPos: Integer; + FChar: PChar; + FToken: String; + FTokenType: TsTokenType; + private + function GetCurrentChar: Char; + procedure ScanError(Msg: String); + protected + procedure SetSource(const AValue: String); virtual; + function DoIdentifier: TsTokenType; + function DoNumber: TsTokenType; + function DoDelimiter: TsTokenType; + function DoString: TsTokenType; + function NextPos: Char; // inline; + procedure SkipWhiteSpace; // inline; + function IsWordDelim(C: Char): Boolean; // inline; + function IsDelim(C: Char): Boolean; // inline; + function IsDigit(C: Char): Boolean; // inline; + function IsAlpha(C: Char): Boolean; // inline; + public + constructor Create; + function GetToken: TsTokenType; + property Token: String read FToken; + property TokenType: TsTokenType read FTokenType; + property Source: String read FSource write SetSource; + property Pos: Integer read FPos; + property CurrentChar: Char read GetCurrentChar; + end; + + EExprScanner = class(Exception); + { TsExpressionParser } TsExpressionParser = class private @@ -627,22 +675,27 @@ type FDirty: Boolean; FWorksheet: TsWorksheet; procedure CheckEOF; + procedure CheckNodes(var ALeft, ARight: TsExprNode); function ConvertNode(Todo: TsExprNode; ToType: TsResultType): TsExprNode; function GetAsBoolean: Boolean; function GetAsDateTime: TDateTime; - function GetAsFloat: TExprFloat; + function GetAsFloat: TsExprFloat; function GetAsInteger: Int64; function GetAsString: String; + function GetRPNFormula: TsRPNFormula; function MatchNodes(Todo, Match: TsExprNode): TsExprNode; - procedure CheckNodes(var ALeft, ARight: TsExprNode); procedure SetBuiltIns(const AValue: TsBuiltInExprCategories); procedure SetIdentifiers(const AValue: TsExprIdentifierDefs); + procedure SetRPNFormula(const AFormula: TsRPNFormula); + protected + class function BuiltinExpressionManager: TsBuiltInExpressionManager; procedure ParserError(Msg: String); procedure SetExpression(const AValue: String); virtual; procedure CheckResultType(const Res: TsExpressionResult; AType: TsResultType); inline; - class function BuiltinExpressionManager: TsBuiltInExpressionManager; + function CurrentToken: String; + function GetToken: TsTokenType; function Level1: TsExprNode; function Level2: TsExprNode; function Level3: TsExprNode; @@ -650,35 +703,40 @@ type function Level5: TsExprNode; function Level6: TsExprNode; function Primitive: TsExprNode; - function GetToken: TTokenType; - function TokenType: TTokenType; - function CurrentToken: String; + function TokenType: TsTokenType; procedure CreateHashList; property Scanner: TsExpressionScanner read FScanner; property ExprNode: TsExprNode read FExprNode; property Dirty: Boolean read FDirty; + public - constructor Create(AWorksheet: TsWorksheet); + constructor Create(AWorksheet: TsWorksheet); virtual; destructor Destroy; override; function IdentifierByName(AName: ShortString): TsExprIdentifierDef; virtual; procedure Clear; - function BuildRPNFormula: TsRPNFormula; - function BuildFormula: String; - procedure EvaluateExpression(var Result: TsExpressionResult); + function BuildStringFormula: String; function Evaluate: TsExpressionResult; + procedure EvaluateExpression(var Result: TsExpressionResult); function ResultType: TsResultType; - property AsFloat: TExprFloat read GetAsFloat; + property AsFloat: TsExprFloat read GetAsFloat; property AsInteger: Int64 read GetAsInteger; property AsString: String read GetAsString; property AsBoolean: Boolean read GetAsBoolean; property AsDateTime: TDateTime read GetAsDateTime; - property Worksheet: TsWorksheet read FWorksheet; // The expression to parse property Expression: String read FExpression write SetExpression; + property RPNFormula: TsRPNFormula read GetRPNFormula write SetRPNFormula; property Identifiers: TsExprIdentifierDefs read FIdentifiers write SetIdentifiers; property BuiltIns: TsBuiltInExprCategories read FBuiltIns write SetBuiltIns; + property Worksheet: TsWorksheet read FWorksheet; end; + TsSpreadsheetParser = class(TsExpressionParser) + public + constructor Create(AWorksheet: TsWorksheet); override; + end; + + { TsBuiltInExpressionManager } TsBuiltInExpressionManager = class(TComponent) private @@ -692,6 +750,7 @@ type destructor Destroy; override; function IndexOfIdentifier(const AName: ShortString): Integer; function FindIdentifier(const AName: ShortString): TsBuiltInExprIdentifierDef; + function IdentifierByExcelCode(const AExcelCode: Integer): TsBuiltInExprIdentifierDef; function IdentifierByName(const AName: ShortString): TsBuiltInExprIdentifierDef; function AddVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString; AResultType: TsResultType; AValue: String): TsBuiltInExprIdentifierDef; @@ -700,39 +759,57 @@ type function AddIntegerVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: Integer): TsBuiltInExprIdentifierDef; function AddFloatVariable(const ACategory: TsBuiltInExprCategory; - const AName: ShortString; AValue: TExprFloat): TsBuiltInExprIdentifierDef; + const AName: ShortString; AValue: TsExprFloat): TsBuiltInExprIdentifierDef; function AddStringVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: String): TsBuiltInExprIdentifierDef; function AddDateTimeVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: TDateTime): TsBuiltInExprIdentifierDef; function AddFunction(const ACategory: TsBuiltInExprCategory; const AName: ShortString; const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef; + const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef; function AddFunction(const ACategory: TsBuiltInExprCategory; const AName: ShortString; const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef; + const AExcelCode: Integer; ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef; property IdentifierCount: Integer read GetCount; property Identifiers[AIndex: Integer]: TsBuiltInExprIdentifierDef read GetI; end; EExprParser = class(Exception); -function TokenName(AToken: TTokenType): String; +function TokenName(AToken: TsTokenType): String; function ResultTypeName(AResult: TsResultType): String; function CharToResultType(C: Char): TsResultType; function BuiltinIdentifiers: TsBuiltInExpressionManager; procedure RegisterStdBuiltins(AManager: TsBuiltInExpressionManager); -function ArgToFloat(Arg: TsExpressionResult): TExprFloat; +function ArgToBoolean(Arg: TsExpressionResult): Boolean; +function ArgToCell(Arg: TsExpressionResult): PCell; +function ArgToDateTime(Arg: TsExpressionResult): TDateTime; +function ArgToInt(Arg: TsExpressionResult): Integer; +function ArgToFloat(Arg: TsExpressionResult): TsExprFloat; +function ArgToString(Arg: TsExpressionResult): String; +procedure ArgsToFloatArray(const Args: TsExprParameterArray; out AData: TsExprFloatArray); +function BooleanResult(AValue: Boolean): TsExpressionResult; +function DateTimeResult(AValue: TDateTime): TsExpressionResult; +function EmptyResult: TsExpressionResult; +function ErrorResult(const AValue: TsErrorValue): TsExpressionResult; +function FloatResult(const AValue: TsExprFloat): TsExpressionResult; +function IntegerResult(const AValue: Integer): TsExpressionResult; +function StringResult(const AValue: String): TsExpressionResult; + +procedure RegisterFunction(const AName: ShortString; const AResultType: Char; + const AParamTypes: String; const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack); const - AllBuiltIns = [bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, - bcData, bcVaria, bcUser]; + AllBuiltIns = [bcMath, bcStatistics, bcStrings, bcLogical, bcDateTime, bcLookup, + bcInfo, bcUser]; +var + ExprFormatSettings: TFormatSettings; implementation uses - typinfo, fpsutils; + typinfo, math, lazutf8, dateutils, xlsconst, fpsutils; const cNull = #0; @@ -740,9 +817,9 @@ const Digits = ['0'..'9', '.']; WhiteSpace = [' ', #13, #10, #9]; - Operators = ['+', '-', '<', '>', '=', '/', '*', '&', '%']; + Operators = ['+', '-', '<', '>', '=', '/', '*', '&', '%', '^']; Delimiters = Operators + [',', '(', ')']; - Symbols = ['^'] + Delimiters; + Symbols = Delimiters; WordDelimiters = WhiteSpace + Symbols; resourcestring @@ -766,9 +843,9 @@ resourcestring SErrNoLeftOperand = 'No left operand for binary operation %s'; SErrNoRightOperand = 'No left operand for binary operation %s'; SErrNoNegation = 'Cannot negate expression of type %s: %s'; + SErrNoUPlus = 'Cannot perform unary plus operation on type %s: %s'; SErrNoNOTOperation = 'Cannot perform NOT operation on expression of type %s: %s'; SErrNoPercentOperation = 'Cannot perform percent operation on expression of type %s: %s'; - SErrNoXOROperationRPN = 'Cannot create RPN item for "xor" expression'; SErrTypesDoNotMatch = 'Type mismatch: %s<>%s for expressions "%s" and "%s".'; SErrTypesIncompatible = 'Incompatible types: %s<>%s for expressions "%s" and "%s".'; SErrNoNodeToCheck = 'Internal error: No node to check !'; @@ -781,12 +858,7 @@ resourcestring SErrInvalidResultType = 'Invalid result type: %s'; SErrNotVariable = 'Identifier %s is not a variable'; SErrInactive = 'Operation not allowed while an expression is active'; - SErrIFNeedsBoolean = 'First argument to IF must be of type boolean: %s'; - SErrCaseLabelNotAConst = 'Case label %d "%s" is not a constant expression'; - SErrCaseLabelType = 'Case label %d "%s" needs type %s, but has type %s'; - SErrCaseValueType = 'Case value %d "%s" needs type %s, but has type %s'; - SErrNoCellOperand = 'Cell operand is NIL.'; - SErrCellError = 'Cell %s contains an error.'; + SErrCircularReference = 'Circular reference found when calculating worksheet formulas'; { --------------------------------------------------------------------- Auxiliary functions @@ -802,9 +874,9 @@ begin raise EExprParser.CreateFmt(Fmt, Args); end; -function TokenName (AToken: TTokenType): String; +function TokenName(AToken: TsTokenType): String; begin - Result := GetEnumName(TypeInfo(TTokenType), ord(AToken)); + Result := GetEnumName(TypeInfo(TsTokenType), ord(AToken)); end; function ResultTypeName(AResult: TsResultType): String; @@ -820,6 +892,9 @@ begin 'B' : Result := rtBoolean; 'I' : Result := rtInteger; 'F' : Result := rtFloat; + 'R' : Result := rtCellRange; + 'C' : Result := rtCell; + '?' : Result := rtAny; else RaiseParserError(SErrInvalidResultCharacter, [C]); end; @@ -850,53 +925,7 @@ begin Source := ''; end; -function TsExpressionScanner.IsAlpha(C: Char): Boolean; -begin - Result := C in ['A'..'Z', 'a'..'z']; -end; - -procedure TsExpressionScanner.SetSource(const AValue: String); -begin - FSource := AValue; - LSource := Length(FSource); - FTokenType := ttEOF; - if LSource = 0 then - FPos := 0 - else - FPos := 1; - FChar := PChar(FSource); - FToken := ''; -end; - -function TsExpressionScanner.NextPos: Char; -begin - Inc(FPos); - Inc(FChar); - Result := FChar^; -end; - -function TsExpressionScanner.IsWordDelim(C: Char): Boolean; -begin - Result := C in WordDelimiters; -end; - -function TsExpressionScanner.IsDelim(C: Char): Boolean; -begin - Result := C in Delimiters; -end; - -function TsExpressionScanner.IsDigit(C: Char): Boolean; -begin - Result := C in Digits; -end; - -procedure TsExpressionScanner.SkipWhiteSpace; -begin - while (FChar^ in WhiteSpace) and (FPos <= LSource) do - NextPos; -end; - -function TsExpressionScanner.DoDelimiter: TTokenType; +function TsExpressionScanner.DoDelimiter: TsTokenType; var B : Boolean; C, D : Char; @@ -938,12 +967,59 @@ begin end; end; -procedure TsExpressionScanner.ScanError(Msg: String); +function TsExpressionScanner.DoIdentifier: TsTokenType; +var + C: Char; + S: String; + row, row2: Cardinal; + col, col2: Cardinal; + flags: TsRelFlags; begin - raise EExprScanner.Create(Msg) + C := CurrentChar; + while (not IsWordDelim(C)) and (C <> cNull) do + begin + FToken := FToken + C; + C := NextPos; + end; + S := LowerCase(Token); + if ParseCellString(S, row, col, flags) and (C <> '(') then + Result := ttCell + else if ParseCellRangeString(S, row, col, row2, col2, flags) and (C <> '(') then + Result := ttCellRange + else if (S = 'true') and (C <> '(') then + Result := ttTrue + else if (S = 'false') and (C <> '(') then + Result := ttFalse + else + Result := ttIdentifier; end; -function TsExpressionScanner.DoString: TTokenType; +function TsExpressionScanner.DoNumber: TsTokenType; +var + C: Char; + X: TsExprFloat; + prevC: Char; +begin + C := CurrentChar; + prevC := #0; + while (not IsWordDelim(C) or (prevC = 'E')) and (C <> cNull) do + begin + if not ( IsDigit(C) + or ((FToken <> '') and (Upcase(C) = 'E')) + or ((FToken <> '') and (C in ['+', '-']) and (prevC = 'E')) + ) + then + ScanError(Format(SErrInvalidNumberChar, [C])); + FToken := FToken+C; + prevC := Upcase(C); + C := NextPos; + end; + if not TryStrToFloat(FToken, X, ExprFormatSettings) then + ScanError(Format(SErrInvalidNumber, [FToken])); + Result := ttNumber; +end; + +function TsExpressionScanner.DoString: TsTokenType; function TerminatingChar(C: Char): boolean; begin @@ -979,71 +1055,7 @@ begin Result := #0; end; -function TsExpressionScanner.DoNumber: TTokenType; -var - C: Char; - X: TExprFloat; - I: Integer; - prevC: Char; -begin - C := CurrentChar; - prevC := #0; - while (not IsWordDelim(C) or (prevC = 'E')) and (C <> cNull) do - begin - if not ( IsDigit(C) - or ((FToken <> '') and (Upcase(C) = 'E')) - or ((FToken <> '') and (C in ['+', '-']) and (prevC = 'E')) - ) - then - ScanError(Format(SErrInvalidNumberChar, [C])); - FToken := FToken+C; - prevC := Upcase(C); - C := NextPos; - end; - val(FToken, X, I); - if (I <> 0) then - ScanError(Format(SErrInvalidNumber, [FToken])); - Result := ttNumber; -end; - -function TsExpressionScanner.DoIdentifier: TTokenType; -var - C: Char; - S: String; - row, row2: Cardinal; - col, col2: Cardinal; - flags: TsRelFlags; -begin - C := CurrentChar; - while (not IsWordDelim(C)) and (C <> cNull) do - begin - FToken := FToken + C; - C := NextPos; - end; - S := LowerCase(Token); - if (S = 'or') then - Result := ttOr - else if (S = 'xor') then - Result := ttXOr - else if (S = 'and') then - Result := ttAnd - else if (S = 'true') then - Result := ttTrue - else if (S = 'false') then - Result := ttFalse - else if (S = 'not') then - Result := ttNot - else if (S = 'if') then - Result := ttIF - else if ParseCellString(S, row, col, flags) then - Result := ttCell - else if ParseCellRangeString(S, row, col, row2, col2, flags) then - Result := ttCellRange - else - Result := ttIdentifier; -end; - -function TsExpressionScanner.GetToken: TTokenType; +function TsExpressionScanner.GetToken: TsTokenType; var C: Char; begin @@ -1058,13 +1070,64 @@ begin Result := DoString else if IsDigit(C) then Result := DoNumber - else if IsAlpha(C) then + else if IsAlpha(C) or (C = '$') then Result := DoIdentifier else ScanError(Format(SErrUnknownCharacter, [FPos, C])); FTokenType := Result; end; +function TsExpressionScanner.IsAlpha(C: Char): Boolean; +begin + Result := C in ['A'..'Z', 'a'..'z']; +end; + +function TsExpressionScanner.IsDelim(C: Char): Boolean; +begin + Result := C in Delimiters; +end; + +function TsExpressionScanner.IsDigit(C: Char): Boolean; +begin + Result := C in Digits; +end; + +function TsExpressionScanner.IsWordDelim(C: Char): Boolean; +begin + Result := C in WordDelimiters; +end; + +function TsExpressionScanner.NextPos: Char; +begin + Inc(FPos); + Inc(FChar); + Result := FChar^; +end; + +procedure TsExpressionScanner.ScanError(Msg: String); +begin + raise EExprScanner.Create(Msg) +end; + +procedure TsExpressionScanner.SetSource(const AValue: String); +begin + FSource := AValue; + LSource := Length(FSource); + FTokenType := ttEOF; + if LSource = 0 then + FPos := 0 + else + FPos := 1; + FChar := PChar(FSource); + FToken := ''; +end; + +procedure TsExpressionScanner.SkipWhiteSpace; +begin + while (FChar^ in WhiteSpace) and (FPos <= LSource) do + NextPos; +end; + {------------------------------------------------------------------------------} { TsExpressionParser } @@ -1089,69 +1152,17 @@ begin inherited Destroy; end; -function TsExpressionParser.BuildRPNFormula: TsRPNFormula; +function TsExpressionParser.BuildStringFormula: String; begin - Result := CreateRPNFormula(FExprNode.AsRPNItem(nil), true); + if FExprNode = nil then + Result := '' + else + Result := FExprNode.AsString; end; -function TsExpressionParser.BuildFormula: String; +class function TsExpressionParser.BuiltinExpressionManager: TsBuiltInExpressionManager; begin - Result := FExprNode.AsString; -end; - -procedure TsExpressionParser.Clear; -begin - FExpression := ''; - FHashList.Clear; - FExprNode.Free; -end; - -procedure TsExpressionParser.CreateHashList; -var - ID: TsExprIdentifierDef; - BID: TsBuiltInExprIdentifierDef; - i: Integer; - M: TsBuiltInExpressionManager; -begin - FHashList.Clear; - // Builtins - M := BuiltinExpressionManager; - If (FBuiltins <> []) and Assigned(M) then - for i:=0 to M.IdentifierCount-1 do - begin - BID := M.Identifiers[i]; - If BID.Category in FBuiltins then - FHashList.Add(LowerCase(BID.Name), BID); - end; - // User - for i:=0 to FIdentifiers.Count-1 do - begin - ID := FIdentifiers[i]; - FHashList.Add(LowerCase(ID.Name), ID); - end; - FDirty := False; -end; - -function TsExpressionParser.CurrentToken: String; -begin - Result := FScanner.Token; -end; - -function TsExpressionParser.TokenType: TTokenType; -begin - Result := FScanner.TokenType; -end; - -function TsExpressionParser.IdentifierByName(AName: ShortString): TsExprIdentifierDef; -begin - If FDirty then - CreateHashList; - Result := TsExprIdentifierDef(FHashList.Find(LowerCase(AName))); -end; - -function TsExpressionParser.GetToken: TTokenType; -begin - Result := FScanner.GetToken; + Result := BuiltinIdentifiers; end; procedure TsExpressionParser.CheckEOF; @@ -1160,23 +1171,25 @@ begin ParserError(SErrUnexpectedEndOfExpression); end; -procedure TsExpressionParser.SetIdentifiers(const AValue: TsExprIdentifierDefs); +{ If the result types differ, they are converted to a common type if possible. } +procedure TsExpressionParser.CheckNodes(var ALeft, ARight: TsExprNode); begin - FIdentifiers.Assign(AValue) + ALeft := MatchNodes(ALeft, ARight); + ARight := MatchNodes(ARight, ALeft); end; -procedure TsExpressionParser.EvaluateExpression(var Result: TsExpressionResult); +procedure TsExpressionParser.CheckResultType(const Res: TsExpressionResult; + AType: TsResultType); inline; begin - if (FExpression = '') then - ParserError(SErrInExpressionEmpty); - if not Assigned(FExprNode) then - ParserError(SErrInExpression); - FExprNode.GetNodeValue(Result); + if (Res.ResultType <> AType) then + RaiseParserError(SErrInvalidResultType, [ResultTypeName(Res.ResultType)]); end; -procedure TsExpressionParser.ParserError(Msg: String); +procedure TsExpressionParser.Clear; begin - raise EExprParser.Create(Msg); + FExpression := ''; + FHashList.Clear; + FreeAndNil(FExprNode); end; function TsExpressionParser.ConvertNode(ToDo: TsExprNode; @@ -1196,6 +1209,51 @@ begin end; end; +procedure TsExpressionParser.CreateHashList; +var + ID: TsExprIdentifierDef; + BID: TsBuiltInExprIdentifierDef; + i: Integer; + M: TsBuiltInExpressionManager; +begin + FHashList.Clear; + // Builtins + M := BuiltinExpressionManager; + If (FBuiltins <> []) and Assigned(M) then + for i:=0 to M.IdentifierCount-1 do + begin + BID := M.Identifiers[i]; + If BID.Category in FBuiltins then + FHashList.Add(UpperCase(BID.Name), BID); + end; + // User + for i:=0 to FIdentifiers.Count-1 do + begin + ID := FIdentifiers[i]; + FHashList.Add(UpperCase(ID.Name), ID); + end; + FDirty := False; +end; + +function TsExpressionParser.CurrentToken: String; +begin + Result := FScanner.Token; +end; + +function TsExpressionParser.Evaluate: TsExpressionResult; +begin + EvaluateExpression(Result); +end; + +procedure TsExpressionParser.EvaluateExpression(var Result: TsExpressionResult); +begin + if (FExpression = '') then + ParserError(SErrInExpressionEmpty); + if not Assigned(FExprNode) then + ParserError(SErrInExpression); + FExprNode.GetNodeValue(Result); +end; + function TsExpressionParser.GetAsBoolean: Boolean; var Res: TsExpressionResult; @@ -1214,7 +1272,7 @@ begin Result := Res.ResDatetime; end; -function TsExpressionParser.GetAsFloat: TExprFloat; +function TsExpressionParser.GetAsFloat: TsExprFloat; var Res: TsExpressionResult; begin @@ -1241,56 +1299,35 @@ begin Result := Res.ResString; end; -{ - Checks types of todo and match. If ToDO can be converted to it matches - the type of match, then a node is inserted. - For binary operations, this function is called for both operands. -} -function TsExpressionParser.MatchNodes(ToDo, Match: TsExprNode): TsExprNode; -Var - TT, MT : TsResultType; +function TsExpressionParser.GetRPNFormula: TsRPNFormula; begin - Result := ToDo; - TT := ToDo.NodeType; - MT := Match.NodeType; - If TT <> MT then - begin - if TT = rtInteger then - begin - if (MT in [rtFloat, rtDateTime]) then - Result := ConvertNode(ToDo, MT); - end - else if (TT = rtFloat) then - begin - if (MT = rtDateTime) then - Result := ConvertNode(ToDo, rtDateTime); - end; - end; + Result := CreateRPNFormula(FExprNode.AsRPNItem(nil), true); end; -{ - If the result types differ, they are converted to a common type if possible. -} -procedure TsExpressionParser.CheckNodes(var ALeft, ARight: TsExprNode); +function TsExpressionParser.GetToken: TsTokenType; begin - ALeft := MatchNodes(ALeft, ARight); - ARight := MatchNodes(ARight, ALeft); + Result := FScanner.GetToken; end; -procedure TsExpressionParser.SetBuiltIns(const AValue: TsBuiltInExprCategories); +function TsExpressionParser.IdentifierByName(AName: ShortString): TsExprIdentifierDef; +var + s: String; begin - if FBuiltIns = AValue then - exit; - FBuiltIns := AValue; - FDirty := true; + if FDirty then + CreateHashList; + s := FHashList.NameOfIndex(0); + s := FHashList.NameOfIndex(25); + s := FHashList.NameOfIndex(36); + Result := TsExprIdentifierDef(FHashList.Find(UpperCase(AName))); end; function TsExpressionParser.Level1: TsExprNode; var - tt: TTokenType; + tt: TsTokenType; Right: TsExprNode; begin {$ifdef debugexpr}Writeln('Level 1 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{ if TokenType = ttNot then begin GetToken; @@ -1299,9 +1336,26 @@ begin Result := TsNotExprNode.Create(Right); end else - Result := Level2; - + } + Result := Level2; + { try + if TokenType = ttPower then + begin + tt := Tokentype; + GetToken; + CheckEOF; + Right := Level2; + Result := TsPowerExprNode.Create(Result, Right); + end; + except + Result.Free; + raise; + end; + } +{ + try + while (TokenType in [ttAnd, ttOr, ttXor]) do begin tt := TokenType; @@ -1320,15 +1374,16 @@ begin Result.Free; raise; end; +} end; function TsExpressionParser.Level2: TsExprNode; var right: TsExprNode; - tt: TTokenType; + tt: TsTokenType; C: TsBinaryOperationExprNodeClass; begin -{$ifdef debugexpr} Writeln('Level 2 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{$ifdef debugexpr} Writeln('Level 2 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} Result := Level3; try if (TokenType in ttComparisons) then @@ -1339,9 +1394,9 @@ begin Right := Level3; CheckNodes(Result, right); case tt of - ttLessthan : C := TsLessThanExprNode; + ttLessthan : C := TsLessExprNode; ttLessthanEqual : C := TsLessEqualExprNode; - ttLargerThan : C := TsGreaterThanExprNode; + ttLargerThan : C := TsGreaterExprNode; ttLargerThanEqual : C := TsGreaterEqualExprNode; ttEqual : C := TsEqualExprNode; ttNotEqual : C := TsNotEqualExprNode; @@ -1358,10 +1413,10 @@ end; function TsExpressionParser.Level3: TsExprNode; var - tt: TTokenType; + tt: TsTokenType; right: TsExprNode; begin -{$ifdef debugexpr} Writeln('Level 3 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{$ifdef debugexpr} Writeln('Level 3 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} Result := Level4; try while TokenType in [ttPlus, ttMinus, ttConcat] do begin @@ -1384,10 +1439,10 @@ end; function TsExpressionParser.Level4: TsExprNode; var - tt: TTokenType; + tt: TsTokenType; right: TsExprNode; begin -{$ifdef debugexpr} Writeln('Level 4 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{$ifdef debugexpr} Writeln('Level 4 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} Result := Level5; try while (TokenType in [ttMul, ttDiv]) do @@ -1409,23 +1464,31 @@ end; function TsExpressionParser.Level5: TsExprNode; var - B: Boolean; + isPlus, isMinus: Boolean; + tt: TsTokenType; begin -{$ifdef debugexpr} Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} - B := false; +{$ifdef debugexpr} Writeln('Level 5 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} + isPlus := false; + isMinus := false; if (TokenType in [ttPlus, ttMinus]) then begin - B := (TokenType = ttMinus); + isPlus := (TokenType = ttPlus); + isMinus := (TokenType = ttMinus); GetToken; end; Result := Level6; - if B then - Result := TsNegateExprNode.Create(Result); + if isPlus then + Result := TsUPlusExprNode.Create(Result); + if isMinus then + Result := TsUMinusExprNode.Create(Result); end; function TsExpressionParser.Level6: TsExprNode; +var + tt: TsTokenType; + Right: TsExprNode; begin -{$ifdef debugexpr} Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{$ifdef debugexpr} Writeln('Level 6 ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} if (TokenType = ttLeft) then begin GetToken; @@ -1441,21 +1504,67 @@ begin end else Result := Primitive; + + if TokenType = ttPower then + begin + try + CheckEOF; + tt := Tokentype; + GetToken; + Right := Primitive; + CheckNodes(Result, right); + Result := TsPowerExprNode.Create(Result, Right); + //GetToken; + except + Result.Free; + raise; + end; + end; +end; + +{ Checks types of todo and match. If ToDO can be converted to it matches + the type of match, then a node is inserted. + For binary operations, this function is called for both operands. } +function TsExpressionParser.MatchNodes(ToDo, Match: TsExprNode): TsExprNode; +var + TT, MT : TsResultType; +begin + Result := ToDo; + TT := ToDo.NodeType; + MT := Match.NodeType; + if TT <> MT then + begin + if TT = rtInteger then + begin + if (MT in [rtFloat, rtDateTime]) then + Result := ConvertNode(ToDo, MT); + end + else if (TT = rtFloat) then + begin + if (MT = rtDateTime) then + Result := ConvertNode(ToDo, rtDateTime); + end; + end; +end; + +procedure TsExpressionParser.ParserError(Msg: String); +begin + raise EExprParser.Create(Msg); end; function TsExpressionParser.Primitive: TsExprNode; var I: Int64; - C: Integer; - X: TExprFloat; - ACount: Integer; - isIF: Boolean; + X: TsExprFloat; + lCount: Integer; ID: TsExprIdentifierDef; - Args: TExprArgumentArray; + Args: TsExprArgumentArray; AI: Integer; cell: PCell; + optional: Boolean; + token: String; begin -{$ifdef debugexpr} Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} +{$ifdef debugexpr} Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} SetLength(Args, 0); if (TokenType = ttNumber) then begin @@ -1463,65 +1572,79 @@ begin Result := TsConstExprNode.CreateInteger(I) else begin - val(CurrentToken, X, C); - if (I = 0) then + if TryStrToFloat(CurrentToken, X, ExprFormatSettings) then Result := TsConstExprNode.CreateFloat(X) else ParserError(Format(SErrInvalidFloat, [CurrentToken])); end; end + else if (TokenType = ttTrue) then + Result := TsConstExprNode.CreateBoolean(true) + else if (TokenType = ttFalse) then + Result := TsConstExprNode.CreateBoolean(false) else if (TokenType = ttString) then Result := TsConstExprNode.CreateString(CurrentToken) - else if (TokenType in [ttTrue, ttFalse]) then - Result := TsConstExprNode.CreateBoolean(TokenType = ttTrue) else if (TokenType = ttCell) then Result := TsCellExprNode.Create(FWorksheet, CurrentToken) else if (TokenType = ttCellRange) then - raise Exception.Create('Cell range missing') - else if not (TokenType in [ttIdentifier, ttIf]) then + Result := TsCellRangeExprNode.Create(FWorksheet, CurrentToken) + else if not (TokenType in [ttIdentifier{, ttIf}]) then ParserError(Format(SerrUnknownTokenAtPos, [Scanner.Pos, CurrentToken])) else begin - isIF := (TokenType = ttIf); - if not isIF then + token := Uppercase(CurrentToken); + ID := self.IdentifierByName(token); + if (ID = nil) then + ParserError(Format(SErrUnknownIdentifier, [token])); + if (ID.IdentifierType in [itFunctionCallBack, itFunctionHandler]) then begin - ID := self.IdentifierByName(CurrentToken); - If (ID = nil) then - ParserError(Format(SErrUnknownIdentifier,[CurrentToken])) - end; - // Determine number of arguments - if isIF then - ACount := 3 - else if (ID.IdentifierType in [itFunctionCallBack, itFunctionHandler]) then - ACount := ID.ArgumentCount + lCount := ID.ArgumentCount; + if lCount = 0 then // we have to handle the () here, it will be skipped below. + begin + GetToken; + if (TokenType <> ttLeft) then + ParserError(Format(SErrLeftBracketExpected, [Scanner.Pos, CurrentToken])); + GetToken; + if (TokenType <> ttRight) then + ParserError(Format(SErrBracketExpected, [Scanner.Pos, CurrentToken])); + SetLength(Args, 0); + end; + end else - ACount := 0; + lCount := 0; + // Parse arguments. // Negative is for variable number of arguments, where Abs(value) is the minimum number of arguments - if (ACount <> 0) then + if (lCount <> 0) then begin GetToken; if (TokenType <> ttLeft) then - ParserError(Format(SErrLeftBracketExpected, [Scanner.Pos, CurrentToken])); - SetLength(Args, abs(ACount)); + ParserError(Format(SErrLeftBracketExpected, [Scanner.Pos, CurrentToken])); + SetLength(Args, abs(lCount)); AI := 0; try repeat GetToken; // Check if we must enlarge the argument array - if (ACount < 0) and (AI = Length(Args)) then + if (lCount < 0) and (AI = Length(Args)) then begin SetLength(Args, AI+1); Args[AI] := nil; end; Args[AI] := Level1; inc(AI); - if (TokenType <> ttComma) then - if (AI < abs(ACount)) then - ParserError(Format(SErrCommaExpected, [Scanner.Pos, CurrentToken])) - until (AI = ACount) or ((ACount < 0) and (TokenType = ttRight)); + optional := ID.IsOptionalArgument(AI+1); + if not optional then + begin + if (TokenType <> ttComma) then + if (AI < abs(lCount)) then + ParserError(Format(SErrCommaExpected, [Scanner.Pos, CurrentToken])) + end; + until (AI = lCount) or (((lCount < 0) or optional) and (TokenType = ttRight)); if TokenType <> ttRight then ParserError(Format(SErrBracketExpected, [Scanner.Pos, CurrentToken])); + if AI < abs(lCount) then + SetLength(Args, AI); except on E: Exception do begin @@ -1535,14 +1658,11 @@ begin end; end; end; - if isIF then - Result := TIfExprNode.Create(Args[0], Args[1], Args[2]) - else - case ID.IdentifierType of - itVariable : Result := TFPExprVariable.CreateIdentifier(ID); - itFunctionCallBack : Result := TFPFunctionCallback.CreateFunction(ID, Args); - itFunctionHandler : Result := TFPFunctionEventHandler.CreateFunction(ID, Args); - end; + case ID.IdentifierType of + itVariable : Result := TsVariableExprNode.CreateIdentifier(ID); + itFunctionCallBack : Result := TsFunctionCallBackExprNode.CreateFunction(ID, Args); + itFunctionHandler : Result := TFPFunctionEventHandlerExprNode.CreateFunction(ID, Args); + end; end; GetToken; if TokenType = ttPercent then begin @@ -1551,43 +1671,6 @@ begin end; end; -procedure TsExpressionParser.SetExpression(const AValue: String); -begin - if FExpression = AValue then - exit; - FExpression := AValue; - FScanner.Source := AValue; - if Assigned(FExprNode) then - FreeAndNil(FExprNode); - if (FExpression <> '') then - begin - GetToken; - FExprNode := Level1; - if (TokenType <> ttEOF) then - ParserError(Format(SErrUnterminatedExpression, [Scanner.Pos, CurrentToken])); - FExprNode.Check; - end - else - FExprNode := nil; -end; - -procedure TsExpressionParser.CheckResultType(const Res: TsExpressionResult; - AType: TsResultType); inline; -begin - if (Res.ResultType <> AType) then - RaiseParserError(SErrInvalidResultType, [ResultTypeName(Res.ResultType)]); -end; - -class function TsExpressionParser.BuiltinExpressionManager: TsBuiltInExpressionManager; -begin - Result := BuiltinIdentifiers; -end; - -function TsExpressionParser.Evaluate: TsExpressionResult; -begin - EvaluateExpression(Result); -end; - function TsExpressionParser.ResultType: TsResultType; begin if not Assigned(FExprNode) then @@ -1595,33 +1678,299 @@ begin Result := FExprNode.NodeType;; end; - -{ --------------------------------------------------------------------- - TsExprIdentifierDefs - ---------------------------------------------------------------------} - -function TsExprIdentifierDefs.GetI(AIndex : Integer): TsExprIdentifierDef; +procedure TsExpressionParser.SetBuiltIns(const AValue: TsBuiltInExprCategories); begin - Result := TsExprIdentifierDef(Items[AIndex]); + if FBuiltIns = AValue then + exit; + FBuiltIns := AValue; + FDirty := true; end; -procedure TsExprIdentifierDefs.SetI(AIndex: Integer; - const AValue: TsExprIdentifierDef); +procedure TsExpressionParser.SetExpression(const AValue: String); begin - Items[AIndex] := AValue; + if FExpression = AValue then + exit; + FExpression := AValue; + if (AValue <> '') and (AValue[1] = '=') then + FScanner.Source := Copy(AValue, 2, Length(AValue)) + else + FScanner.Source := AValue; + FreeAndNil(FExprNode); + if (FExpression <> '') then + begin + GetToken; + FExprNode := Level1; + if (TokenType <> ttEOF) then + ParserError(Format(SErrUnterminatedExpression, [Scanner.Pos, CurrentToken])); + FExprNode.Check; + end; end; -procedure TsExprIdentifierDefs.Update(Item: TCollectionItem); +procedure TsExpressionParser.SetIdentifiers(const AValue: TsExprIdentifierDefs); begin - if Assigned(FParser) then - FParser.FDirty := true; + FIdentifiers.Assign(AValue) end; -function TsExprIdentifierDefs.IndexOfIdentifier(const AName: ShortString): Integer; +procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula); + + procedure CreateNodeFromRPN(var ANode: TsExprNode; var AIndex: Integer); + var + node: TsExprNode; + left: TsExprNode; + right: TsExprNode; + operand: TsExprNode; + fek: TFEKind; + r,c, r2,c2: Cardinal; + flags: TsRelFlags; + ID: TsExprIdentifierDef; + i, n: Integer; + args: TsExprArgumentArray; + begin + if AIndex < 0 then + exit; + + fek := AFormula[AIndex].ElementKind; + + case fek of + fekCell, fekCellRef: + begin + r := AFormula[AIndex].Row; + c := AFormula[AIndex].Col; + flags := AFormula[AIndex].RelFlags; + ANode := TsCellExprNode.Create(FWorksheet, r, c, flags); + dec(AIndex); + end; + fekCellRange: + begin + r := AFormula[AIndex].Row; + c := AFormula[AIndex].Col; + r2 := AFormula[AIndex].Row2; + c2 := AFormula[AIndex].Col2; + flags := AFormula[AIndex].RelFlags; + ANode := TsCellRangeExprNode.Create(FWorksheet, r, c, r2, c2, flags); + dec(AIndex); + end; + fekNum: + begin + ANode := TsConstExprNode.CreateFloat(AFormula[AIndex].DoubleValue); + dec(AIndex); + end; + fekInteger: + begin + ANode := TsConstExprNode.CreateInteger(AFormula[AIndex].IntValue); + dec(AIndex); + end; + fekString: + begin + ANode := TsConstExprNode.CreateString(AFormula[AIndex].StringValue); + dec(AIndex); + end; + fekBool: + begin + ANode := TsConstExprNode.CreateBoolean(AFormula[AIndex].DoubleValue <> 0.0); + dec(AIndex); + end; + fekErr: + begin + ANode := TsConstExprNode.CreateError(TsErrorValue(AFormula[AIndex].IntValue)); + dec(AIndex); + end; + + // unary operations + fekPercent, fekUMinus, fekUPlus, fekParen: + begin + dec(AIndex); + CreateNodeFromRPN(operand, AIndex); + case fek of + fekPercent : ANode := TsPercentExprNode.Create(operand); + fekUMinus : ANode := TsUMinusExprNode.Create(operand); + fekUPlus : ANode := TsUPlusExprNode.Create(operand); + fekParen : ANode := TsParenthesisExprNode.Create(operand); + end; + end; + + // binary operations + fekAdd, fekSub, fekMul, fekDiv, + fekPower, fekConcat, + fekEqual, fekNotEqual, + fekGreater, fekGreaterEqual, + fekLess, fekLessEqual: + begin + dec(AIndex); + CreateNodeFromRPN(right, AIndex); + CreateNodeFromRPN(left, AIndex); + CheckNodes(left, right); + case fek of + fekAdd : ANode := TsAddExprNode.Create(left, right); + fekSub : ANode := TsSubtractExprNode.Create(left, right); + fekMul : ANode := TsMultiplyExprNode.Create(left, right); + fekDiv : ANode := TsDivideExprNode.Create(left, right); + fekPower : ANode := TsPowerExprNode.Create(left, right); + fekConcat : ANode := tsConcatExprNode.Create(left, right); + fekEqual : ANode := TsEqualExprNode.Create(left, right); + fekNotEqual : ANode := TsNotEqualExprNode.Create(left, right); + fekGreater : ANode := TsGreaterExprNode.Create(left, right); + fekGreaterEqual: ANode := TsGreaterEqualExprNode.Create(left, right); + fekLess : ANode := TsLessExprNode.Create(left, right); + fekLessEqual : ANode := tsLessEqualExprNode.Create(left, right); + end; + end; + + // functions + fekFunc: + begin + ID := self.IdentifierByName(AFormula[AIndex].FuncName); + if ID = nil then + begin + ParserError(Format(SErrUnknownIdentifier,[AFormula[AIndex].FuncName])); + dec(AIndex); + end else + begin + if ID.HasFixedArgumentCount then + n := ID.ArgumentCount + else + n := AFormula[AIndex].ParamsNum; + dec(AIndex); + SetLength(args, n); + for i:=n-1 downto 0 do + CreateNodeFromRPN(args[i], AIndex); + case ID.IdentifierType of + itVariable : ANode := TsVariableExprNode.CreateIdentifier(ID); + itFunctionCallBack : ANode := TsFunctionCallBackExprNode.CreateFunction(ID, args); + itFunctionHandler : ANode := TFPFunctionEventHandlerExprNode.CreateFunction(ID, args); + end; + end; + end; + + end; //case + end; //begin + +var + index: Integer; + node: TsExprNode; begin - Result := Count-1; - while (Result >= 0) and (CompareText(GetI(Result).Name, AName) <> 0) do - dec(Result); + FExpression := ''; + FreeAndNil(FExprNode); + index := Length(AFormula)-1; + CreateNodeFromRPN(FExprNode, index); + if Assigned(FExprNode) then FExprNode.Check; +end; + +function TsExpressionParser.TokenType: TsTokenType; +begin + Result := FScanner.TokenType; +end; + + +{------------------------------------------------------------------------------} +{ TsSpreadsheetParser } +{------------------------------------------------------------------------------} + +constructor TsSpreadsheetParser.Create(AWorksheet: TsWorksheet); +begin + inherited Create(AWorksheet); + BuiltIns := AllBuiltIns; +end; + + +{------------------------------------------------------------------------------} +{ TsExprIdentifierDefs } +{------------------------------------------------------------------------------} + +function TsExprIdentifierDefs.AddBooleanVariable(const AName: ShortString; + AValue: Boolean): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := rtBoolean; + Result.FValue.ResBoolean := AValue; +end; + +function TsExprIdentifierDefs.AddDateTimeVariable(const AName: ShortString; + AValue: TDateTime): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := rtDateTime; + Result.FValue.ResDateTime := AValue; +end; + +function TsExprIdentifierDefs.AddFloatVariable(const AName: ShortString; + AValue: TsExprFloat): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := rtFloat; + Result.FValue.ResFloat := AValue; +end; + +function TsExprIdentifierDefs.AddFunction(const AName: ShortString; + const AResultType: Char; const AParamTypes: String; const AExcelCode: Integer; + ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.Name := AName; + Result.IdentifierType := itFunctionCallBack; + Result.ResultType := CharToResultType(AResultType); + Result.ExcelCode := AExcelCode; + Result.FOnGetValueCB := ACallBack; + if (Length(AParamTypes) > 0) and (AParamTypes[Length(AParamTypes)]='+') then + begin + Result.ParameterTypes := Copy(AParamTypes, 1, Length(AParamTypes)-1); + Result.VariableArgumentCount := true; + end else + Result.ParameterTypes := AParamTypes; +end; + +function TsExprIdentifierDefs.AddFunction(const AName: ShortString; + const AResultType: Char; const AParamTypes: String; const AExcelCode: Integer; + ACallBack: TsExprFunctionEvent): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.Name := AName; + Result.IdentifierType := itFunctionHandler; + Result.ResultType := CharToResultType(AResultType); + Result.ExcelCode := AExcelCode; + Result.FOnGetValue := ACallBack; + if (Length(AParamTypes) > 0) and (AParamTypes[Length(AParamTypes)]='+') then + begin + Result.ParameterTypes := Copy(AParamTypes, 1, Length(AParamTypes)-1); + Result.VariableArgumentCount := true; + end else + Result.ParameterTypes := AParamTypes; +end; + +function TsExprIdentifierDefs.AddIntegerVariable(const AName: ShortString; + AValue: Integer): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := rtInteger; + Result.FValue.ResInteger := AValue; +end; + +function TsExprIdentifierDefs.AddStringVariable(const AName: ShortString; + AValue: String): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := rtString; + Result.FValue.ResString := AValue; +end; + +function TsExprIdentifierDefs.AddVariable(const AName: ShortString; + AResultType: TsResultType; AValue: String): TsExprIdentifierDef; +begin + Result := Add as TsExprIdentifierDef; + Result.IdentifierType := itVariable; + Result.Name := AName; + Result.ResultType := AResultType; + Result.Value := AValue; end; function TsExprIdentifierDefs.FindIdentifier(const AName: ShortString @@ -1636,6 +1985,23 @@ begin Result := GetI(I); end; +function TsExprIdentifierDefs.GetI(AIndex : Integer): TsExprIdentifierDef; +begin + Result := TsExprIdentifierDef(Items[AIndex]); +end; + +function TsExprIdentifierDefs.IdentifierByExcelCode(const AExcelCode: Integer + ): TsExprIdentifierDef; +var + I: Integer; +begin + I := IndexOfIdentifier(AExcelCode); + if I = -1 then + Result := nil + else + Result := GetI(I); +end; + function TsExprIdentifierDefs.IdentifierByName(const AName: ShortString ): TsExprIdentifierDef; begin @@ -1644,88 +2010,39 @@ begin RaiseParserError(SErrUnknownIdentifier, [AName]); end; -function TsExprIdentifierDefs.AddVariable(const AName: ShortString; - AResultType: TsResultType; AValue: String): TsExprIdentifierDef; +function TsExprIdentifierDefs.IndexOfIdentifier(const AName: ShortString): Integer; begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := AResultType; - Result.Value := AValue; + Result := Count-1; + while (Result >= 0) and (CompareText(GetI(Result).Name, AName) <> 0) do + dec(Result); end; -function TsExprIdentifierDefs.AddBooleanVariable(const AName: ShortString; - AValue: Boolean): TsExprIdentifierDef; +function TsExprIdentifierDefs.IndexOfIdentifier(const AExcelCode: Integer): Integer; +var + ID: TsExprIdentifierDef; begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := rtBoolean; - Result.FValue.ResBoolean := AValue; + Result := Count-1; + while (Result >= 0) do begin + ID := GetI(Result); + if ID.ExcelCode = AExcelCode then exit; + dec(Result); + end; + { + while (Result >= 0) and (GetI(Result).ExcelCode = AExcelCode) do + dec(Result); + } end; -function TsExprIdentifierDefs.AddIntegerVariable(const AName: ShortString; - AValue: Integer): TsExprIdentifierDef; +procedure TsExprIdentifierDefs.SetI(AIndex: Integer; + const AValue: TsExprIdentifierDef); begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := rtInteger; - Result.FValue.ResInteger := AValue; + Items[AIndex] := AValue; end; -function TsExprIdentifierDefs.AddFloatVariable(const AName: ShortString; - AValue: TExprFloat): TsExprIdentifierDef; +procedure TsExprIdentifierDefs.Update(Item: TCollectionItem); begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := rtFloat; - Result.FValue.ResFloat := AValue; -end; - -function TsExprIdentifierDefs.AddStringVariable(const AName: ShortString; - AValue: String): TsExprIdentifierDef; -begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := rtString; - Result.FValue.ResString := AValue; -end; - -function TsExprIdentifierDefs.AddDateTimeVariable(const AName: ShortString; - AValue: TDateTime): TsExprIdentifierDef; -begin - Result := Add as TsExprIdentifierDef; - Result.IdentifierType := itVariable; - Result.Name := AName; - Result.ResultType := rtDateTime; - Result.FValue.ResDateTime := AValue; -end; - -function TsExprIdentifierDefs.AddFunction(const AName: ShortString; - const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionCallBack): TsExprIdentifierDef; -begin - Result := Add as TsExprIdentifierDef; - Result.Name := AName; - Result.IdentifierType := itFunctionCallBack; - Result.ParameterTypes := AParamTypes; - Result.ResultType := CharToResultType(AResultType); - Result.FOnGetValueCB := ACallBack; -end; - -function TsExprIdentifierDefs.AddFunction(const AName: ShortString; - const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionEvent): TsExprIdentifierDef; -begin - Result := Add as TsExprIdentifierDef; - Result.Name := AName; - Result.IdentifierType := itFunctionHandler; - Result.ParameterTypes := AParamTypes; - Result.ResultType := CharToResultType(AResultType); - Result.FOnGetValue := ACallBack; + if Assigned(FParser) then + FParser.FDirty := true; end; @@ -1733,6 +2050,174 @@ end; { TsExprIdentifierDef } {------------------------------------------------------------------------------} +function TsExprIdentifierDef.ArgumentCount: Integer; +begin + if FVariableArgumentCount then + Result := -Length(FArgumentTypes) + else + Result := Length(FArgumentTypes); +end; + +procedure TsExprIdentifierDef.Assign(Source: TPersistent); +var + EID: TsExprIdentifierDef; +begin + if (Source is TsExprIdentifierDef) then + begin + EID := Source as TsExprIdentifierDef; + FStringValue := EID.FStringValue; + FValue := EID.FValue; + FArgumentTypes := EID.FArgumentTypes; + FVariableArgumentCount := EID.FVariableArgumentCount; + FExcelCode := EID.ExcelCode; + FIDType := EID.FIDType; + FName := EID.FName; + FOnGetValue := EID.FOnGetValue; + FOnGetValueCB := EID.FOnGetValueCB; + end + else + inherited Assign(Source); +end; + +procedure TsExprIdentifierDef.CheckResultType(const AType: TsResultType); +begin + if (FValue.ResultType <> AType) then + RaiseParserError(SErrInvalidResultType, [ResultTypeName(AType)]) +end; + +procedure TsExprIdentifierDef.CheckVariable; +begin + if Identifiertype <> itVariable then + RaiseParserError(SErrNotVariable, [Name]); +end; + +function TsExprIdentifierDef.GetAsBoolean: Boolean; +begin + CheckResultType(rtBoolean); + CheckVariable; + Result := FValue.ResBoolean; +end; + +function TsExprIdentifierDef.GetAsDateTime: TDateTime; +begin + CheckResultType(rtDateTime); + CheckVariable; + Result := FValue.ResDateTime; +end; + +function TsExprIdentifierDef.GetAsFloat: TsExprFloat; +begin + CheckResultType(rtFloat); + CheckVariable; + Result := FValue.ResFloat; +end; + +function TsExprIdentifierDef.GetAsInteger: Int64; +begin + CheckResultType(rtInteger); + CheckVariable; + Result := FValue.ResInteger; +end; + +function TsExprIdentifierDef.GetAsString: String; +begin + CheckResultType(rtString); + CheckVariable; + Result := FValue.ResString; +end; + +function TsExprIdentifierDef.GetResultType: TsResultType; +begin + Result := FValue.ResultType; +end; + +function TsExprIdentifierDef.GetValue: String; +begin + case FValue.ResultType of + rtBoolean : if FValue.ResBoolean then + Result := 'True' + else + Result := 'False'; + rtInteger : Result := IntToStr(FValue.ResInteger); + rtFloat : Result := FloatToStr(FValue.ResFloat, ExprFormatSettings); + rtDateTime : Result := FormatDateTime('cccc', FValue.ResDateTime); + rtString : Result := FValue.ResString; + end; +end; + +{ Returns true if the epxression has a fixed number of arguments. } +function TsExprIdentifierDef.HasFixedArgumentCount: Boolean; +var + i: Integer; +begin + if FVariableArgumentCount then + Result := false + else + begin + for i:= 1 to Length(FArgumentTypes) do + if IsOptionalArgument(i) then + begin + Result := false; + exit; + end; + Result := true; + end; +end; + +{ Checks whether an argument is optional. Index number starts at 1. + Optional arguments are lower-case characters in the argument list. } +function TsExprIdentifierDef.IsOptionalArgument(AIndex: Integer): Boolean; +begin + Result := (AIndex <= Length(FArgumentTypes)) + and (UpCase(FArgumentTypes[AIndex]) <> FArgumentTypes[AIndex]); +end; + +procedure TsExprIdentifierDef.SetArgumentTypes(const AValue: String); +var + i: integer; +begin + if FArgumentTypes = AValue then + exit; + for i:=1 to Length(AValue) do + CharToResultType(AValue[i]); + FArgumentTypes := AValue; +end; + +procedure TsExprIdentifierDef.SetAsBoolean(const AValue: Boolean); +begin + CheckVariable; + CheckResultType(rtBoolean); + FValue.ResBoolean := AValue; +end; + +procedure TsExprIdentifierDef.SetAsDateTime(const AValue: TDateTime); +begin + CheckVariable; + CheckResultType(rtDateTime); + FValue.ResDateTime := AValue; +end; + +procedure TsExprIdentifierDef.SetAsFloat(const AValue: TsExprFloat); +begin + CheckVariable; + CheckResultType(rtFloat); + FValue.ResFloat := AValue; +end; + +procedure TsExprIdentifierDef.SetAsInteger(const AValue: Int64); +begin + CheckVariable; + CheckResultType(rtInteger); + FValue.ResInteger := AValue; +end; + +procedure TsExprIdentifierDef.SetAsString(const AValue: String); +begin + CheckVariable; + CheckResultType(rtString); + FValue.resString := AValue; +end; + procedure TsExprIdentifierDef.SetName(const AValue: ShortString); begin if FName = AValue then @@ -1773,142 +2258,6 @@ begin end end; -procedure TsExprIdentifierDef.CheckResultType(const AType: TsResultType); -begin - if FValue.ResultType <> AType then - RaiseParserError(SErrInvalidResultType, [ResultTypeName(AType)]) -end; - -procedure TsExprIdentifierDef.CheckVariable; -begin - if Identifiertype <> itVariable then - RaiseParserError(SErrNotVariable, [Name]); -end; - -function TsExprIdentifierDef.ArgumentCount: Integer; -begin - Result := Length(FArgumentTypes); -end; - -procedure TsExprIdentifierDef.Assign(Source: TPersistent); -var - EID: TsExprIdentifierDef; -begin - if (Source is TsExprIdentifierDef) then - begin - EID := Source as TsExprIdentifierDef; - FStringValue := EID.FStringValue; - FValue := EID.FValue; - FArgumentTypes := EID.FArgumentTypes; - FIDType := EID.FIDType; - FName := EID.FName; - FOnGetValue := EID.FOnGetValue; - FOnGetValueCB := EID.FOnGetValueCB; - end - else - inherited Assign(Source); -end; - -procedure TsExprIdentifierDef.SetArgumentTypes(const AValue: String); -var - i: integer; -begin - if FArgumentTypes = AValue then - exit; - for i:=1 to Length(AValue) do - CharToResultType(AValue[i]); - FArgumentTypes := AValue; -end; - -procedure TsExprIdentifierDef.SetAsBoolean(const AValue: Boolean); -begin - CheckVariable; - CheckResultType(rtBoolean); - FValue.ResBoolean := AValue; -end; - -procedure TsExprIdentifierDef.SetAsDateTime(const AValue: TDateTime); -begin - CheckVariable; - CheckResultType(rtDateTime); - FValue.ResDateTime := AValue; -end; - -procedure TsExprIdentifierDef.SetAsFloat(const AValue: TExprFloat); -begin - CheckVariable; - CheckResultType(rtFloat); - FValue.ResFloat := AValue; -end; - -procedure TsExprIdentifierDef.SetAsInteger(const AValue: Int64); -begin - CheckVariable; - CheckResultType(rtInteger); - FValue.ResInteger := AValue; -end; - -procedure TsExprIdentifierDef.SetAsString(const AValue: String); -begin - CheckVariable; - CheckResultType(rtString); - FValue.resString := AValue; -end; - -function TsExprIdentifierDef.GetValue: String; -begin - case FValue.ResultType of - rtBoolean : if FValue.ResBoolean then - Result := 'True' - else - Result := 'False'; - rtInteger : Result := IntToStr(FValue.ResInteger); - rtFloat : Result := FloatToStr(FValue.ResFloat); - rtDateTime : Result := FormatDateTime('cccc', FValue.ResDateTime); - rtString : Result := FValue.ResString; - end; -end; - -function TsExprIdentifierDef.GetResultType: TsResultType; -begin - Result := FValue.ResultType; -end; - -function TsExprIdentifierDef.GetAsFloat: TExprFloat; -begin - CheckResultType(rtFloat); - CheckVariable; - Result := FValue.ResFloat; -end; - -function TsExprIdentifierDef.GetAsBoolean: Boolean; -begin - CheckResultType(rtBoolean); - CheckVariable; - Result := FValue.ResBoolean; -end; - -function TsExprIdentifierDef.GetAsDateTime: TDateTime; -begin - CheckResultType(rtDateTime); - CheckVariable; - Result := FValue.ResDateTime; -end; - -function TsExprIdentifierDef.GetAsInteger: Int64; -begin - CheckResultType(rtInteger); - CheckVariable; - Result := FValue.ResInteger; -end; - -function TsExprIdentifierDef.GetAsString: String; -begin - CheckResultType(rtString); - CheckVariable; - Result := FValue.ResString; -end; - {------------------------------------------------------------------------------} { TsBuiltInExpressionManager } @@ -1926,33 +2275,6 @@ begin inherited Destroy; end; -function TsBuiltInExpressionManager.GetCount: Integer; -begin - Result := FDefs.Count; -end; - -function TsBuiltInExpressionManager.GetI(AIndex: Integer): TsBuiltInExprIdentifierDef; -begin - Result := TsBuiltInExprIdentifierDef(FDefs[Aindex]) -end; - -function TsBuiltInExpressionManager.IndexOfIdentifier(const AName: ShortString): Integer; -begin - Result := FDefs.IndexOfIdentifier(AName); -end; - -function TsBuiltInExpressionManager.FindIdentifier(const AName: ShortString - ): TsBuiltInExprIdentifierDef; -begin - Result := TsBuiltInExprIdentifierDef(FDefs.FindIdentifier(AName)); -end; - -function TsBuiltInExpressionManager.IdentifierByName(const AName: ShortString - ): TsBuiltInExprIdentifierDef; -begin - Result := TsBuiltInExprIdentifierDef(FDefs.IdentifierByName(AName)); -end; - function TsBuiltInExpressionManager.AddVariable(const ACategory: TsBuiltInExprCategory; const AName: ShortString; AResultType: TsResultType; AValue: String ): TsBuiltInExprIdentifierDef; @@ -1969,22 +2291,48 @@ begin Result.Category := ACategory; end; -function TsBuiltInExpressionManager.AddIntegerVariable( - const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: Integer +function TsBuiltInExpressionManager.AddDateTimeVariable( + const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: TDateTime ): TsBuiltInExprIdentifierDef; begin - Result := TsBuiltInExprIdentifierDef(FDefs.AddIntegerVariable(AName, AValue)); + Result := TsBuiltInExprIdentifierDef(FDefs.AddDateTimeVariable(AName, AValue)); Result.Category := ACategory; end; function TsBuiltInExpressionManager.AddFloatVariable( const ACategory: TsBuiltInExprCategory; const AName: ShortString; - AValue: TExprFloat): TsBuiltInExprIdentifierDef; + AValue: TsExprFloat): TsBuiltInExprIdentifierDef; begin Result := TsBuiltInExprIdentifierDef(FDefs.AddFloatVariable(AName, AValue)); Result.Category := ACategory; end; +function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory; + const AName: ShortString; const AResultType: Char; const AParamTypes: String; + const AExcelCode: Integer; ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef; +begin + Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType, + AParamTypes, AExcelCode, ACallBack)); + Result.Category := ACategory; +end; + +function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory; + const AName: ShortString; const AResultType: Char; const AParamTypes: String; + const AExcelCode: Integer; ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef; +begin + Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType, + AParamTypes, AExcelCode, ACallBack)); + Result.Category := ACategory; +end; + +function TsBuiltInExpressionManager.AddIntegerVariable( + const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: Integer + ): TsBuiltInExprIdentifierDef; +begin + Result := TsBuiltInExprIdentifierDef(FDefs.AddIntegerVariable(AName, AValue)); + Result.Category := ACategory; +end; + function TsBuiltInExpressionManager.AddStringVariable( const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: String ): TsBuiltInExprIdentifierDef; @@ -1993,28 +2341,37 @@ begin Result.Category := ACategory; end; -function TsBuiltInExpressionManager.AddDateTimeVariable( - const ACategory: TsBuiltInExprCategory; const AName: ShortString; AValue: TDateTime +function TsBuiltInExpressionManager.FindIdentifier(const AName: ShortString ): TsBuiltInExprIdentifierDef; begin - Result := TsBuiltInExprIdentifierDef(FDefs.AddDateTimeVariable(AName, AValue)); - Result.Category := ACategory; + Result := TsBuiltInExprIdentifierDef(FDefs.FindIdentifier(AName)); end; -function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory; - const AName: ShortString; const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionCallBack): TsBuiltInExprIdentifierDef; +function TsBuiltInExpressionManager.GetCount: Integer; begin - Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType, AParamTypes, ACallBack)); - Result.Category := ACategory; + Result := FDefs.Count; end; -function TsBuiltInExpressionManager.AddFunction(const ACategory: TsBuiltInExprCategory; - const AName: ShortString; const AResultType: Char; const AParamTypes: String; - ACallBack: TsExprFunctionEvent): TsBuiltInExprIdentifierDef; +function TsBuiltInExpressionManager.GetI(AIndex: Integer): TsBuiltInExprIdentifierDef; begin - Result := TsBuiltInExprIdentifierDef(FDefs.AddFunction(AName, AResultType, AParamTypes, ACallBack)); - Result.Category := ACategory; + Result := TsBuiltInExprIdentifierDef(FDefs[Aindex]) +end; + +function TsBuiltInExpressionManager.IdentifierByExcelCode(const AExcelCode: Integer + ): TsBuiltInExprIdentifierDef; +begin + Result := TsBuiltInExprIdentifierDef(FDefs.IdentifierByExcelCode(AExcelCode)); +end; + +function TsBuiltInExpressionManager.IdentifierByName(const AName: ShortString + ): TsBuiltInExprIdentifierDef; +begin + Result := TsBuiltInExprIdentifierDef(FDefs.IdentifierByName(AName)); +end; + +function TsBuiltInExpressionManager.IndexOfIdentifier(const AName: ShortString): Integer; +begin + Result := FDefs.IndexOfIdentifier(AName); end; @@ -2022,6 +2379,55 @@ end; { Various Nodes } {------------------------------------------------------------------------------} +{ TsExprNode } + +procedure TsExprNode.CheckNodeType(ANode: TsExprNode; Allowed: TsResultTypes); +var + S: String; + A: TsResultType; +begin + if (ANode = nil) then + RaiseParserError(SErrNoNodeToCheck); + if not (ANode.NodeType in Allowed) then + begin + S := ''; + for A := Low(TsResultType) to High(TsResultType) do + if A in Allowed then + begin + if S <> '' then + S := S + ','; + S := S + ResultTypeName(A); + end; + RaiseParserError(SInvalidNodeType, [ResultTypeName(ANode.NodeType), S, ANode.AsString]); + end; +end; + +function TsExprNode.NodeValue: TsExpressionResult; +begin + GetNodeValue(Result); +end; + + +{ TsUnaryOperationExprNode } + +constructor TsUnaryOperationExprNode.Create(AOperand: TsExprNode); +begin + FOperand := AOperand; +end; + +destructor TsUnaryOperationExprNode.Destroy; +begin + FreeAndNil(FOperand); + inherited Destroy; +end; + +procedure TsUnaryOperationExprNode.Check; +begin + if not Assigned(Operand) then + RaiseParserError(SErrNoOperand, [Self.ClassName]); +end; + + { TsBinaryOperationExprNode } constructor TsBinaryOperationExprNode.Create(ALeft, ARight: TsExprNode); @@ -2056,23 +2462,19 @@ begin end; -{ TsUnaryOperationExprNode } +{ TsBooleanOperationExprNode } -constructor TsUnaryOperationExprNode.Create(AOperand: TsExprNode); +procedure TsBooleanOperationExprNode.Check; begin - FOperand := AOperand; + inherited Check; + CheckNodeType(Left, [rtBoolean, rtCell, rtError, rtEmpty]); + CheckNodeType(Right, [rtBoolean, rtCell, rtError, rtEmpty]); + CheckSameNodeTypes; end; -destructor TsUnaryOperationExprNode.Destroy; +function TsBooleanOperationExprNode.NodeType: TsResultType; begin - FreeAndNil(FOperand); - inherited Destroy; -end; - -procedure TsUnaryOperationExprNode.Check; -begin - if not Assigned(Operand) then - RaiseParserError(SErrNoOperand, [Self.ClassName]); + Result := Left.NodeType; end; @@ -2096,7 +2498,7 @@ begin FValue.ResDateTime := AValue; end; -constructor TsConstExprNode.CreateFloat(AValue: TExprFloat); +constructor TsConstExprNode.CreateFloat(AValue: TsExprFloat); begin Inherited Create; FValue.ResultType := rtFloat; @@ -2109,6 +2511,12 @@ begin FValue.ResBoolean := AValue; end; +constructor TsConstExprNode.CreateError(AValue: TsErrorValue); +begin + FValue.ResultType := rtError; + FValue.ResError := AValue; +end; + procedure TsConstExprNode.Check; begin // Nothing to check; @@ -2131,7 +2539,7 @@ begin rtInteger : Result := IntToStr(FValue.ResInteger); rtDateTime : Result := '''' + FormatDateTime('cccc', FValue.ResDateTime) + ''''; // Probably wrong !!! rtBoolean : if FValue.ResBoolean then Result := 'TRUE' else Result := 'FALSE'; - rtFloat : Str(FValue.ResFloat, Result); + rtFloat : Result := FloatToStr(FValue.ResFloat, ExprFormatSettings); end; end; @@ -2139,7 +2547,7 @@ function TsConstExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin case NodeType of rtString : Result := RPNString(FValue.ResString, ANext); - rtInteger : Result := RPNNumber(FValue.ResInteger, ANext); + rtInteger : Result := RPNInteger(FValue.ResInteger, ANext); rtDateTime : Result := RPNNumber(FValue.ResDateTime, ANext); rtBoolean : Result := RPNBool(FValue.ResBoolean, ANext); rtFloat : Result := RPNNumber(FValue.ResFloat, ANext); @@ -2147,9 +2555,69 @@ begin end; -{ TsNegateExprNode } +{ TsUPlusExprNode } -function TsNegateExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +function TsUPlusExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +begin + Result := RPNFunc(fekUPlus, + Operand.AsRPNItem( + ANext + )); +end; + +function TsUPlusExprNode.AsString: String; +begin + Result := '+' + TrimLeft(Operand.AsString); +end; + +procedure TsUPlusExprNode.Check; +const + AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError]; +begin + inherited; + if not (Operand.NodeType in AllowedTokens) then + RaiseParserError(SErrNoUPlus, [ResultTypeName(Operand.NodeType), Operand.AsString]) +end; + +procedure TsUPlusExprNode.GetNodeValue(var Result: TsExpressionResult); +var + res: TsExpressionresult; + cell: PCell; +begin + Operand.GetNodeValue(Result); + case Result.ResultType of + rtInteger, rtFloat, rtError: + exit; + rtCell: + begin + cell := ArgToCell(Result); + if cell = nil then + Result := FloatResult(0.0) + else + if cell^.ContentType = cctNumber then + begin + if frac(cell^.NumberValue) = 0.0 then + Result := IntegerResult(trunc(cell^.NumberValue)) + else + Result := FloatResult(cell^.NumberValue); + end; + end; + rtEmpty: + Result := FloatResult(0.0); + else + Result := ErrorResult(errWrongType); + end; +end; + +function TsUPlusExprNode.NodeType: TsResultType; +begin + Result := Operand.NodeType; +end; + + +{ TsUMinusExprNode } + +function TsUMinusExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin Result := RPNFunc(fekUMinus, Operand.AsRPNItem( @@ -2157,28 +2625,52 @@ begin )); end; -function TsNegateExprNode.AsString: String; +function TsUMinusExprNode.AsString: String; begin Result := '-' + TrimLeft(Operand.AsString); end; -procedure TsNegateExprNode.Check; +procedure TsUMinusExprNode.Check; +const + AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError]; begin inherited; - if not (Operand.NodeType in [rtInteger, rtFloat]) then + if not (Operand.NodeType in AllowedTokens) then RaiseParserError(SErrNoNegation, [ResultTypeName(Operand.NodeType), Operand.AsString]) end; -procedure TsNegateExprNode.GetNodeValue(var Result: TsExpressionResult); +procedure TsUMinusExprNode.GetNodeValue(var Result: TsExpressionResult); +var + cell: PCell; begin Operand.GetNodeValue(Result); case Result.ResultType of - rtInteger : Result.ResInteger := -Result.ResInteger; - rtFloat : Result.ResFloat := -Result.ResFloat; + rtError: + exit; + rtFloat: + Result := FloatResult(-Result.ResFloat); + rtInteger: + Result := IntegerResult(-Result.ResInteger); + rtCell: + begin + cell := ArgToCell(Result); + if (cell <> nil) and (cell^.ContentType = cctNumber) then + begin + if frac(cell^.NumberValue) = 0.0 then + Result := IntegerResult(-trunc(cell^.NumberValue)) + else + Result := FloatResult(cell^.NumberValue); + end else + Result := FloatResult(0.0); + end; + rtEmpty: + Result := FloatResult(0.0); + else + Result := ErrorResult(errWrongType); end; end; -function TsNegateExprNode.NodeType: TsResultType; +function TsUMinusExprNode.NodeType: TsResultType; begin Result := Operand.NodeType; end; @@ -2200,9 +2692,11 @@ begin end; procedure TsPercentExprNode.Check; +const + AllowedTokens = [rtInteger, rtFloat, rtCell, rtEmpty, rtError]; begin inherited; - if not (Operand.NodeType in [rtInteger, rtFloat]) then + if not (Operand.NodeType in AllowedTokens) then RaiseParserError(SErrNoPercentOperation, [ResultTypeName(Operand.NodeType), Operand.AsString]) end; @@ -2210,10 +2704,13 @@ procedure TsPercentExprNode.GetNodeValue(var Result: TsExpressionResult); begin Operand.GetNodeValue(Result); case Result.ResultType of - rtInteger : Result.ResFloat := 0.01 * Result.ResInteger; - rtFloat : Result.ResFloat := 0.01 * Result.ResFloat; + rtError: + exit; + rtFloat, rtInteger, rtCell: + Result := FloatResult(ArgToFloat(Result)*0.01); + else + Result := ErrorResult(errWrongType); end; - Result.ResultType := Nodetype; end; function TsPercentExprNode.NodeType: TsResultType; @@ -2248,136 +2745,11 @@ begin end; -{ TsBinaryAndExprNode } - -function TsBinaryAndExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; -begin - Result := RPNFunc(fekAND, - Right.AsRPNItem( - Left.AsRPNItem( - ANext - ))); -end; - -function TsBinaryAndExprNode.AsString: string; -begin - Result := Left.AsString + ' and ' + Right.AsString; -end; - -procedure TsBooleanOperationExprNode.Check; -begin - inherited Check; - CheckNodeType(Left, [rtInteger, rtBoolean]); - CheckNodeType(Right, [rtInteger, rtBoolean]); - CheckSameNodeTypes; -end; - -function TsBooleanOperationExprNode.NodeType: TsResultType; -begin - Result := Left.NodeType; -end; - -procedure TsBinaryAndExprNode.GetNodeValue(var Result: TsExpressionResult); -var - RRes: TsExpressionResult; -begin - Left.GetNodeValue(Result); - Right.GetNodeValue(RRes); - case Result.ResultType of - rtBoolean : Result.resBoolean := Result.ResBoolean and RRes.ResBoolean; - rtInteger : Result.resInteger := Result.ResInteger and RRes.ResInteger; - end; -end; - - -{ TsExprNode } - -procedure TsExprNode.CheckNodeType(Anode: TsExprNode; Allowed: TsResultTypes); -var - S: String; - A: TsResultType; -begin - if (Anode = nil) then - RaiseParserError(SErrNoNodeToCheck); - if not (ANode.NodeType in Allowed) then - begin - S := ''; - for A := Low(TsResultType) to High(TsResultType) do - if A in Allowed then - begin - if S <> '' then - S := S + ','; - S := S + ResultTypeName(A); - end; - RaiseParserError(SInvalidNodeType, [ResultTypeName(ANode.NodeType), S, ANode.AsString]); - end; -end; - -function TsExprNode.NodeValue: TsExpressionResult; -begin - GetNodeValue(Result); -end; - - -{ TsBinaryOrExprNode } - -function TsBinaryOrExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; -begin - Result := RPNFunc(fekOR, - Right.AsRPNItem( - Left.AsRPNItem( - ANext - ))); -end; - -function TsBinaryOrExprNode.AsString: string; -begin - Result := Left.AsString + ' or ' + Right.AsString; -end; - -procedure TsBinaryOrExprNode.GetNodeValue(var Result: TsExpressionResult); -var - RRes: TsExpressionResult; -begin - Left.GetNodeValue(Result); - Right.GetNodeValue(RRes); - case Result.ResultType of - rtBoolean : Result.resBoolean := Result.ResBoolean or RRes.ResBoolean; - rtInteger : Result.resInteger := Result.ResInteger or RRes.ResInteger; - end; -end; - - -{ TsBinaryXorExprNode } - -function TsBinaryXorExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; -begin - RaiseParserError(SErrNoXOROperationRPN); -end; - -function TsBinaryXorExprNode.AsString: string; -begin - Result := Left.AsString + ' xor ' + Right.AsString; -end; - -procedure TsBinaryXorExprNode.GetNodeValue(var Result: TsExpressionResult); -var - RRes: TsExpressionResult; -begin - Left.GetNodeValue(Result); - Right.GetNodeValue(RRes); - case Result.ResultType of - rtBoolean : Result.resBoolean := Result.ResBoolean xor RRes.ResBoolean; - rtInteger : Result.resInteger := Result.ResInteger xor RRes.ResInteger; - end; -end; - - { TsNotExprNode } function TsNotExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin - Result := RPNFunc(fekNOT, + Result := RPNFunc('NOT', Operand.AsRPNItem( ANext )); @@ -2389,8 +2761,10 @@ begin end; procedure TsNotExprNode.Check; +const + AllowedTokens = [rtBoolean, rtEmpty, rtError]; begin - if not (Operand.NodeType in [rtInteger, rtBoolean]) then + if not (Operand.NodeType in AllowedTokens) then RaiseParserError(SErrNoNotOperation, [ResultTypeName(Operand.NodeType), Operand.AsString]) end; @@ -2398,8 +2772,8 @@ procedure TsNotExprNode.GetNodeValue(var Result: TsExpressionResult); begin Operand.GetNodeValue(Result); case Result.ResultType of - rtInteger : Result.ResInteger := not Result.ResInteger; rtBoolean : Result.ResBoolean := not Result.ResBoolean; + rtEmpty : Result := BooleanResult(true); end end; @@ -2409,66 +2783,6 @@ begin end; -{ TIfExprNode } - -constructor TIfExprNode.Create(ACondition, ALeft, ARight: TsExprNode); -begin - inherited Create(ALeft,ARight); - FCondition := ACondition; -end; - -destructor TIfExprNode.Destroy; -begin - FreeAndNil(FCondition); - inherited Destroy; -end; - -function TIfExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; -begin - if Left = nil then - Result := RPNFunc(fekIF, - Right.AsRPNItem( - ANext - )) - else - Result := RPNFunc(fekIF, - Right.AsRPNItem( - Left.AsRPNItem( - ANext - ))); -end; - -function TIfExprNode.AsString: string; -begin - if Right = nil then - Result := Format('IF(%s, %s)', [Condition.AsString, Left.AsString]) - else - Result := Format('IF(%s, %s, %s)',[Condition.AsString, Left.AsString, Right.AsString]); -end; - -procedure TIfExprNode.Check; -begin - inherited Check; - if (Condition.NodeType <> rtBoolean) then - RaiseParserError(SErrIFNeedsBoolean, [Condition.AsString]); - CheckSameNodeTypes; -end; - -procedure TIfExprNode.GetNodeValue(var Result: TsExpressionResult); -begin - FCondition.GetNodeValue(Result); - if Result.ResBoolean then - Left.GetNodeValue(Result) - else - Right.GetNodeValue(Result) -end; - -function TIfExprNode.NodeType: TsResultType; -begin - Result := Left.NodeType; -end; - - { TsBooleanResultExprNode } procedure TsBooleanResultExprNode.Check; @@ -2477,6 +2791,11 @@ begin CheckSameNodeTypes; end; +procedure TsBooleanResultExprNode.CheckSameNodeTypes; +begin + // Same node types are checked in GetNodevalue +end; + function TsBooleanResultExprNode.NodeType: TsResultType; begin Result := rtBoolean; @@ -2496,7 +2815,7 @@ end; function TsEqualExprNode.AsString: string; begin - Result := Left.AsString + ' = ' + Right.AsString; + Result := Left.AsString + '=' + Right.AsString; end; procedure TsEqualExprNode.GetNodeValue(var Result: TsExpressionResult); @@ -2505,14 +2824,34 @@ var begin Left.GetNodeValue(Result); Right.GetNodeValue(RRes); - case Result.ResultType of - rtBoolean : Result.resBoolean := Result.ResBoolean = RRes.ResBoolean; - rtInteger : Result.resBoolean := Result.ResInteger = RRes.ResInteger; - rtFloat : Result.resBoolean := Result.ResFloat = RRes.ResFLoat; - rtDateTime : Result.resBoolean := Result.ResDateTime = RRes.ResDateTime; - rtString : Result.resBoolean := Result.ResString = RRes.ResString; - end; - Result.ResultType := rtBoolean; + + if (Result.ResultType in [rtInteger, rtFloat, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtFloat, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToFloat(Result) = ArgToFloat(RRes)) + else + if (Result.ResultType in [rtString, rtCell, rtEmpty]) and + (RRes.ResultType in [rtString, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToString(Result) = ArgToString(RRes)) + else + if (Result.ResultType in [rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtDateTime, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToDateTime(Result) = ArgToDateTime(RRes)) + else + if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and + (RRes.ResultType in [rtBoolean, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToBoolean(Result) = ArgToBoolean(RRes)) + else + if (Result.ResultType = rtError) + then Result := ErrorResult(Result.ResError) + else + if (RRes.ResultType = rtError) + then Result := ErrorResult(RRes.ResError) + else + Result := BooleanResult(false); end; @@ -2529,7 +2868,7 @@ end; function TsNotEqualExprNode.AsString: string; begin - Result := Left.AsString + ' <> ' + Right.AsString; + Result := Left.AsString + '<>' + Right.AsString; end; procedure TsNotEqualExprNode.GetNodeValue(var Result: TsExpressionResult); @@ -2539,9 +2878,38 @@ begin end; -{ TsLessThanExprNode } +{ TsOrderingExprNode } -function TsLessThanExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +procedure TsOrderingExprNode.Check; +const + AllowedTypes = [rtBoolean, rtInteger, rtFloat, rtDateTime, rtString, rtEmpty, rtError, rtCell]; +begin + CheckNodeType(Left, AllowedTypes); + CheckNodeType(Right, AllowedTypes); + inherited Check; +end; + +procedure TsOrderingExprNode.CheckSameNodeTypes; +var + LT, RT: TsResultType; +begin + { + LT := Left.NodeType; + RT := Right.NodeType; + case LT of + rtFloat, rtInteger: + if (RT in [rtFloat, rtInteger]) or + ((Rt = rtCell) and (Right.Res + if (RT <> LT) then + RaiseParserError(SErrTypesDoNotMatch, [ResultTypeName(LT), ResultTypeName(RT), Left.AsString, Right.AsString]) + } +end; + + + +{ TsLessExprNode } + +function TsLessExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin Result := RPNFunc(fekLess, Right.AsRPNItem( @@ -2550,30 +2918,45 @@ begin ))); end; -function TsLessThanExprNode.AsString: string; +function TsLessExprNode.AsString: string; begin - Result := Left.AsString + ' < ' + Right.AsString; + Result := Left.AsString + '<' + Right.AsString; end; -procedure TsLessThanExprNode.GetNodeValue(var Result: TsExpressionResult); +procedure TsLessExprNode.GetNodeValue(var Result: TsExpressionResult); var RRes: TsExpressionResult; begin Left.GetNodeValue(Result); Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : Result.resBoolean := Result.ResInteger < RRes.ResInteger; - rtFloat : Result.resBoolean := Result.ResFloat < RRes.ResFLoat; - rtDateTime : Result.resBoolean := Result.ResDateTime < RRes.ResDateTime; - rtString : Result.resBoolean := Result.ResString < RRes.ResString; - end; - Result.ResultType := rtBoolean; + if (Result.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToFloat(Result) < ArgToFloat(RRes)) + else + if (Result.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) and + (RRes.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToString(Result) < ArgToString(RRes)) + else + if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and + (RRes.ResultType in [rtBoolean, rtCell, rtEmpty]) + then + Result := BooleanResult(ord(ArgToBoolean(Result)) < ord(ArgToBoolean(RRes))) + else + if (Result.ResultType = rtError) + then Result := ErrorResult(Result.ResError) + else + if (RRes.ResultType = rtError) + then Result := ErrorResult(RRes.ResError) + else + Result := ErrorResult(errWrongType); end; -{ TsGreaterThanExprNode } +{ TsGreaterExprNode } -function TsGreaterThanExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +function TsGreaterExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin Result := RPNFunc(fekGreater, Right.AsRPNItem( @@ -2582,30 +2965,39 @@ begin ))); end; -function TsGreaterThanExprNode.AsString: string; +function TsGreaterExprNode.AsString: string; begin - Result := Left.AsString + ' > ' + Right.AsString; + Result := Left.AsString + '>' + Right.AsString; end; -procedure TsGreaterThanExprNode.GetNodeValue(var Result: TsExpressionResult); +procedure TsGreaterExprNode.GetNodeValue(var Result: TsExpressionResult); var RRes: TsExpressionResult; begin Left.GetNodeValue(Result); Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : case Right.NodeType of - rtInteger : Result.resBoolean := Result.ResInteger > RRes.ResInteger; - rtFloat : Result.resBoolean := Result.ResInteger > RRes.ResFloat; - end; - rtFloat : case Right.NodeType of - rtInteger : Result.resBoolean := Result.ResFloat > RRes.ResInteger; - rtFloat : Result.resBoolean := Result.ResFloat > RRes.ResFLoat; - end; - rtDateTime : Result.resBoolean := Result.ResDateTime > RRes.ResDateTime; - rtString : Result.resBoolean := Result.ResString > RRes.ResString; - end; - Result.ResultType := rtBoolean; + if (Result.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToFloat(Result) > ArgToFloat(RRes)) + else + if (Result.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) and + (RRes.ResultType in [rtString, rtInteger, rtFloat, rtCell, rtEmpty]) + then + Result := BooleanResult(ArgToString(Result) > ArgToString(RRes)) + else + if (Result.ResultType in [rtBoolean, rtCell, rtEmpty]) and + (RRes.ResultType in [rtBoolean, rtCell, rtEmpty]) + then + Result := BooleanResult(ord(ArgToBoolean(Result)) > ord(ArgToBoolean(RRes))) + else + if (Result.ResultType = rtError) + then Result := ErrorResult(Result.ResError) + else + if (RRes.ResultType = rtError) + then Result := ErrorResult(RRes.ResError) + else + Result := ErrorResult(errWrongType); end; @@ -2622,7 +3014,7 @@ end; function TsGreaterEqualExprNode.AsString: string; begin - Result := Left.AsString + ' >= ' + Right.AsString; + Result := Left.AsString + '>=' + Right.AsString; end; procedure TsGreaterEqualExprNode.GetNodeValue(var Result: TsExpressionResult); @@ -2645,7 +3037,7 @@ end; function TsLessEqualExprNode.AsString: string; begin - Result := Left.AsString + ' <= ' + Right.AsString; + Result := Left.AsString + '<=' + Right.AsString; end; procedure TsLessEqualExprNode.GetNodeValue(var Result: TsExpressionResult); @@ -2655,42 +3047,8 @@ begin end; -{ TsOrderingExprNode } - -procedure TsOrderingExprNode.Check; -const - AllowedTypes =[rtInteger, rtfloat, rtDateTime, rtString]; -begin - CheckNodeType(Left, AllowedTypes); - CheckNodeType(Right, AllowedTypes); - inherited Check; -end; - - { TsConcatExprNode } -procedure TsConcatExprNode.Check; -begin - inherited Check; - CheckNodeType(Left, [rtString]); - CheckNodeType(Right, [rtString]); -end; - -procedure TsConcatExprNode.GetNodeValue(var Result: TsExpressionResult); -var - RRes : TsExpressionResult; -begin - Left.GetNodeValue(Result); - Right.GetNodeValue(RRes); - Result.ResString := Result.ResString + RRes.ResString; - Result.ResultType := rtString; -end; - -function TsConcatExprNode.NodeType: TsResultType; -begin - Result := rtString; -end; - function TsConcatExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin Result := RPNFunc(fekConcat, @@ -2704,12 +3062,46 @@ begin Result := Left.AsString + '&' + Right.AsString; end; +procedure TsConcatExprNode.Check; +begin + inherited Check; + CheckNodeType(Left, [rtString, rtCell, rtEmpty, rtError]); + CheckNodeType(Right, [rtString, rtCell, rtEmpty, rtError]); +end; + +procedure TsConcatExprNode.CheckSameNodeTypes; +begin + // Same node types are checked in GetNodevalue +end; + +procedure TsConcatExprNode.GetNodeValue(var Result: TsExpressionResult); +var + RRes : TsExpressionResult; +begin + Left.GetNodeValue(Result); + if (Result.ResultType = rtError) + then exit; + Right.GetNodeValue(RRes); + if (Result.ResultType in [rtString, rtCell]) and (RRes.ResultType in [rtString, rtCell]) + then Result := StringResult(ArgToString(Result) + ArgToString(RRes)) + else + if (RRes.ResultType = rtError) + then Result := ErrorResult(RRes.ResError) + else + Result := ErrorResult(errWrongType); +end; + +function TsConcatExprNode.NodeType: TsResultType; +begin + Result := rtString; +end; + { TsMathOperationExprNode } procedure TsMathOperationExprNode.Check; const - AllowedTypes = [rtInteger, rtfloat, rtDateTime]; + AllowedTypes = [rtInteger, rtFloat, rtDateTime, rtCell, rtEmpty, rtError]; begin inherited Check; CheckNodeType(Left, AllowedTypes); @@ -2717,6 +3109,11 @@ begin CheckSameNodeTypes; end; +procedure TsMathOperationExprNode.CheckSameNodeTypes; +begin + // Same node types are checked in GetNodevalue +end; + function TsMathOperationExprNode.NodeType: TsResultType; begin Result := Left.NodeType; @@ -2741,17 +3138,28 @@ end; procedure TsAddExprNode.GetNodeValue(var Result: TsExpressionResult); var - RRes : TsExpressionResult; + RRes: TsExpressionResult; begin Left.GetNodeValue(Result); + if Result.ResultType = rtError then + exit; + Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : Result.ResInteger := Result.ResInteger + RRes.ResInteger; -// rtString : Result.ResString := Result.ResString + RRes.ResString; - rtDateTime : Result.ResDateTime := Result.ResDateTime + RRes.ResDateTime; - rtFloat : Result.ResFloat := Result.ResFloat + RRes.ResFloat; + if RRes.ResultType = rtError then + begin + Result := ErrorResult(RRes.ResError); + exit; end; - Result.ResultType := NodeType; + + if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtCell, rtEmpty]) + then + Result := IntegerResult(ArgToInt(Result) + ArgToInt(RRes)) + else + if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) + then + Result := FloatResult(ArgToFloat(Result) + ArgToFloat(RRes)); end; @@ -2771,26 +3179,30 @@ begin Result := Left.AsString + '-' + Right.asString; end; -procedure TsSubtractExprNode.Check; -const - AllowedTypes =[rtInteger, rtfloat, rtDateTime]; -begin - CheckNodeType(Left, AllowedTypes); - CheckNodeType(Right, AllowedTypes); - inherited Check; -end; - procedure TsSubtractExprNode.GetNodeValue(var Result: TsExpressionResult); var RRes: TsExpressionResult; begin Left.GetNodeValue(Result); + if Result.ResultType = rtError then + exit; + Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : Result.ResInteger := Result.ResInteger - RRes.ResInteger; - rtDateTime : Result.ResDateTime := Result.ResDateTime - RRes.ResDateTime; - rtFloat : Result.ResFLoat := Result.ResFLoat - RRes.ResFLoat; + if RRes.ResultType = rtError then + begin + Result := ErrorResult(RRes.ResError); + exit; end; + + if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtCell, rtEmpty]) + then + Result := IntegerResult(ArgToInt(Result) - ArgToInt(RRes)) + else + if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) + then + Result := FloatResult(ArgToFloat(Result) - ArgToFloat(RRes)); end; @@ -2810,25 +3222,30 @@ begin Result := Left.AsString + '*' + Right.AsString; end; -procedure TsMultiplyExprNode.Check; -const - AllowedTypes = [rtInteger, rtFloat]; -begin - CheckNodeType(Left, AllowedTypes); - CheckNodeType(Right, AllowedTypes); - inherited; -end; - procedure TsMultiplyExprNode.GetNodeValue(var Result: TsExpressionResult); var RRes: TsExpressionResult; begin Left.GetNodeValue(Result); + if Result.ResultType = rtError then + exit; + Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : Result.ResInteger := Result.ResInteger * RRes.ResInteger; - rtFloat : Result.ResFloat := Result.ResFloat * RRes.ResFloat; + if RRes.ResultType = rtError then + begin + Result := ErrorResult(RRes.ResError); + exit; end; + + if (Result.ResultType in [rtInteger, rtCell, rtEmpty]) and + (RRes.ResultType in [rtInteger, rtCell, rtEmpty]) + then + Result := IntegerResult(ArgToInt(Result) * ArgToInt(RRes)) + else + if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) + then + Result := FloatResult(ArgToFloat(Result) * ArgToFloat(RRes)); end; @@ -2848,26 +3265,31 @@ begin Result := Left.AsString + '/' + Right.asString; end; -procedure TsDivideExprNode.Check; -const - AllowedTypes =[rtInteger, rtFloat]; -begin - CheckNodeType(Left, AllowedTypes); - CheckNodeType(Right, AllowedTypes); - inherited Check; -end; - procedure TsDivideExprNode.GetNodeValue(var Result: TsExpressionResult); var RRes: TsExpressionResult; + y: TsExprFloat; begin Left.GetNodeValue(Result); + if Result.ResultType = rtError then + exit; + Right.GetNodeValue(RRes); - case Result.ResultType of - rtInteger : Result.ResFloat := Result.ResInteger / RRes.ResInteger; - rtFloat : Result.ResFloat := Result.ResFloat / RRes.ResFloat; + if RRes.ResultType = rtError then + begin + Result := ErrorResult(RRes.ResError); + exit; + end; + + if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) + then begin + y := ArgToFloat(RRes); + if y = 0.0 then + Result := ErrorResult(errDivideByZero) + else + Result := FloatResult(ArgToFloat(Result) / y); end; - Result.ResultType := rtFloat; end; function TsDivideExprNode.NodeType: TsResultType; @@ -2876,6 +3298,54 @@ begin end; +{ TsPowerExprNode } + +function TsPowerExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +begin + Result := RPNFunc(fekPower, + Right.AsRPNItem( + Left.AsRPNItem( + ANext + ))); +end; + +function TsPowerExprNode.AsString: string; +begin + Result := Left.AsString + '^' + Right.AsString; +end; + +procedure TsPowerExprNode.GetNodeValue(var Result: TsExpressionResult); +var + RRes: TsExpressionResult; + ex: TsExprFloat; +begin + Left.GetNodeValue(Result); + if Result.ResultType = rtError then + exit; + + Right.GetNodeValue(RRes); + if RRes.ResultType = rtError then + begin + Result := ErrorResult(RRes.ResError); + exit; + end; + + if (Result.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) and + (RRes.ResultType in [rtFloat, rtInteger, rtDateTime, rtCell, rtEmpty]) + then + try + Result := FloatResult(Power(ArgToFloat(Result), ArgToFloat(RRes))); + except + on E: EInvalidArgument do Result := ErrorResult(errOverflow); + end; +end; + +function TsPowerExprNode.NodeType: TsResultType; +begin + Result := rtFloat; +end; + + { TsConvertExprNode } function TsConvertExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; @@ -2894,14 +3364,14 @@ end; procedure TsConvertToIntExprNode.Check; begin inherited Check; - CheckNodeType(Operand, [rtInteger]) + CheckNodeType(Operand, [rtInteger, rtCell]) end; procedure TsIntToFloatExprNode.GetNodeValue(var Result: TsExpressionResult); begin Operand.GetNodeValue(Result); - Result.ResFloat := Result.ResInteger; - Result.ResultType := rtFloat; + if Result.ResultType in [rtInteger, rtCell] then + Result := FloatResult(ArgToInt(Result)); end; function TsIntToFloatExprNode.NodeType: TsResultType; @@ -2920,16 +3390,17 @@ end; procedure TsIntToDateTimeExprNode.GetNodeValue(var Result: TsExpressionResult); begin Operand.GetnodeValue(Result); - Result.ResDateTime := Result.ResInteger; - Result.ResultType := rtDateTime; + if Result.ResultType in [rtInteger, rtCell] then + Result := DateTimeResult(ArgToInt(Result)); end; + { TsFloatToDateTimeExprNode } procedure TsFloatToDateTimeExprNode.Check; begin inherited Check; - CheckNodeType(Operand, [rtFloat]); + CheckNodeType(Operand, [rtFloat, rtCell]); end; function TsFloatToDateTimeExprNode.NodeType: TsResultType; @@ -2940,8 +3411,8 @@ end; procedure TsFloatToDateTimeExprNode.GetNodeValue(var Result: TsExpressionResult); begin Operand.GetNodeValue(Result); - Result.ResDateTime := Result.ResFloat; - Result.ResultType := rtDateTime; + if Result.ResultType in [rtFloat, rtCell] then + Result := DateTimeResult(ArgToFloat(Result)); end; @@ -2967,35 +3438,35 @@ begin end; -{ TFPExprVariable } +{ TsVariableExprNode } -procedure TFPExprVariable.Check; +procedure TsVariableExprNode.Check; begin // Do nothing; end; -function TFPExprVariable.AsRPNItem(ANext: PRPNItem): PRPNItem; +function TsVariableExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin RaiseParserError('Cannot handle variables for RPN, so far.'); end; -function TFPExprVariable.AsString: string; +function TsVariableExprNode.AsString: string; begin Result := FID.Name; end; -{ TFPExprFunction } +{ TsFunctionExprNode } -constructor TFPExprFunction.CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); +constructor TsFunctionExprNode.CreateFunction(AID: TsExprIdentifierDef; + const Args: TsExprArgumentArray); begin inherited CreateIdentifier(AID); FArgumentNodes := Args; SetLength(FArgumentParams, Length(Args)); end; -destructor TFPExprFunction.Destroy; +destructor TsFunctionExprNode.Destroy; var i: Integer; begin @@ -3004,13 +3475,22 @@ begin inherited Destroy; end; -function TFPExprFunction.AsRPNItem(ANext: PRPNItem): PRPNItem; +function TsFunctionExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +var + i, n: Integer; begin + if FID.HasFixedArgumentCount then + n := FID.ArgumentCount + else + n := Length(FArgumentNodes); Result := ANext; -// RaiseParserError('Cannot handle functions for RPN, so far.'); +// for i:=Length(FArgumentNodes)-1 downto 0 do + for i:=0 to High(FArgumentNodes) do + Result := FArgumentNodes[i].AsRPNItem(Result); + Result := RPNFunc(FID.Name, n, Result); end; -function TFPExprFunction.AsString: String; +function TsFunctionExprNode.AsString: String; var S : String; i : Integer; @@ -3022,78 +3502,109 @@ begin S := S + ','; S := S + FArgumentNodes[i].AsString; end; - if (S <> '') then - S := '(' + S + ')'; + S := '(' + S + ')'; Result := FID.Name + S; end; -procedure TFPExprFunction.CalcParams; +procedure TsFunctionExprNode.CalcParams; var i : Integer; begin for i := 0 to Length(FArgumentParams)-1 do + { + case FArgumentParams[i].ResultType of + rtEmpty: FID.FValue.ResultType := rtEmpty; + rtError: if FID.FValue.ResultType <> rtError then + begin + FID.FValue.ResultType := rtError; + FID.FValue.ResError := FArgumentParams[i].ResError; + end; + else FArgumentNodes[i].GetNodeValue(FArgumentParams[i]); + end; + } FArgumentNodes[i].GetNodeValue(FArgumentParams[i]); end; -procedure TFPExprFunction.Check; +procedure TsFunctionExprNode.Check; var i: Integer; - rtp, rta: TsResultType; + rta, // parameter types passed to the function + rtp: TsResultType; // Parameter types expected from the parameter symbol + lastrtp: TsResultType; begin if Length(FArgumentNodes) <> FID.ArgumentCount then - RaiseParserError(ErrInvalidArgumentCount, [FID.Name]); + begin + for i:=Length(FArgumentNodes)+1 to FID.ArgumentCount do + if not FID.IsOptionalArgument(i) then + RaiseParserError(ErrInvalidArgumentCount, [FID.Name]); + end; + for i := 0 to Length(FArgumentNodes)-1 do begin - rtp := CharToResultType(FID.ParameterTypes[i+1]); rta := FArgumentNodes[i].NodeType; - if (rtp <> rta) then + + // A "cell" can return any type --> no type conversion required here. + if rta = rtCell then + Continue; + + if i+1 <= Length(FID.ParameterTypes) then begin - // Automatically convert integers to floats in functions that return - // a float + rtp := CharToResultType(FID.ParameterTypes[i+1]); + lastrtp := rtp; + end else + rtp := lastrtp; + if rtp = rtAny then + Continue; + if (rtp <> rta) and not (rta in [rtCellRange, rtError, rtEmpty]) then + begin + // Automatically convert integers to floats in functions that return a float if (rta = rtInteger) and (rtp = rtFloat) then begin FArgumentNodes[i] := TsIntToFloatExprNode(FArgumentNodes[i]); exit; end; - RaiseParserError(SErrInvalidArgumentType, [I+1, ResultTypeName(rtp), ResultTypeName(rta)]) + // Floats are truncated automatically to integers - that's what Excel does. + if (rta = rtFloat) and (rtp = rtInteger) then + exit; + RaiseParserError(SErrInvalidArgumentType, [i+1, ResultTypeName(rtp), ResultTypeName(rta)]) end; end; end; -{ TFPFunctionCallBack } +{ TsFunctionCallBackExprNode } -constructor TFPFunctionCallBack.CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); +constructor TsFunctionCallBackExprNode.CreateFunction(AID: TsExprIdentifierDef; + const Args: TsExprArgumentArray); begin inherited; FCallBack := AID.OnGetFunctionValueCallBack; end; -procedure TFPFunctionCallBack.GetNodeValue(var Result: TsExpressionResult); +procedure TsFunctionCallBackExprNode.GetNodeValue(var Result: TsExpressionResult); begin + Result.ResultType := NodeType; // was at end! if Length(FArgumentParams) > 0 then CalcParams; FCallBack(Result, FArgumentParams); - Result.ResultType := NodeType; end; -{ TFPFunctionEventHandler } +{ TFPFunctionEventHandlerExprNode } -constructor TFPFunctionEventHandler.CreateFunction(AID: TsExprIdentifierDef; - const Args: TExprArgumentArray); +constructor TFPFunctionEventHandlerExprNode.CreateFunction(AID: TsExprIdentifierDef; + const Args: TsExprArgumentArray); begin inherited; FCallBack := AID.OnGetFunctionValue; end; -procedure TFPFunctionEventHandler.GetNodeValue(var Result: TsExpressionResult); +procedure TFPFunctionEventHandlerExprNode.GetNodeValue(var Result: TsExpressionResult); begin - if Length(FArgumentParams)>0 then + Result.ResultType := NodeType; // was at end + if Length(FArgumentParams) > 0 then CalcParams; FCallBack(Result, FArgumentParams); - Result.ResultType := NodeType; end; @@ -3105,545 +3616,1873 @@ var flags: TsRelFlags; begin ParseCellString(ACellString, r, c, flags); - Create(AWorksheet, AWorksheet.FindCell(r, c), flags); + Create(AWorksheet, r, c, flags); end; -constructor TsCellExprNode.Create(AWorksheet: TsWorksheet; ACell: PCell; AFlags: TsRelFlags); +constructor TsCellExprNode.Create(AWorksheet: TsWorksheet; ARow,ACol: Cardinal; + AFlags: TsRelFlags); begin - FCell := ACell; + FWorksheet := AWorksheet; + FRow := ARow; + FCol := ACol; FFlags := AFlags; + FCell := AWorksheet.FindCell(FRow, FCol); end; function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; begin - Result := RPNCellValue(FCell^.Row, FCell^.Col, FFlags, ANext); + if FIsRef then + Result := RPNCellRef(FRow, FCol, FFlags, ANext) + else + Result := RPNCellValue(FRow, FCol, FFlags, ANext); end; function TsCellExprNode.AsString: string; begin - Result := GetCellString(FCell^.Row, FCell^.Col, FFlags); -end; - -procedure TsCellExprNode.Check; -begin - if not Assigned(FCell) then - RaiseParserError(SErrNoCellOperand); - if (FCell^.ContentType = cctError) and (FCell^.ErrorValue <> errOK) then - raise EExprParser.CreateFmt(SErrCellError, [AsString]); + Result := GetCellString(FRow, FCol, FFlags); end; procedure TsCellExprNode.GetNodeValue(var Result: TsExpressionResult); begin - case FCell^.ContentType of - cctNumber: - Result.ResFloat := FCell^.NumberValue; - cctDateTime: - Result.ResDateTime := FCell^.DateTimeValue; - cctUTF8String: - Result.ResString := FCell^.UTF8StringValue; - cctBool: - Result.ResBoolean := FCell^.BoolValue; - cctEmpty: - Result.ResString := ''; - end; - Result.ResultType := NodeType; + if (FCell <> nil) and HasFormula(FCell) then + case FCell^.CalcState of + csNotCalculated: + Worksheet.CalcFormula(FCell); + csCalculating: + raise Exception.Create(SErrCircularReference); + end; + + Result.ResultType := rtCell; + Result.ResRow := FRow; + Result.ResCol := FCol; + Result.Worksheet := FWorksheet; +end; + +procedure TsCellExprNode.Check; +begin + // Nothing to check; end; function TsCellExprNode.NodeType: TsResultType; begin - case FCell^.ContentType of - cctNumber: - Result := rtFloat; - cctDateTime: - Result := rtDateTime; - cctUTF8String: - Result := rtString; - cctBool: - Result := rtBoolean; - cctEmpty: - Result := rtString; + Result := rtCell; + { + if FIsRef then + Result := rtCell + else + begin + Result := rtEmpty; + if FCell <> nil then + case FCell^.ContentType of + cctNumber: + if frac(FCell^.NumberValue) = 0 then + Result := rtInteger + else + Result := rtFloat; + cctDateTime: + Result := rtDateTime; + cctUTF8String: + Result := rtString; + cctBool: + Result := rtBoolean; + cctError: + Result := rtError; + end; + end; + } +end; + + +{ TsCellRangeExprNode } + +constructor TsCellRangeExprNode.Create(AWorksheet: TsWorksheet; ACellRangeString: String); +var + r1, c1, r2, c2: Cardinal; + flags: TsRelFlags; +begin + if pos(':', ACellRangeString) = 0 then + begin + ParseCellString(ACellRangeString, r1, c1, flags); + if rfRelRow in flags then Include(flags, rfRelRow2); + if rfRelCol in flags then Include(flags, rfRelCol2); + Create(AWorksheet, r1, c1, r1, c1, flags); + end else + begin + ParseCellRangeString(ACellRangeString, r1, c1, r2, c2, flags); + Create(AWorksheet, r1, c1, r2, c2, flags); + end; +end; + +constructor TsCellRangeExprNode.Create(AWorksheet: TsWorksheet; + ARow1,ACol1,ARow2,ACol2: Cardinal; AFlags: TsRelFlags); +begin + FWorksheet := AWorksheet; + FRow1 := ARow1; + FCol1 := ACol1; + FRow2 := ARow2; + FCol2 := ACol2; + FFlags := AFlags; +end; + +function TsCellRangeExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem; +begin + { + if (FRow1 = FRow2) and (FCol1 = FCol2) then + Result := RPNCellRef(FRow1, FCol1, FFlags, ANext) + else + } + Result := RPNCellRange(FRow1, FCol1, FRow2, FCol2, FFlags, ANext); +end; + +function TsCellRangeExprNode.AsString: string; +begin + if (FRow1 = FRow2) and (FCol1 = FCol2) then + Result := GetCellString(FRow1, FCol1, FFlags) + else + Result := GetCellRangeString(FRow1, FCol1, FRow2, FCol2, FFlags); +end; + +procedure TsCellRangeExprNode.Check; +begin + // Nothing to check; +end; + +procedure TsCellRangeExprNode.GetNodeValue(var Result: TsExpressionResult); +var + r,c: Cardinal; + cell: PCell; +begin + for r := FRow1 to FRow2 do + for c := FCol1 to FCol2 do + begin + cell := FWorksheet.FindCell(r, c); + if HasFormula(cell) then + case cell^.CalcState of + csNotCalculated: FWorksheet.CalcFormula(cell); + csCalculating : raise Exception.Create(SErrCircularReference); + end; + end; + + Result.ResultType := rtCellRange; + Result.ResCellRange.Row1 := FRow1; + Result.ResCellRange.Col1 := FCol1; + Result.ResCellRange.Row2 := FRow2; + Result.ResCellRange.Col2 := FCol2; + Result.Worksheet := FWorksheet; +end; + +function TsCellRangeExprNode.NodeType: TsResultType; +begin + Result := rtCellRange; +end; + + +{------------------------------------------------------------------------------} +{ Conversion of arguments to simple data types } +{------------------------------------------------------------------------------} + +function ArgToBoolean(Arg: TsExpressionResult): Boolean; +var + cell: PCell; +begin + Result := false; + if Arg.ResultType = rtBoolean then + Result := Arg.ResBoolean + else + if (Arg.ResultType = rtCell) then begin + cell := ArgToCell(Arg); + if (cell <> nil) and (cell^.ContentType = cctBool) then + Result := cell^.BoolValue; + end; +end; + +function ArgToCell(Arg: TsExpressionResult): PCell; +begin + if Arg.ResultType = rtCell then + Result := Arg.Worksheet.FindCell(Arg.ResRow, Arg.ResCol) + else + Result := nil; +end; + +function ArgToInt(Arg: TsExpressionResult): Integer; +var + cell: PCell; +begin + Result := 0; + if Arg.ResultType = rtInteger then + result := Arg.ResInteger + else + if Arg.ResultType = rtFloat then + result := trunc(Arg.ResFloat) + else + if Arg.ResultType = rtDateTime then + result := trunc(Arg.ResDateTime) + else + if (Arg.ResultType = rtCell) then + begin + cell := ArgToCell(Arg); + if Assigned(cell) and (cell^.ContentType = cctNumber) then + result := trunc(cell^.NumberValue); + end; +end; + +function ArgToFloat(Arg: TsExpressionResult): TsExprFloat; +// Utility function for the built-in math functions. Accepts also integers +// in place of the floating point arguments. To be called in builtins or +// user-defined callbacks having float results. +var + cell: PCell; +begin + Result := 0.0; + if Arg.ResultType = rtInteger then + result := Arg.ResInteger + else + if Arg.ResultType = rtDateTime then + result := Arg.ResDateTime + else + if Arg.ResultType = rtFloat then + result := Arg.ResFloat + else + if (Arg.ResultType = rtCell) then + begin + cell := ArgToCell(Arg); + if Assigned(cell) then + case cell^.ContentType of + cctNumber : Result := cell^.NumberValue; + cctDateTime : Result := cell^.DateTimeValue; + end; + end; +end; + +function ArgToDateTime(Arg: TsExpressionResult): TDateTime; +var + cell: PCell; +begin + Result := 0.0; + if Arg.ResultType = rtDateTime then + result := Arg.ResDateTime + else + if Arg.ResultType = rtInteger then + Result := Arg.ResInteger + else + if Arg.ResultType = rtFloat then + Result := Arg.ResFloat + else + if (Arg.ResultType = rtCell) then + begin + cell := ArgToCell(Arg); + if Assigned(cell) and (cell^.ContentType = cctDateTime) then + Result := cell^.DateTimeValue; + end; +end; + +function ArgToString(Arg: TsExpressionResult): String; +var + cell: PCell; +begin + Result := ''; + case Arg.ResultType of + rtString : result := Arg.ResString; + rtInteger : Result := IntToStr(Arg.ResInteger); + rtFloat : Result := FloatToStr(Arg.ResFloat); + rtCell : begin + cell := ArgToCell(Arg); + if Assigned(cell) and (cell^.ContentType = cctUTF8String) then + Result := cell^.UTF8Stringvalue; + end; end; end; -{ --------------------------------------------------------------------- - Standard Builtins support - ---------------------------------------------------------------------} +{------------------------------------------------------------------------------} +{ Conversion simple data types to ExpressionResults } +{------------------------------------------------------------------------------} -{ Template for builtin. - -Procedure MyCallback (Var Result : TsExpressionResult; Const Args : TExprParameterArray); +function BooleanResult(AValue: Boolean): TsExpressionResult; begin + Result.ResultType := rtBoolean; + Result.ResBoolean := AValue; end; -} -function ArgToFloat(Arg: TsExpressionResult): TExprFloat; -// Utility function for the built-in math functions. Accepts also integers -// in place of the floating point arguments. To be called in builtins or -// user-defined callbacks having float results. +function DateTimeResult(AValue: TDateTime): TsExpressionResult; begin - if Arg.ResultType = rtInteger then - result := Arg.resInteger + Result.ResultType := rtDateTime; + Result.ResDateTime := AValue; +end; + +function EmptyResult: TsExpressionResult; +begin + Result.ResultType := rtEmpty; +end; + +function ErrorResult(const AValue: TsErrorValue): TsExpressionResult; +begin + Result.ResultType := rtError; + Result.ResError := AValue; +end; + +function FloatResult(const AValue: TsExprFloat): TsExpressionResult; +begin + Result.ResultType := rtFloat; + Result.ResFloat := AValue; +end; + +function IntegerResult(const AValue: Integer): TsExpressionResult; +begin + Result.ResultType := rtInteger; + Result.ResInteger := AValue; +end; + +function StringResult(const AValue: string): TsExpressionResult; +begin + Result.ResultType := rtString; + Result.ResString := AValue; +end; + + +{------------------------------------------------------------------------------} +{ Standard Builtins support } +{------------------------------------------------------------------------------} + +// Builtin math functions + +procedure fpsABS(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(abs(ArgToFloat(Args[0]))); +end; + +procedure fpsACOS(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if InRange(x, -1, +1) then + Result := FloatResult(arccos(x)) else - result := Arg.resFloat; + Result := ErrorResult(errOverflow); // #NUM! end; - -// Math builtins - -procedure BuiltInCos(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsACOSH(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; begin - Result.ResFloat := cos(ArgToFloat(Args[0])); + x := ArgToFloat(Args[0]); + if x >= 1 then + Result := FloatResult(arccosh(ArgToFloat(Args[0]))) + else + Result := ErrorResult(errOverflow); end; -procedure BuiltInSin(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsASIN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; begin - Result.ResFloat := sin(ArgToFloat(Args[0])); + x := ArgToFloat(Args[0]); + if InRange(x, -1, +1) then + Result := FloatResult(arcsin(ArgToFloat(Args[0]))) + else + Result := ErrorResult(errOverflow); end; -procedure BuiltInArcTan(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsASINH(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - Result.ResFloat := arctan(ArgToFloat(Args[0])); + Result := FloatResult(arcsinh(ArgToFloat(Args[0]))); end; -procedure BuiltInAbs(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsATAN(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - Result.ResFloat := abs(ArgToFloat(Args[0])); + Result := FloatResult(arctan(ArgToFloat(Args[0]))); end; -procedure BuiltInSqr(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsATANH(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; begin - Result.ResFloat := sqr(ArgToFloat(Args[0])); + x := ArgToFloat(Args[0]); + if (x > -1) and (x < +1) then + Result := FloatResult(arctanh(ArgToFloat(Args[0]))) + else + Result := ErrorResult(errOverflow); // #NUM! end; -procedure BuiltInSqrt(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsCOS(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - Result.ResFloat := sqrt(ArgToFloat(Args[0])); + Result := FloatResult(cos(ArgToFloat(Args[0]))); end; -procedure BuiltInExp(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsCOSH(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - Result.ResFloat := exp(ArgToFloat(Args[0])); + Result := FloatResult(cosh(ArgToFloat(Args[0]))); end; -procedure BuiltInLn(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsDEGREES(var Result: TsExpressionResult; const Args: TsExprParameterArray); begin - Result.ResFloat := ln(ArgToFloat(Args[0])); + Result := FloatResult(RadToDeg(ArgToFloat(Args[0]))); end; +procedure fpsEXP(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(exp(ArgToFloat(Args[0]))); +end; + +procedure fpsINT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(floor(ArgToFloat(Args[0]))); +end; + +procedure fpsLN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if x > 0 then + Result := FloatResult(ln(x)) + else + Result := ErrorResult(errOverflow); // #NUM! +end; + +procedure fpsLOG(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// LOG( number [, base] ) - base is 10 if omitted. +var + x: TsExprFloat; + base: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if x <= 0 then begin + Result := ErrorResult(errOverflow); // #NUM! + exit; + end; + + if Length(Args) = 2 then begin + base := ArgToFloat(Args[1]); + if base < 0 then begin + Result := ErrorResult(errOverflow); // #NUM! + exit; + end; + end else + base := 10; + + Result := FloatResult(logn(base, x)); +end; + +procedure fpsLOG10(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if x > 0 then + Result := FloatResult(log10(x)) + else + Result := ErrorResult(errOverflow); // #NUM! +end; + +procedure fpsPI(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Unused(Args); + Result := FloatResult(pi); +end; + +procedure fpsPOWER(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + try + Result := FloatResult(Power(ArgToFloat(Args[0]), ArgToFloat(Args[1]))); + except + Result := ErrorResult(errOverflow); + end; +end; + +procedure fpsRADIANS(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(DegToRad(ArgToFloat(Args[0]))); +end; + +procedure fpsRAND(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Unused(Args); + Result := FloatResult(random); +end; + +procedure fpsROUND(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + n: Integer; +begin + if Args[1].ResultType = rtInteger then + n := Args[1].ResInteger + else + n := round(Args[1].ResFloat); + Result := FloatResult(RoundTo(ArgToFloat(Args[0]), n)); +end; + +procedure fpsSIGN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(sign(ArgToFloat(Args[0]))); +end; + +procedure fpsSIN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(sin(ArgToFloat(Args[0]))); +end; + +procedure fpsSINH(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(sinh(ArgToFloat(Args[0]))); +end; + +procedure fpsSQRT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if x >= 0 then + Result := FloatResult(sqrt(x)) + else + Result := ErrorResult(errOverflow); +end; + +procedure fpsTAN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +var + x: TsExprFloat; +begin + x := ArgToFloat(Args[0]); + if frac(x / (pi*0.5)) = 0 then + Result := ErrorResult(errOverflow) // #NUM! + else + Result := FloatResult(tan(ArgToFloat(Args[0]))); +end; + +procedure fpsTANH(var Result: TsExpressionResult; const Args: TsExprParameterArray); +begin + Result := FloatResult(tanh(ArgToFloat(Args[0]))); +end; + + +// Builtin date/time functions + +procedure fpsDATE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// DATE( year, month, day ) +begin + Result := DateTimeResult( + EncodeDate(ArgToInt(Args[0]), ArgToInt(Args[1]), ArgToInt(Args[2])) + ); +end; + +procedure fpsDATEDIF(var Result: TsExpressionResult; const Args: TsExprParameterArray); +{ DATEDIF( start_date, end_date, interval ) + start_date <= end_date ! + interval = Y - The number of complete years. + = M - The number of complete months. + = D - The number of days. + = MD - The difference between the days (months and years are ignored). + = YM - The difference between the months (days and years are ignored). + = YD - The difference between the days (years and dates are ignored). } +var + interval: String; + start_date, end_date: TDate; +begin + start_date := ArgToDateTime(Args[0]); + end_date := ArgToDateTime(Args[1]); + interval := ArgToString(Args[2]); + + if end_date > start_date then + Result := ErrorResult(errOverflow) + else if interval = 'Y' then + Result := FloatResult(YearsBetween(end_date, start_date)) + else if interval = 'M' then + Result := FloatResult(MonthsBetween(end_date, start_date)) + else if interval = 'D' then + Result := FloatResult(DaysBetween(end_date, start_date)) + else + Result := ErrorResult(errFormulaNotSupported); +end; + +procedure fpsDATEVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the serial number of a date. Input is a string. +// DATE( date_string ) +var + d: TDateTime; +begin + if TryStrToDate(Args[0].ResString, d) then + Result := DateTimeResult(d) + else + Result := ErrorResult(errWrongType); +end; + +procedure fpsDAY(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// DAY( date_value ) +// date_value can be a serial number or a string +var + y,m,d: Word; + dt: TDateTime; +begin + if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then + DecodeDate(ArgToFloat(Args[0]), y,m,d) + else + if Args[0].ResultType in [rtString] then + begin + if TryStrToDate(Args[0].ResString, dt) then + DecodeDate(dt, y,m,d) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(d); +end; + +procedure fpsHOUR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// HOUR( time_value ) +// time_value can be a number or a string. +var + h, m, s, ms: Word; + t: double; +begin + if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then + DecodeTime(ArgToFloat(Args[0]), h,m,s,ms) + else + if (Args[0].ResultType in [rtString]) then + begin + if TryStrToTime(Args[0].ResString, t) then + DecodeTime(t, h,m,s,ms) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(h); +end; + +procedure fpsMINUTE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// MINUTE( serial_number or string ) +var + h, m, s, ms: Word; + t: double; +begin + if (Args[0].resultType in [rtDateTime, rtFloat, rtInteger]) then + DecodeTime(ArgToFloat(Args[0]), h,m,s,ms) + else + if (Args[0].ResultType in [rtString]) then + begin + if TryStrToTime(Args[0].ResString, t) then + DecodeTime(t, h,m,s,ms) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(m); +end; + +procedure fpsMONTH(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// MONTH( date_value or string ) +var + y,m,d: Word; + dt: TDateTime; +begin + if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then + DecodeDate(ArgToFloat(Args[0]), y,m,d) + else + if (Args[0].ResultType in [rtString]) then + begin + if TryStrToDate(Args[0].ResString, dt) then + DecodeDate(dt, y,m,d) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(m); +end; + +procedure fpsNOW(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the current system date and time. Willrefresh the date/time value +// whenever the worksheet recalculates. +// NOW() +begin + Result := DateTimeResult(Now); +end; + +procedure fpsSECOND(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// SECOND( serial_number ) +var + h, m, s, ms: Word; + t: Double; +begin + if (Args[0].ResultType in [rtDateTime, rtFloat, rtInteger]) then + DecodeTime(ArgToFloat(Args[0]), h,m,s,ms) + else + if (Args[0].ResultType in [rtString]) then + begin + if TryStrToTime(Args[0].ResString, t) then + DecodeTime(t, h,m,s,ms) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(s); +end; + +procedure fpsTIME(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// TIME( hour, minute, second) +begin + Result := DateTimeResult( + EncodeTime(ArgToInt(Args[0]), ArgToInt(Args[1]), ArgToInt(Args[2]), 0) + ); +end; + +procedure fpsTIMEVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the serial number of a time. Input must be a string. +// DATE( date_string ) +var + t: TDateTime; +begin + if TryStrToTime(Args[0].ResString, t) then + Result := DateTimeResult(t) + else + Result := ErrorResult(errWrongType); +end; + +procedure fpsTODAY(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the current system date. This function will refresh the date +// whenever the worksheet recalculates. +// TODAY() +begin + Result := DateTimeResult(Date); +end; + +procedure fpsWEEKDAY(var Result: TsExpressionResult; const Args: TsExprParameterArray); +{ WEEKDAY( serial_number, [return_value] ) + return_value = 1 - Returns a number from 1 (Sunday) to 7 (Saturday) (default) + = 2 - Returns a number from 1 (Monday) to 7 (Sunday). + = 3 - Returns a number from 0 (Monday) to 6 (Sunday). } +var + n: Integer; + dow: Integer; + dt: TDateTime; +begin + if Length(Args) = 2 then + n := ArgToInt(Args[1]) + else + n := 1; + if Args[0].ResultType in [rtDateTime, rtFloat, rtInteger] then + dt := ArgToDateTime(Args[0]) + else + if Args[0].ResultType in [rtString] then + if not TryStrToDate(Args[0].ResString, dt) then + begin + Result := ErrorResult(errWrongType); + exit; + end; + dow := DayOfWeek(dt); // Sunday = 1 ... Saturday = 7 + case n of + 1: ; + 2: if dow > 1 then dow := dow - 1 else dow := 7; + 3: if dow > 1 then dow := dow - 2 else dow := 6; + end; + Result := IntegerResult(dow); +end; + +procedure fpsYEAR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// YEAR( date_value ) +var + y,m,d: Word; + dt: TDateTime; +begin + if Args[0].ResultType in [rtDateTime, rtFloat, rtInteger] then + DecodeDate(ArgToFloat(Args[0]), y,m,d) + else + if Args[0].ResultType in [rtString] then + begin + if TryStrToDate(Args[0].ResString, dt) then + DecodeDate(dt, y,m,d) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + end; + Result := IntegerResult(y); +end; + + +// Builtin string functions + +procedure fpsCHAR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// CHAR( ascii_value ) +// returns the character based on the ASCII value +var + arg: Integer; +begin + Result := ErrorResult(errWrongType); + case Args[0].ResultType of + rtInteger, rtFloat: + if Args[0].ResultType in [rtInteger, rtFloat] then + begin + arg := ArgToInt(Args[0]); + if (arg >= 0) and (arg < 256) then + Result := StringResult(AnsiToUTF8(Char(arg))); + end; + rtError: + Result := ErrorResult(Args[0].ResError); + rtEmpty: + Result.ResultType := rtEmpty; + end; +end; + +procedure fpsCODE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// CODE( text ) +// returns the ASCII value of a character or the first character in a string. +var + s: String; + ch: Char; +begin + s := ArgToString(Args[0]); + if s = '' then + Result := ErrorResult(errWrongType) + else + begin + ch := UTF8ToAnsi(s)[1]; + Result := IntegerResult(ord(ch)); + end; +end; + +procedure fpsCONCATENATE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// CONCATENATE( text1, text2, ... text_n ) +// Joins two or more strings together +var + s: String; + i: Integer; +begin + s := ''; + for i:=0 to Length(Args)-1 do + begin + if Args[i].ResultType = rtError then + begin + Result := ErrorResult(Args[i].ResError); + exit; + end; + s := s + ArgToString(Args[i]); + end; + Result := StringResult(s); +end; + +procedure fpsLEFT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// LEFT( text, [number_of_characters] ) +// extracts a substring from a string, starting from the left-most character +var + s: String; + count: Integer; +begin + s := Args[0].ResString; + if s = '' then + Result.ResultType := rtEmpty + else + begin + if Length(Args) = 1 then + count := 1 + else + if Args[1].ResultType in [rtInteger, rtFloat] then + count := ArgToInt(Args[1]) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + Result := StringResult(UTF8LeftStr(s, count)); + end; +end; + +procedure fpsLEN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// LEN( text ) +// returns the length of the specified string. +begin + Result := IntegerResult(UTF8Length(Args[0].ResString)); +end; + +procedure fpsLOWER(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// LOWER( text ) +// converts all letters in the specified string to lowercase. If there are +// characters in the string that are not letters, they are not affected. +begin + Result := StringResult(UTF8Lowercase(Args[0].ResString)); +end; + +procedure fpsMID(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// MID( text, start_position, number_of_characters ) +// extracts a substring from a string (starting at any position). +begin + Result := StringResult(UTF8Copy(Args[0].ResString, ArgToInt(Args[1]), ArgToInt(Args[2]))); +end; + +procedure fpsREPLACE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// REPLACE( old_text, start, number_of_chars, new_text ) +// replaces a sequence of characters in a string with another set of characters +var + sOld, sNew, s1, s2: String; + start: Integer; + count: Integer; +begin + sOld := Args[0].ResString; + start := ArgToInt(Args[1]); + count := ArgToInt(Args[2]); + sNew := Args[3].ResString; + s1 := UTF8Copy(sOld, 1, start-1); + s2 := UTF8Copy(sOld, start+count, UTF8Length(sOld)); + Result := StringResult(s1 + sNew + s2); +end; + +procedure fpsRIGHT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// RIGHT( text, [number_of_characters] ) +// extracts a substring from a string, starting from the last character +var + s: String; + count: Integer; +begin + s := Args[0].ResString; + if s = '' then + Result.ResultType := rtEmpty + else begin + if Length(Args) = 1 then + count := 1 + else + if Args[1].ResultType in [rtInteger, rtFloat] then + count := ArgToInt(Args[1]) + else + begin + Result := ErrorResult(errWrongType); + exit; + end; + Result := StringResult(UTF8RightStr(s, count)); + end; +end; + +procedure fpsSUBSTITUTE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// SUBSTITUTE( text, old_text, new_text, [nth_appearance] ) +// replaces a set of characters with another. +var + sOld: String; + sNew: String; + s1, s2: String; + n: Integer; + s: String; + p: Integer; +begin + s := Args[0].ResString; + sOld := ArgToString(Args[1]); + sNew := ArgToString(Args[2]); + if Length(Args) = 4 then + begin + n := ArgToInt(Args[3]); // THIS PART NOT YET CHECKED !!!!!! + if n <= 0 then + begin + Result := ErrorResult(errWrongType); + exit; + end; + p := UTF8Pos(sOld, s); + while (n > 1) do begin + p := UTF8Pos(sOld, s, p+1); + dec(n); + end; + if p > 0 then begin + s1 := UTF8Copy(s, 1, p-1); + s2 := UTF8Copy(s, p+UTF8Length(sOld), UTF8Length(s)); + s := s1 + sNew + s2; + end; + Result := StringResult(s); + end else + Result := StringResult(UTF8StringReplace(s, sOld, sNew, [rfReplaceAll])); +end; + +procedure fpsTRIM(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// TRIM( text ) +// returns a text value with the leading and trailing spaces removed +begin + Result := StringResult(UTF8Trim(Args[0].ResString)); +end; + +procedure fpsUPPER(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// UPPER( text ) +// converts all letters in the specified string to uppercase. If there are +// characters in the string that are not letters, they are not affected. +begin + Result := StringResult(UTF8Uppercase(Args[0].ResString)); +end; + +procedure fpsVALUE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// VALUE( text ) +// converts a text value that represents a number to a number. +var + x: Double; + n: Integer; + s: String; +begin + s := ArgToString(Args[0]); + if TryStrToInt(s, n) then + Result := IntegerResult(n) + else + if TryStrToFloat(s, x, ExprFormatSettings) then + Result := FloatResult(x) + else + Result := ErrorResult(errWrongType); +end; + + +{ Builtin logical functions } + +procedure fpsAND(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// AND( condition1, [condition2], ... ) +// up to 30 parameters. At least 1 parameter. +var + i: Integer; + b: Boolean; +begin + b := true; + for i:=0 to High(Args) do + b := b and Args[i].ResBoolean; + Result.ResBoolean := b; +end; + +procedure fpsFALSE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// FALSE () +begin + Unused(Args); + Result.ResBoolean := false; +end; + +procedure fpsIF(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// IF( condition, value_if_true, [value_if_false] ) +begin + if Length(Args) > 2 then + begin + if Args[0].ResBoolean then + Result := Args[1] + else + Result := Args[2]; + end else + begin + if Args[0].ResBoolean then + Result := Args[1] + else + Result.ResBoolean := false; + end; +end; + +procedure fpsNOT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// NOT( condition ) +begin + Result.ResBoolean := not Args[0].ResBoolean; +end; + +procedure fpsOR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// OR( condition1, [condition2], ... ) +// up to 30 parameters. At least 1 parameter. +var + i: Integer; + b: Boolean; +begin + b := false; + for i:=0 to High(Args) do + b := b or Args[i].ResBoolean; + Result.ResBoolean := b; +end; + +procedure fpsTRUE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// TRUE() +begin + Unused(Args); + Result.ResBoolean := true; +end; + + +{ Builtin statistical functions } + +procedure ArgsToFloatArray(const Args: TsExprParameterArray; out AData: TsExprFloatArray); const - ln10 = ln(10); - -procedure BuiltInLog(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResFloat := ln(ArgToFloat(Args[0]))/ln10; -end; - -procedure BuiltInRound(var Result: TsExpressionResult; const Args: TExprParameterArray); + BLOCKSIZE = 128; var - decs: Integer; - f: TExprFloat; -begin (* - decs := round(ArgToFloat(Args[1])); - f := 1.0; - while decs > 0 do begin - f := f * 10; - dec(decs); - end; *) - Result.ResInteger := round(ArgToFloat(Args[0])); -end; - -procedure BuiltInTrunc(var Result: TsExpressionResult; const Args: TExprParameterArray); + i, n: Integer; + r, c: Cardinal; + cell: PCell; + arg: TsExpressionResult; begin - Result.ResInteger := trunc(ArgToFloat(Args[0])); -end; - -procedure BuiltInInt(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResFloat := int(ArgToFloat(Args[0])); -end; - -procedure BuiltInFrac(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResFloat := frac(ArgToFloat(Args[0])); + SetLength(AData, BLOCKSIZE); + n := 0; + for i:=0 to High(Args) do + begin + arg := Args[i]; + if arg.ResultType = rtCellRange then + for r := arg.ResCellRange.Row1 to arg.ResCellRange.Row2 do + for c := arg.ResCellRange.Col1 to arg.ResCellRange.Col2 do + begin + cell := arg.Worksheet.FindCell(r, c); + if (cell <> nil) and (cell^.ContentType in [cctNumber, cctDateTime]) then + begin + case cell^.ContentType of + cctNumber : AData[n] := cell^.NumberValue; + cctDateTime : AData[n] := cell^.DateTimeValue + end; + inc(n); + if n = Length(AData) then SetLength(AData, length(AData) + BLOCKSIZE); + end; + end + else + if (arg.ResultType in [rtInteger, rtFloat, rtDateTime, rtCell]) then + begin + AData[n] := ArgToFloat(arg); + inc(n); + if n = Length(AData) then SetLength(AData, Length(AData) + BLOCKSIZE); + end; + end; + SetLength(AData, n); end; -// String builtins - -procedure BuiltInLength(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := Length(Args[0].ResString); -end; - -procedure BuiltInCopy(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := copy(Args[0].ResString, Args[1].ResInteger, Args[2].ResInteger); -end; - -procedure BuiltInDelete(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := Args[0].resString; - Delete(Result.ResString, Args[1].ResInteger, Args[2].ResInteger); -end; - -procedure BuiltInPos(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := pos(Args[0].ResString, Args[1].ResString); -end; - -procedure BuiltInUppercase(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := Uppercase(Args[0].ResString); -end; - -procedure BuiltInLowercase(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := Lowercase(Args[0].ResString); -end; - -procedure BuiltInStringReplace(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsAVEDEV(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Average value of absolute deviations of data from their mean. +// AVEDEV( value1, [value2, ... value_n] ) var - flags : TReplaceFlags; + data: TsExprFloatArray; + m: TsExprFloat; + i: Integer; begin - flags := []; - if Args[3].ResBoolean then - Include(flags, rfReplaceAll); - if Args[4].ResBoolean then - Include(flags, rfIgnoreCase); - Result.ResString := StringReplace(Args[0].ResString, Args[1].ResString, Args[2].ResString, flags); + ArgsToFloatArray(Args, data); + m := Mean(data); + for i:=0 to High(data) do // replace data by their average deviation from the mean + data[i] := abs(data[i] - m); + Result.ResFloat := Mean(data); end; -procedure BuiltInCompareText(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := CompareText(Args[0].ResString, Args[1].ResString); -end; - - -// Date/Time builtins - -procedure BuiltInDate(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := Date; -end; - -procedure BuiltInTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := Time; -end; - -procedure BuiltInNow(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := Now; -end; - -procedure BuiltInDayOfWeek(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := DayOfWeek(Args[0].resDateTime); -end; - -procedure BuiltInExtractYear(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsAVERAGE(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// AVERAGE( value1, [value2, ... value_n] ) var - Y, M, D: Word; + data: TsExprFloatArray; begin - DecodeDate(Args[0].ResDateTime, Y, M, D); - Result.ResInteger := Y; + ArgsToFloatArray(Args, data); + Result.ResFloat := Mean(data); end; -procedure BuiltInExtractMonth(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsCOUNT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +{ counts the number of cells that contain numbers as well as the number of + arguments that contain numbers. + COUNT( value1, [value2, ... value_n] ) } var - Y, M, D: Word; + data: TsExprFloatArray; begin - DecodeDate(Args[0].ResDateTime, Y, M, D); - Result.ResInteger := M; + ArgsToFloatArray(Args, data); + Result.ResInteger := Length(data); end; -procedure BuiltInExtractDay(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsCOUNTA(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Counts the number of cells that are not empty as well as the number of +// arguments that contain values +// COUNTA( value1, [value2, ... value_n] ) var - Y, M, D: Word; + i, n: Integer; + r, c: Cardinal; + cell: PCell; + arg: TsExpressionResult; begin - DecodeDate(Args[0].ResDateTime, Y, M, D); - Result.ResInteger := D; + n := 0; + for i:=0 to High(Args) do + begin + arg := Args[i]; + case arg.ResultType of + rtInteger, rtFloat, rtDateTime, rtBoolean: + inc(n); + rtString: + if arg.ResString <> '' then inc(n); + rtError: + if arg.ResError <> errOK then inc(n); + rtCell: + begin + cell := ArgToCell(arg); + if cell <> nil then + case cell^.ContentType of + cctNumber, cctDateTime, cctBool: inc(n); + cctUTF8String: if cell^.UTF8StringValue <> '' then inc(n); + cctError: if cell^.ErrorValue <> errOK then inc(n); + end; + end; + rtCellRange: + for r := arg.ResCellRange.Row1 to arg.ResCellRange.Row2 do + for c := arg.ResCellRange.Col1 to arg.ResCellRange.Col2 do + begin + cell := arg.Worksheet.FindCell(r, c); + if (cell <> nil) then + case cell^.ContentType of + cctNumber, cctDateTime, cctBool : inc(n); + cctUTF8String: if cell^.UTF8StringValue <> '' then inc(n); + cctError: if cell^.ErrorValue <> errOK then inc(n); + end; + end; + end; + end; + Result.ResInteger := n; end; -procedure BuiltInExtractHour(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsCOUNTBLANK(var Result: TsExpressionResult; const Args: TsExprParameterArray); +{ Counts the number of empty cells in a range. + COUNTBLANK( range ) + "range" is the range of cells to count empty cells. } var - H, M, S, MS: Word; + n: Integer; + r, c: Cardinal; + cell: PCell; + arg: TsExpressionResult; begin - DecodeTime(Args[0].ResDateTime, H, M, S, MS); - Result.ResInteger := H; + n := 0; + case Args[0].ResultType of + rtEmpty: + inc(n); + rtCell: + begin + cell := ArgToCell(Args[0]); + if cell = nil then + inc(n) + else + case cell^.ContentType of + cctNumber, cctDateTime, cctBool: ; + cctUTF8String: if cell^.UTF8StringValue = '' then inc(n); + cctError: if cell^.ErrorValue = errOK then inc(n); + end; + end; + rtCellRange: + for r := Args[0].ResCellRange.Row1 to Args[0].ResCellRange.Row2 do + for c := Args[0].ResCellRange.Col1 to Args[0].ResCellRange.Col2 do begin + cell := Args[0].Worksheet.FindCell(r, c); + if cell = nil then + inc(n) + else + case cell^.ContentType of + cctNumber, cctDateTime, cctBool: ; + cctUTF8String: if cell^.UTF8StringValue = '' then inc(n); + cctError: if cell^.ErrorValue = errOK then inc(n); + end; + end; + end; + Result.ResInteger := n; end; -procedure BuiltInExtractMin(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsMAX(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// MAX( value1, [value2, ... value_n] ) var - H, M, S, MS: word; + data: TsExprFloatArray; begin - DecodeTime(Args[0].ResDateTime, H, M, S, MS); - Result.ResInteger := M; + ArgsToFloatArray(Args, data); + Result.ResFloat := MaxValue(data); end; -procedure BuiltInExtractSec(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsMIN(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// MIN( value1, [value2, ... value_n] ) var - H, M, S, MS: Word; + data: TsExprFloatArray; begin - DecodeTime(Args[0].ResDateTime, H, M, S, MS); - Result.ResInteger := S; + ArgsToFloatArray(Args, data); + Result.ResFloat := MinValue(data); end; -procedure BuiltInExtractMSec(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsPRODUCT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// PRODUCT( value1, [value2, ... value_n] ) var - H, M, S, MS: Word; + data: TsExprFloatArray; + i: Integer; + p: TsExprFloat; begin - DecodeTime(Args[0].ResDateTime, H, M, S, MS); - Result.ResInteger := MS; + ArgsToFloatArray(Args, data); + p := 1.0; + for i := 0 to High(data) do + p := p * data[i]; + Result.ResFloat := p; end; -procedure BuiltInEncodeDate(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsSTDEV(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the standard deviation of a population based on a sample of numbers +// of numbers. +// STDEV( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; begin - Result.ResDateTime := Encodedate(Args[0].ResInteger, Args[1].ResInteger, Args[2].ResInteger); -end; - -procedure BuiltInEncodeTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := EncodeTime(Args[0].ResInteger, Args[1].ResInteger, Args[2].ResInteger, Args[3].ResInteger); -end; - -procedure BuiltInEncodeDateTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := EncodeDate(Args[0].ResInteger, Args[1].ResInteger, Args[2].ResInteger) - + EncodeTime(Args[3].ResInteger, Args[4].ResInteger, Args[5].ResInteger, Args[6].ResInteger); -end; - -procedure BuiltInShortDayName(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := ShortDayNames[Args[0].ResInteger]; -end; - -procedure BuiltInShortMonthName(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := ShortMonthNames[Args[0].ResInteger]; -end; - -Procedure BuiltInLongDayName(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := LongDayNames[Args[0].ResInteger]; -end; - -procedure BuiltInLongMonthName(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := LongMonthNames[Args[0].ResInteger]; -end; - -procedure BuiltInFormatDateTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := FormatDateTime(Args[0].ResString, Args[1].ResDateTime); -end; - - -// Conversion -procedure BuiltInIntToStr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := IntToStr(Args[0].Resinteger); -end; - -procedure BuiltInStrToInt(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := StrToInt(Args[0].ResString); -end; - -procedure BuiltInStrToIntDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := StrToIntDef(Args[0].ResString, Args[1].ResInteger); -end; - -procedure BuiltInFloatToStr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := FloatToStr(Args[0].ResFloat); -end; - -procedure BuiltInStrToFloat(var Result: TsExpressionResult; Const Args: TExprParameterArray); -begin - Result.ResFloat := StrToFloat(Args[0].ResString); -end; - -procedure BuiltInStrToFloatDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResFloat := StrToFloatDef(Args[0].ResString, Args[1].ResFloat); -end; - -procedure BuiltInDateToStr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := DateToStr(Args[0].ResDateTime); -end; - -procedure BuiltInTimeToStr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := TimeToStr(Args[0].ResDateTime); -end; - -procedure BuiltInStrToDate(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToDate(Args[0].ResString); -end; - -procedure BuiltInStrToDateDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToDateDef(Args[0].ResString, Args[1].ResDateTime); -end; - -procedure BuiltInStrToTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToTime(Args[0].ResString); -end; - -procedure BuiltInStrToTimeDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToTimeDef(Args[0].ResString, Args[1].ResDateTime); -end; - -procedure BuiltInStrToDateTime(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToDateTime(Args[0].ResString); -end; - -procedure BuiltInStrToDateTimeDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResDateTime := StrToDateTimeDef(Args[0].ResString, Args[1].ResDateTime); -end; - -procedure BuiltInBoolToStr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResString := BoolToStr(Args[0].ResBoolean); -end; - -procedure BuiltInStrToBool(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResBoolean := StrToBool(Args[0].ResString); -end; - -procedure BuiltInStrToBoolDef(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResBoolean := StrToBoolDef(Args[0].ResString, Args[1].ResBoolean); -end; - - -// Boolean - -procedure BuiltInShl(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := Args[0].ResInteger shl Args[1].ResInteger -end; - -procedure BuiltInShr(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - Result.ResInteger := Args[0].ResInteger shr Args[1].ResInteger -end; - -procedure BuiltinIFS(var Result: TsExpressionResult; const Args: TExprParameterArray); -begin - if Args[0].ResBoolean then - Result.ResString := Args[1].ResString + ArgsToFloatArray(Args, data); + if Length(data) > 1 then + Result.ResFloat := StdDev(data) else - Result.ResString := Args[2].ResString + begin + Result.ResultType := rtError; + Result.ResError := errDivideByZero; + end; end; -procedure BuiltinIFI(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsSTDEVP(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the standard deviation of a population based on an entire population +// STDEVP( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; begin - if Args[0].ResBoolean then - Result.ResInteger := Args[1].ResInteger + ArgsToFloatArray(Args, data); + if Length(data) > 0 then + Result.ResFloat := PopnStdDev(data) else - Result.ResInteger := Args[2].ResInteger + begin + Result.ResultType := rtError; + Result.ResError := errDivideByZero; + end; end; -procedure BuiltinIFF(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsSUM(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// SUM( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; begin - if Args[0].ResBoolean then - Result.ResFloat := Args[1].ResFloat - else - Result.ResFloat := Args[2].ResFloat + ArgsToFloatArray(Args, data); + Result.ResFloat := Sum(data); end; -procedure BuiltinIFD(var Result: TsExpressionResult; const Args: TExprParameterArray); +procedure fpsSUMSQ(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the sum of the squares of a series of values. +// SUMSQ( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; begin - if Args[0].ResBoolean then - Result.ResDateTime := Args[1].ResDateTime - else - Result.ResDateTime := Args[2].ResDateTime + ArgsToFloatArray(Args, data); + Result.ResFloat := SumOfSquares(data); end; +procedure fpsVAR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the variance of a population based on a sample of numbers. +// VAR( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; +begin + ArgsToFloatArray(Args, data); + if Length(data) > 1 then + Result.ResFloat := Variance(data) + else + begin + Result.ResultType := rtError; + Result.ResError := errDivideByZero; + end; +end; + +procedure fpsVARP(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// Returns the variance of a population based on an entire population of numbers. +// VARP( value1, [value2, ... value_n] ) +var + data: TsExprFloatArray; +begin + ArgsToFloatArray(Args, data); + if Length(data) > 0 then + Result.ResFloat := PopnVariance(data) + else + begin + Result.ResultType := rtError; + Result.ResError := errDivideByZero; + end; +end; + + +{ Builtin info functions } + +{ !!!!!!!!!!!!!! not working !!!!!!!!!!!!!!!!!!!!!! } +{ !!!!!!!!!!!!!! needs localized strings !!!!!!!!!!! } + +procedure fpsCELL(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// CELL( type, [range] ) + +{ from http://www.techonthenet.com/excel/formulas/cell.php: + + "type" is the type of information that we retrieve for the cell and can have + one of the following values: + Value Explanation + ------------- -------------------------------------------------------------- + "address" Address of the cell. If the cell refers to a range, it is the + first cell in the range. + "col" Column number of the cell. + "color" Returns 1 if the color is a negative value; Otherwise it returns 0. + "contents" Contents of the upper-left cell. + "filename" Filename of the file that contains reference. + "format" Number format of the cell according to: + "G" General + "F0" 0 + ",0" #,##0 + "F2" 0.00 + ",2" #,##0.00 + "C0" $#,##0_);($#,##0) + "C0-" $#,##0_);[Red]($#,##0) + "C2" $#,##0.00_);($#,##0.00) + "C2-" $#,##0.00_);[Red]($#,##0.00) + "P0" 0% + "P2" 0.00% + "S2" 0.00E+00 + "G" # ?/? or # ??/?? + "D4" m/d/yy or m/d/yy h:mm or mm/dd/yy + "D1" d-mmm-yy or dd-mmm-yy + "D2" d-mmm or dd-mmm + "D3" mmm-yy + "D5" mm/dd + "D6" h:mm:ss AM/PM + "D7" h:mm AM/PM + "D8" h:mm:ss + "D9" h:mm + "parentheses" Returns 1 if the cell is formatted with parentheses; + Otherwise, it returns 0. + "prefix" Label prefix for the cell. + - Returns a single quote (') if the cell is left-aligned. + - Returns a double quote (") if the cell is right-aligned. + - Returns a caret (^) if the cell is center-aligned. + - Returns a back slash (\) if the cell is fill-aligned. + - Returns an empty text value for all others. + "protect" Returns 1 if the cell is locked. Returns 0 if the cell is not locked. + "row" Row number of the cell. + "type" Returns "b" if the cell is empty. + Returns "l" if the cell contains a text constant. + Returns "v" for all others. + "width" Column width of the cell, rounded to the nearest integer. + + !!!! NOT ALL OF THEM ARE SUPPORTED HERE !!! + + "range" is optional in Excel. It is the cell (or range) that you wish to retrieve + information for. If the range parameter is omitted, the CELL function will + assume that you are retrieving information for the last cell that was changed. + + "range" is NOT OPTIONAL here because we don't know the last cell changed !!! +} +var + stype: String; + r1,r2, c1,c2: Cardinal; + cell: PCell; + res: TsExpressionResult; +begin + if Length(Args)=1 then + begin + // This case is not supported by us, but it is by Excel. + // Therefore the error is not quite correct... + Result := ErrorResult(errIllegalRef); + exit; + end; + + stype := lowercase(ArgToString(Args[0])); + + case Args[1].ResultType of + rtCell: + begin + cell := ArgToCell(Args[1]); + r1 := Args[1].ResRow; + c1 := Args[1].ResCol; + r2 := r1; + c2 := c1; + end; + rtCellRange: + begin + r1 := Args[1].ResCellRange.Row1; + r2 := Args[1].ResCellRange.Row2; + c1 := Args[1].ResCellRange.Col1; + c2 := Args[1].ResCellRange.Col2; + cell := Args[1].Worksheet.FindCell(r1, c1); + end; + else + Result := ErrorResult(errWrongType); + exit; + end; + + if stype = 'address' then + Result := StringResult(GetCellString(r1, c1, [])) + else + if stype = 'col' then + Result := IntegerResult(c1+1) + else + if stype = 'color' then + begin + if (cell <> nil) and (cell^.NumberFormat = nfCurrencyRed) then + Result := IntegerResult(1) + else + Result := IntegerResult(0); + end else + if stype = 'contents' then + begin + if cell = nil then + Result := IntegerResult(0) + else + case cell^.ContentType of + cctNumber : if frac(cell^.NumberValue) = 0 then + Result := IntegerResult(trunc(cell^.NumberValue)) + else + Result := FloatResult(cell^.NumberValue); + cctDateTime : Result := DateTimeResult(cell^.DateTimeValue); + cctUTF8String : Result := StringResult(cell^.UTF8StringValue); + cctBool : Result := BooleanResult(cell^.BoolValue); + cctError : Result := ErrorResult(cell^.ErrorValue); + end; + end else + if stype = 'filename' then + Result := Stringresult( + ExtractFilePath(Args[1].Worksheet.Workbook.FileName) + '[' + + ExtractFileName(Args[1].Worksheet.Workbook.FileName) + ']' + + Args[1].Worksheet.Name + ) + else + if stype = 'format' then begin + Result := StringResult('G'); + if cell <> nil then + case cell^.NumberFormat of + nfGeneral: + Result := StringResult('G'); + nfFixed: + if cell^.NumberFormatStr= '0' then Result := StringResult('0') else + if cell^.NumberFormatStr = '0.00' then Result := StringResult('F0'); + nfFixedTh: + if cell^.NumberFormatStr = '#,##0' then Result := StringResult(',0') else + if cell^.NumberFormatStr = '#,##0.00' then Result := StringResult(',2'); + nfPercentage: + if cell^.NumberFormatStr = '0%' then Result := StringResult('P0') else + if cell^.NumberFormatStr = '0.00%' then Result := StringResult('P2'); + nfExp: + if cell^.NumberFormatStr = '0.00E+00' then Result := StringResult('S2'); + nfShortDate, nfLongDate, nfShortDateTime: + Result := StringResult('D4'); + nfLongTimeAM: + Result := StringResult('D6'); + nfShortTimeAM: + Result := StringResult('D7'); + nfLongTime: + Result := StringResult('D8'); + nfShortTime: + Result := StringResult('D9'); + end; + end else + if stype = 'prefix' then + begin + Result := StringResult(''); + if (cell^.ContentType = cctUTF8String) then + case cell^.HorAlignment of + haLeft : Result := StringResult(''''); + haCenter: Result := StringResult('^'); + haRight : Result := StringResult('"'); + end; + end else + if stype = 'row' then + Result := IntegerResult(r1+1) + else + if stype = 'type' then begin + if (cell = nil) or (cell^.ContentType = cctEmpty) then + Result := StringResult('b') + else if cell^.ContentType = cctUTF8String then begin + if (cell^.UTF8StringValue = '') + then Result := StringResult('b') + else Result := StringResult('l'); + end else + Result := StringResult('v'); + end else + if stype = 'width' then + Result := FloatResult(Args[1].Worksheet.GetColWidth(c1)) + else + Result := ErrorResult(errWrongType); +end; + +procedure fpsISBLANK(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISBLANK( value ) +// Checks for blank or null values. +// "value" is the value that you want to test. +// If "value" is blank, this function will return TRUE. +// If "value" is not blank, the function will return FALSE. +var + cell: PCell; +begin + case Args[0].ResultType of + rtEmpty : Result := BooleanResult(true); + rtString: Result := BooleanResult(Result.ResString = ''); + rtCell : begin + cell := ArgToCell(Args[0]); + if (cell = nil) or (cell^.ContentType = cctEmpty) then + Result := BooleanResult(true) + else + Result := BooleanResult(false); + end; + end; +end; + +procedure fpsISERR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISERR( value ) +// If "value" is an error value (except #N/A), this function will return TRUE. +// Otherwise, it will return FALSE. +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue <> errArgError) + then Result := BooleanResult(true); + end else + if (Args[0].ResultType = rtError) and (Args[0].ResError <> errArgError) then + Result := BooleanResult(true); +end; + +procedure fpsISERROR(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISERROR( value ) +// If "value" is an error value (#N/A, #VALUE!, #REF!, #DIV/0!, #NUM!, #NAME? +// or #NULL), this function will return TRUE. Otherwise, it will return FALSE. +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue <= errArgError) + then Result := BooleanResult(true); + end else + if (Args[0].ResultType = rtError) then + Result := BooleanResult(true); +end; + +procedure fpsISLOGICAL(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISLOGICAL( value ) +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType = cctBool) then + Result := BooleanResult(true); + end else + if (Args[0].ResultType = rtBoolean) then + Result := BooleanResult(true); +end; + +procedure fpsISNA(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISNA( value ) +// If "value" is a #N/A error value , this function will return TRUE. +// Otherwise, it will return FALSE. +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType = cctError) and (cell^.ErrorValue = errArgError) + then Result := BooleanResult(true); + end else + if (Args[0].ResultType = rtError) and (Args[0].ResError = errArgError) then + Result := BooleanResult(true); +end; + +procedure fpsISNONTEXT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISNONTEXT( value ) +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell = nil) or ((cell <> nil) and (cell^.ContentType <> cctUTF8String)) then + Result := BooleanResult(true); + end else + if (Args[0].ResultType <> rtString) then + Result := BooleanResult(true); +end; + +procedure fpsISNUMBER(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISNUMBER( value ) +// Tests "value" for a number (or date/time - checked with Excel). +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType in [cctNumber, cctDateTime]) then + Result := BooleanResult(true); + end else + if (Args[0].ResultType in [rtFloat, rtInteger, rtDateTime]) then + Result := BooleanResult(true); +end; + +procedure fpsISREF(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISREF( value ) +begin + Result := BooleanResult(Args[0].ResultType in [rtCell, rtCellRange]); +end; + +procedure fpsISTEXT(var Result: TsExpressionResult; const Args: TsExprParameterArray); +// ISTEXT( value ) +var + cell: PCell; +begin + Result := BooleanResult(false); + if (Args[0].ResultType = rtCell) then + begin + cell := ArgToCell(Args[0]); + if (cell <> nil) and (cell^.ContentType = cctUTF8String) then + Result := BooleanResult(true); + end else + if (Args[0].ResultType = rtString) then + Result := BooleanResult(true); +end; + + +{------------------------------------------------------------------------------} +{@@ + Registers a non-built-in function: + + @param AName Name of the function as used for calling it in the spreadsheet + @param AResultType A character classifying the data type of the function result: + 'I' integer + 'F' floating point number + 'D' date/time value + 'S' string + 'B' boolean value (TRUE/FALSE) + 'R' cell range, can also be used for functions requiring + a cell "reference", like "CELL(..)" + @param AParamTypes A string with result type symbols for each parameter of the + function. Symbols as used for "ResultType" with these + additions: + - Use a lower-case character if a parameter is optional. + (must be at the end of the string) + - Add "+" if the last parameter type is valid for a variable + parameter count (Excel does pose a limit of 30, though). + - Use "?" if the data type should not be checked. + @param AExcelCode ID of the function needed in the xls biff file. Please see + the "OpenOffice Documentation of Microsoft Excel File Format" + section 3.11. + @param ACallBack Address of the procedure called when the formula is + calculated. +} +{------------------------------------------------------------------------------} +procedure RegisterFunction(const AName: ShortString; const AResultType: Char; + const AParamTypes: String; const AExcelCode: Integer; ACallback: TsExprFunctionCallBack); +begin + with BuiltinIdentifiers do + AddFunction(bcUser, AName, AResultType, AParamTypes, AExcelCode, ACallBack); +end; + +{@@ + Registers the built-in functions. Called automatically. +} procedure RegisterStdBuiltins(AManager : TsBuiltInExpressionManager); +var + cat: TsBuiltInExprCategory; begin with AManager do begin - AddFloatVariable(bcMath, 'pi', Pi); // Math functions - AddFunction(bcMath, 'cos', 'F', 'F', @BuiltinCos); - AddFunction(bcMath, 'sin', 'F', 'F', @BuiltinSin); - AddFunction(bcMath, 'arctan', 'F', 'F', @BuiltinArctan); - AddFunction(bcMath, 'abs', 'F', 'F', @BuiltinAbs); - AddFunction(bcMath, 'sqr', 'F', 'F', @BuiltinSqr); - AddFunction(bcMath, 'sqrt', 'F', 'F', @BuiltinSqrt); - AddFunction(bcMath, 'exp', 'F', 'F', @BuiltinExp); - AddFunction(bcMath, 'ln', 'F', 'F', @BuiltinLn); - AddFunction(bcMath, 'log', 'F', 'F', @BuiltinLog); - AddFunction(bcMath, 'frac', 'F', 'F', @BuiltinFrac); - AddFunction(bcMath, 'int', 'F', 'F', @BuiltinInt); - AddFunction(bcMath, 'round', 'I', 'F', @BuiltinRound); - AddFunction(bcMath, 'trunc', 'I', 'F', @BuiltinTrunc); - // String - AddFunction(bcStrings, 'length', 'I', 'S', @BuiltinLength); - AddFunction(bcStrings, 'copy', 'S', 'SII', @BuiltinCopy); - AddFunction(bcStrings, 'delete', 'S', 'SII', @BuiltinDelete); - AddFunction(bcStrings, 'pos', 'I', 'SS', @BuiltinPos); - AddFunction(bcStrings, 'lowercase', 'S', 'S', @BuiltinLowercase); - AddFunction(bcStrings, 'uppercase', 'S', 'S', @BuiltinUppercase); - AddFunction(bcStrings, 'stringreplace','S', 'SSSBB',@BuiltinStringReplace); - AddFunction(bcStrings, 'comparetext', 'I', 'SS', @BuiltinCompareText); - // Date/Time - AddFunction(bcDateTime, 'date', 'D', '', @BuiltinDate); - AddFunction(bcDateTime, 'time', 'D', '', @BuiltinTime); - AddFunction(bcDateTime, 'now', 'D', '', @BuiltinNow); - AddFunction(bcDateTime, 'dayofweek', 'I', 'D', @BuiltinDayofweek); - AddFunction(bcDateTime, 'extractyear', 'I', 'D', @BuiltinExtractYear); - AddFunction(bcDateTime, 'extractmonth', 'I', 'D', @BuiltinExtractMonth); - AddFunction(bcDateTime, 'extractday', 'I', 'D', @BuiltinExtractDay); - AddFunction(bcDateTime, 'extracthour', 'I', 'D', @BuiltinExtractHour); - AddFunction(bcDateTime, 'extractmin', 'I', 'D', @BuiltinExtractMin); - AddFunction(bcDateTime, 'extractsec', 'I', 'D', @BuiltinExtractSec); - AddFunction(bcDateTime, 'extractmsec', 'I', 'D', @BuiltinExtractMSec); - AddFunction(bcDateTime, 'encodedate', 'D', 'III', @BuiltinEncodedate); - AddFunction(bcDateTime, 'encodetime', 'D', 'IIII',@BuiltinEncodeTime); - AddFunction(bcDateTime, 'encodedatetime', 'D', 'IIIIIII',@BuiltinEncodeDateTime); - AddFunction(bcDateTime, 'shortdayname', 'S', 'I', @BuiltinShortDayName); - AddFunction(bcDateTime, 'shortmonthname', 'S', 'I', @BuiltinShortMonthName); - AddFunction(bcDateTime, 'longdayname', 'S', 'I', @BuiltinLongDayName); - AddFunction(bcDateTime, 'longmonthname', 'S', 'I', @BuiltinLongMonthName); - AddFunction(bcDateTime, 'formatdatetime', 'S', 'SD', @BuiltinFormatDateTime); - // Boolean - AddFunction(bcBoolean, 'shl', 'I', 'II', @BuiltinShl); - AddFunction(bcBoolean, 'shr', 'I', 'II', @BuiltinShr); - AddFunction(bcBoolean, 'IFS', 'S', 'BSS', @BuiltinIFS); - AddFunction(bcBoolean, 'IFF', 'F', 'BFF', @BuiltinIFF); - AddFunction(bcBoolean, 'IFD', 'D', 'BDD', @BuiltinIFD); - AddFunction(bcBoolean, 'IFI', 'I', 'BII', @BuiltinIFI); - // Conversion - AddFunction(bcConversion, 'inttostr', 'S', 'I', @BuiltInIntToStr); - AddFunction(bcConversion, 'strtoint', 'I', 'S', @BuiltInStrToInt); - AddFunction(bcConversion, 'strtointdef', 'I', 'SI', @BuiltInStrToIntDef); - AddFunction(bcConversion, 'floattostr', 'S', 'F', @BuiltInFloatToStr); - AddFunction(bcConversion, 'strtofloat', 'F', 'S', @BuiltInStrToFloat); - AddFunction(bcConversion, 'strtofloatdef', 'F', 'SF', @BuiltInStrToFloatDef); - AddFunction(bcConversion, 'booltostr', 'S', 'B', @BuiltInBoolToStr); - AddFunction(bcConversion, 'strtobool', 'B', 'S', @BuiltInStrToBool); - AddFunction(bcConversion, 'strtobooldef', 'B', 'SB', @BuiltInStrToBoolDef); - AddFunction(bcConversion, 'datetostr', 'S', 'D', @BuiltInDateToStr); - AddFunction(bcConversion, 'timetostr', 'S', 'D', @BuiltInTimeToStr); - AddFunction(bcConversion, 'strtodate', 'D', 'S', @BuiltInStrToDate); - AddFunction(bcConversion, 'strtodatedef', 'D', 'SD', @BuiltInStrToDateDef); - AddFunction(bcConversion, 'strtotime', 'D', 'S', @BuiltInStrToTime); - AddFunction(bcConversion, 'strtotimedef', 'D', 'SD', @BuiltInStrToTimeDef); - AddFunction(bcConversion, 'strtodatetime', 'D', 'S', @BuiltInStrToDateTime); - AddFunction(bcConversion, 'strtodatetimedef', 'D', 'SD', @BuiltInStrToDateTimeDef); + cat := bcMath; + AddFunction(cat, 'ABS', 'F', 'F', INT_EXCEL_SHEET_FUNC_ABS, @fpsABS); + AddFunction(cat, 'ACOS', 'F', 'F', INT_EXCEL_SHEET_FUNC_ACOS, @fpsACOS); + AddFunction(cat, 'ACOSH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ACOSH, @fpsACOSH); + AddFunction(cat, 'ASIN', 'F', 'F', INT_EXCEL_SHEET_FUNC_ASIN, @fpsASIN); + AddFunction(cat, 'ASINH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ASINH, @fpsASINH); + AddFunction(cat, 'ATAN', 'F', 'F', INT_EXCEL_SHEET_FUNC_ATAN, @fpsATAN); + AddFunction(cat, 'ATANH', 'F', 'F', INT_EXCEL_SHEET_FUNC_ATANH, @fpsATANH); + AddFunction(cat, 'COS', 'F', 'F', INT_EXCEL_SHEET_FUNC_COS, @fpsCOS); + AddFunction(cat, 'COSH', 'F', 'F', INT_EXCEL_SHEET_FUNC_COSH, @fpsCOSH); + AddFunction(cat, 'DEGREES', 'F', 'F', INT_EXCEL_SHEET_FUNC_DEGREES, @fpsDEGREES); + AddFunction(cat, 'EXP', 'F', 'F', INT_EXCEL_SHEET_FUNC_EXP, @fpsEXP); + AddFunction(cat, 'INT', 'I', 'F', INT_EXCEL_SHEET_FUNC_INT, @fpsINT); + AddFunction(cat, 'LN', 'F', 'F', INT_EXCEL_SHEET_FUNC_LN, @fpsLN); + AddFunction(cat, 'LOG', 'F', 'Ff', INT_EXCEL_SHEET_FUNC_LOG, @fpsLOG); + AddFunction(cat, 'LOG10', 'F', 'F', INT_EXCEL_SHEET_FUNC_LOG10, @fpsLOG10); + AddFunction(cat, 'PI', 'F', '', INT_EXCEL_SHEET_FUNC_PI, @fpsPI); + AddFunction(cat, 'POWER', 'F', 'FF', INT_EXCEL_SHEET_FUNC_POWER, @fpsPOWER); + AddFunction(cat, 'RADIANS', 'F', 'F', INT_EXCEL_SHEET_FUNC_RADIANS, @fpsRADIANS); + AddFunction(cat, 'RAND', 'F', '', INT_EXCEL_SHEET_FUNC_RAND, @fpsRAND); + AddFunction(cat, 'ROUND', 'F', 'FF', INT_EXCEL_SHEET_FUNC_ROUND, @fpsROUND); + AddFunction(cat, 'SIGN', 'F', 'F', INT_EXCEL_SHEET_FUNC_SIGN, @fpsSIGN); + AddFunction(cat, 'SIN', 'F', 'F', INT_EXCEL_SHEET_FUNC_SIN, @fpsSIN); + AddFunction(cat, 'SINH', 'F', 'F', INT_EXCEL_SHEET_FUNC_SINH, @fpsSINH); + AddFunction(cat, 'SQRT', 'F', 'F', INT_EXCEL_SHEET_FUNC_SQRT, @fpsSQRT); + AddFunction(cat, 'TAN', 'F', 'F', INT_EXCEL_SHEET_FUNC_TAN, @fpsTAN); + AddFunction(cat, 'TANH', 'F', 'F', INT_EXCEL_SHEET_FUNC_TANH, @fpsTANH); + + // Date/time + cat := bcDateTime; + AddFunction(cat, 'DATE', 'D', 'III', INT_EXCEL_SHEET_FUNC_DATE, @fpsDATE); + AddFunction(cat, 'DATEDIF', 'F', 'DDS', INT_EXCEL_SHEET_FUNC_DATEDIF, @fpsDATEDIF); + AddFunction(cat, 'DATEVALUE', 'D', 'S', INT_EXCEL_SHEET_FUNC_DATEVALUE, @fpsDATEVALUE); + AddFunction(cat, 'DAY', 'I', '?', INT_EXCEL_SHEET_FUNC_DAY, @fpsDAY); + AddFunction(cat, 'HOUR', 'I', '?', INT_EXCEL_SHEET_FUNC_HOUR, @fpsHOUR); + AddFunction(cat, 'MINUTE', 'I', '?', INT_EXCEL_SHEET_FUNC_MINUTE, @fpsMINUTE); + AddFunction(cat, 'MONTH', 'I', '?', INT_EXCEL_SHEET_FUNC_MONTH, @fpsMONTH); + AddFunction(cat, 'NOW', 'D', '', INT_EXCEL_SHEET_FUNC_NOW, @fpsNOW); + AddFunction(cat, 'SECOND', 'I', '?', INT_EXCEL_SHEET_FUNC_SECOND, @fpsSECOND); + AddFunction(cat, 'TIME' , 'D', 'III', INT_EXCEL_SHEET_FUNC_TIME, @fpsTIME); + AddFunction(cat, 'TIMEVALUE', 'D', 'S', INT_EXCEL_SHEET_FUNC_TIMEVALUE, @fpsTIMEVALUE); + AddFunction(cat, 'TODAY', 'D', '', INT_EXCEL_SHEET_FUNC_TODAY, @fpsTODAY); + AddFunction(cat, 'WEEKDAY', 'I', '?i', INT_EXCEL_SHEET_FUNC_WEEKDAY, @fpsWEEKDAY); + AddFunction(cat, 'YEAR', 'I', '?', INT_EXCEL_SHEET_FUNC_YEAR, @fpsYEAR); + + // Strings + cat := bcStrings; + AddFunction(cat, 'CHAR', 'S', 'I', INT_EXCEL_SHEET_FUNC_CHAR, @fpsCHAR); + AddFunction(cat, 'CODE', 'I', 'S', INT_EXCEL_SHEET_FUNC_CODE, @fpsCODE); + AddFunction(cat, 'CONCATENATE','S','S+', INT_EXCEL_SHEET_FUNC_CONCATENATE,@fpsCONCATENATE); + AddFunction(cat, 'LEFT', 'S', 'Si', INT_EXCEL_SHEET_FUNC_LEFT, @fpsLEFT); + AddFunction(cat, 'LEN', 'I', 'S', INT_EXCEL_SHEET_FUNC_LEN, @fpsLEN); + AddFunction(cat, 'LOWER', 'S', 'S', INT_EXCEL_SHEET_FUNC_LOWER, @fpsLOWER); + AddFunction(cat, 'MID', 'S', 'SII', INT_EXCEL_SHEET_FUNC_MID, @fpsMID); + AddFunction(cat, 'REPLACE', 'S', 'SIIS', INT_EXCEL_SHEET_FUNC_REPLACE, @fpsREPLACE); + AddFunction(cat, 'RIGHT', 'S', 'Si', INT_EXCEL_SHEET_FUNC_RIGHT, @fpsRIGHT); + AddFunction(cat, 'SUBSTITUTE','S', 'SSSi', INT_EXCEL_SHEET_FUNC_SUBSTITUTE, @fpsSUBSTITUTE); + AddFunction(cat, 'TRIM', 'S', 'S', INT_EXCEL_SHEET_FUNC_TRIM, @fpsTRIM); + AddFunction(cat, 'UPPER', 'S', 'S', INT_EXCEL_SHEET_FUNC_UPPER, @fpsUPPER); + AddFunction(cat, 'VALUE', 'F', 'S', INT_EXCEL_SHEET_FUNC_VALUE, @fpsVALUE); + + // Logical + cat := bcLogical; + AddFunction(cat, 'AND', 'B', 'B+', INT_EXCEL_SHEET_FUNC_AND, @fpsAND); + AddFunction(cat, 'FALSE', 'B', '', INT_EXCEL_SHEET_FUNC_FALSE, @fpsFALSE); + AddFunction(cat, 'IF', 'B', 'B?+', INT_EXCEL_SHEET_FUNC_IF, @fpsIF); + AddFunction(cat, 'NOT', 'B', 'B', INT_EXCEL_SHEET_FUNC_NOT, @fpsNOT); + AddFunction(cat, 'OR', 'B', 'B+', INT_EXCEL_SHEET_FUNC_OR, @fpsOR); + AddFunction(cat, 'TRUE', 'B', '', INT_EXCEL_SHEET_FUNC_TRUE , @fpsTRUE); + + // Statistical + cat := bcStatistics; + AddFunction(cat, 'AVEDEV', 'F', '?+', INT_EXCEL_SHEET_FUNC_AVEDEV, @fpsAVEDEV); + AddFunction(cat, 'AVERAGE', 'F', '?+', INT_EXCEL_SHEET_FUNC_AVERAGE, @fpsAVERAGE); + AddFunction(cat, 'COUNT', 'I', '?+', INT_EXCEL_SHEET_FUNC_COUNT, @fpsCOUNT); + AddFunction(cat, 'COUNTA', 'I', '?+', INT_EXCEL_SHEET_FUNC_COUNTA, @fpsCOUNTA); + AddFunction(cat, 'COUNTBLANK','I', 'R', INT_EXCEL_SHEET_FUNC_COUNTBLANK, @fpsCOUNTBLANK); + AddFunction(cat, 'MAX', 'F', '?+', INT_EXCEL_SHEET_FUNC_MAX, @fpsMAX); + AddFunction(cat, 'MIN', 'F', '?+', INT_EXCEL_SHEET_FUNC_MIN, @fpsMIN); + AddFunction(cat, 'PRODUCT', 'F', '?+', INT_EXCEL_SHEET_FUNC_PRODUCT, @fpsPRODUCT); + AddFunction(cat, 'STDEV', 'F', '?+', INT_EXCEL_SHEET_FUNC_STDEV, @fpsSTDEV); + AddFunction(cat, 'STDEVP', 'F', '?+', INT_EXCEL_SHEET_FUNC_STDEVP, @fpsSTDEVP); + AddFunction(cat, 'SUM', 'F', '?+', INT_EXCEL_SHEET_FUNC_SUM, @fpsSUM); + AddFunction(cat, 'SUMSQ', 'F', '?+', INT_EXCEL_SHEET_FUNC_SUMSQ, @fpsSUMSQ); + AddFunction(cat, 'VAR', 'F', '?+', INT_EXCEL_SHEET_FUNC_VAR, @fpsVAR); + AddFunction(cat, 'VARP', 'F', '?+', INT_EXCEL_SHEET_FUNC_VARP, @fpsVARP); + // to do: CountIF, SUMIF + + // Info functions + cat := bcInfo; + AddFunction(cat, 'CELL', '?', 'Sr', INT_EXCEL_SHEET_FUNC_CELL, @fpsCELL); + AddFunction(cat, 'ISBLANK', 'B', '?', INT_EXCEL_SHEET_FUNC_ISBLANK, @fpsISBLANK); + AddFunction(cat, 'ISERR', 'B', '?', INT_EXCEL_SHEET_FUNC_ISERR, @fpsISERR); + AddFunction(cat, 'ISERROR', 'B', '?', INT_EXCEL_SHEET_FUNC_ISERROR, @fpsISERROR); + AddFunction(cat, 'ISLOGICAL', 'B', '?', INT_EXCEL_SHEET_FUNC_ISLOGICAL, @fpsISLOGICAL); + AddFunction(cat, 'ISNA', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNA, @fpsISNA); + AddFunction(cat, 'ISNONTEXT', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNONTEXT, @fpsISNONTEXT); + AddFunction(cat, 'ISNUMBER', 'B', '?', INT_EXCEL_SHEET_FUNC_ISNUMBER, @fpsISNUMBER); + AddFunction(cat, 'ISREF', 'B', '?', INT_EXCEL_SHEET_FUNC_ISREF, @fpsISREF); + AddFunction(cat, 'ISTEXT', 'B', '?', INT_EXCEL_SHEET_FUNC_ISTEXT, @fpsISTEXT); + + (* + // Lookup / reference functions + cat := bcLookup; + AddFunction(cat, 'COLUMN', 'I', 'R', INT_EXCEL_SHEET_FUNC_COLUMN, @fpsCOLUMN); + *) + end; end; @@ -3653,10 +5492,14 @@ procedure TsBuiltInExprIdentifierDef.Assign(Source: TPersistent); begin inherited Assign(Source); if Source is TsBuiltInExprIdentifierDef then - FCategory:=(Source as TsBuiltInExprIdentifierDef).Category; + FCategory := (Source as TsBuiltInExprIdentifierDef).Category; end; initialization + ExprFormatSettings := DefaultFormatSettings; + ExprFormatSettings.DecimalSeparator := '.'; + ExprFormatSettings.ListSeparator := ','; + RegisterStdBuiltins(BuiltinIdentifiers); finalization diff --git a/components/fpspreadsheet/fpsfunc.pas b/components/fpspreadsheet/fpsfunc.pas index 6c13300e2..85520af8d 100644 --- a/components/fpspreadsheet/fpsfunc.pas +++ b/components/fpspreadsheet/fpsfunc.pas @@ -2182,7 +2182,7 @@ begin end; -{ Lookup / refernence functions } +{ Lookup / reference functions } function fpsCOLUMN(Args: TsArgumentStack; NumArgs: Integer): TsArgument; { COLUMN( [reference] ) diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index e7d4d1c19..a01541409 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -1272,7 +1272,7 @@ begin // Read formula, store in the cell's FormulaValue.FormulaStr formula := GetAttrValue(ACellNode, 'table:formula'); if formula <> '' then Delete(formula, 1, 3); // delete "of:" - cell^.FormulaValue.FormulaStr := formula; + cell^.FormulaValue := formula; // Read formula results // ... number value @@ -3590,7 +3590,7 @@ begin AppendToStream(AStream, Format( '' + '', [ - ACell^.FormulaValue.FormulaStr, lStyle + ACell^.FormulaValue, lStyle ])); end; diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 51f4545f3..eec901751 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -40,6 +40,8 @@ const STR_WIKITABLE_PIPES = '.wikitable_pipes'; STR_WIKITABLE_WIKIMEDIA = '.wikitable_wikimedia'; + MAX_COL_COUNT = 65535; + type {@@ Possible encodings for a non-unicode encoded text } @@ -52,7 +54,7 @@ type seHebrew, seArabic ); - + (* {@@ Describes a formula Supported syntax: @@ -65,7 +67,7 @@ type FormulaStr: string; DoubleValue: double; end; - + *) {@@ Tokens to identify the elements in an expanded formula. See http://www.techonthenet.com/excel/formulas/ for an explanation of @@ -79,6 +81,19 @@ type in TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID, unit xlscommon, in sync } + TFEKind = ( + { Basic operands } + fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger, + fekString, fekBool, fekErr, fekMissingArg, + { Basic operations } + fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, + fekConcat, // string concatenation + fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, + fekParen, // show parenthesis around expression node + { Functions - they are identified by their name } + fekFunc + ); + (* TFEKind = ( { Basic operands } fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger, @@ -119,15 +134,17 @@ type { Other operations } fekOpSUM {Unary sum operation. Note: CANNOT be used for summing sell contents; use fekSUM} ); - + *) {@@ These tokens identify operands in RPN formulas. } TOperandTokens = fekCell..fekMissingArg; {@@ These tokens identify basic operations in RPN formulas. } TBasicOperationTokens = fekAdd..fekParen; + (* {@@ These tokens identify spreadsheet functions in RPN formulas. } TFuncTokens = fekAbs..fekOpSum; + *) {@@ Flags to mark the address or a cell or a range of cells to be absolute or relative. They are used in the set TsRelFlags. } @@ -149,20 +166,17 @@ type IntValue: Word; StringValue: String; RelFlags: TsRelFlags; // store info on relative/absolute addresses + FuncName: String; ParamsNum: Byte; end; - {@@ Expanded formula. Used by backend modules. Provides more information than the text only. - Is an array of TsFormulaElement items. } - TsExpandedFormula = array of TsFormulaElement; - {@@ RPN formula. Similar to the expanded formula, but in RPN notation. Simplifies the task of format writers which need RPN } TsRPNFormula = array of TsFormulaElement; {@@ Describes the type of content in a cell of a TsWorksheet } - TCellContentType = (cctEmpty, cctFormula, cctRPNFormula, cctNumber, - cctUTF8String, cctDateTime, cctBool, cctError); + TCellContentType = (cctEmpty, cctFormula, cctNumber, cctUTF8String, + cctDateTime, cctBool, cctError); {@@ Error code values } TsErrorValue = ( @@ -383,6 +397,16 @@ type {@@ State flags while calculating formulas } TsCalcState = (csNotCalculated, csCalculating, csCalculated); + {@@ Record combining a cell's row and column indexes } + TsCellCoord = record + Row, Col: Cardinal; + end; + + {@@ Record combining row and column cornder indexes of a range of cells } + TsCellRange = record + Row1, Col1, Row2, Col2: Cardinal; + end; + {@@ Pointer to a TCell record } PCell = ^TCell; @@ -401,8 +425,8 @@ type Row: Cardinal; // zero-based ContentType: TCellContentType; { Possible values for the cells } - FormulaValue: TsFormula; - RPNFormulaValue: TsRPNFormula; + FormulaValue: string; +// RPNFormulaValue: TsRPNFormula; NumberValue: double; UTF8StringValue: ansistring; DateTimeValue: TDateTime; @@ -475,7 +499,6 @@ type TsCustomSpreadWriter = class; TsWorkbook = class; - { TsWorksheet } {@@ This event fires whenever a cell value or cell formatting changes. It is @@ -511,7 +534,6 @@ type procedure RemoveCallback(data, arg: pointer); protected - procedure CalcRPNFormula(ACell: PCell); function CellUsedInFormula(ARow, ACol: Cardinal): Boolean; procedure ChangedCell(ARow, ACol: Cardinal); @@ -582,8 +604,6 @@ type function WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue): PCell; overload; procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload; - function WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula): PCell; overload; - procedure WriteFormula(ACell: PCell; AFormula: TsFormula); overload; function WriteFormula(ARow, ACol: Cardinal; AFormula: String): PCell; overload; procedure WriteFormula(ACell: PCell; AFormula: String); overload; @@ -676,8 +696,10 @@ type procedure WriteWordwrap(ACell: PCell; AValue: boolean); overload; { Formulas } + function BuildRPNFormula(ACell: PCell): TsRPNFormula; + procedure CalcFormula(ACell: PCell); procedure CalcFormulas; - function ReadRPNFormulaAsString(ACell: PCell): String; + function ConvertRPNFormulaToStringFormula(const AFormula: TsRPNFormula): String; function UseSharedFormula(ARow, ACol: Cardinal; ASharedFormulaBase: PCell): PCell; overload; procedure UseSharedFormula(ACellRangeStr: String; ASharedFormulaBase: PCell); overload; @@ -765,12 +787,12 @@ type @param boCalcBeforeSaving Calculates formulas before saving the file. Otherwise there are no results when the file is loaded back by fpspreadsheet. - @param boReadFormulas Allows to turn on/off reading of formulas; this - is a precaution since formulas not or not fully + @param boReadFormulas Allows to turn off reading of rpn formulas; this is + a precaution since formulas not correctly implemented by fpspreadsheet could crash the reading operation. } - TsWorkbookOption = (boVirtualMode, boBufStream, - boAutoCalc, boCalcBeforeSaving, boReadFormulas); + TsWorkbookOption = (boVirtualMode, boBufStream, boAutoCalc, boCalcBeforeSaving, + boReadFormulas); {@@ Set of options flags for the workbook } @@ -1069,7 +1091,6 @@ type { Helper routines } procedure AddDefaultFormats(); virtual; procedure CheckLimitations; - function ExpandFormula(AFormula: TsFormula): TsExpandedFormula; function FindFormattingInList(AFormat: PCell): Integer; procedure FixCellColors(ACell: PCell); function FixColor(AColor: TsColor): TsColor; virtual; @@ -1159,14 +1180,15 @@ type function RPNParenthesis(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; + function RPNFunc(AFuncName: String; ANext: PRPNItem): PRPNItem; overload; + function RPNFunc(AFuncName: String; ANumParams: Byte; ANext: PRPNItem): PRPNItem; overload; - function FixedParamCount(AElementKind: TFEKind): Boolean; +// function FixedParamCount(AElementKind: TFEKind): Boolean; var GsSpreadFormats: array of TsSpreadFormatData; -procedure RegisterFormulaFunc(AFormulaKind: TFEKind; AFunc: pointer); +//procedure RegisterFormulaFunc(AFormulaKind: TFEKind; AFunc: pointer); procedure RegisterSpreadFormat( AReaderClass: TsSpreadReaderClass; AWriterClass: TsSpreadWriterClass; AFormat: TsSpreadsheetFormat); @@ -1185,7 +1207,8 @@ function HasFormula(ACell: PCell): Boolean; implementation uses - Math, StrUtils, TypInfo, fpsStreams, fpsUtils, fpsNumFormatParser, fpsFunc; + Math, StrUtils, TypInfo, + fpsStreams, fpsUtils, fpsNumFormatParser, fpsExprParser, fpsFunc; { Translatable strings } resourcestring @@ -1312,7 +1335,7 @@ var 'wheat' // $16 ); - +(* { Properties of formula elements } type @@ -1328,7 +1351,7 @@ type end; var - FEProps: array[TFEKind] of TFEProp = ( // functions marked by (*) + FEProps: array[TFEKind] of TFEProp = ( // functions marked by ( * ) { Operands } // are only partially supported (Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCell (Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRef @@ -1385,7 +1408,7 @@ var (Symbol:'TANH'; MinParams:1; MaxParams:1; Func:fpsTANH), // fekTANH, { date/time } (Symbol:'DATE'; MinParams:3; MaxParams:3; Func:fpsDATE), // fekDATE - (Symbol:'DATEDIF'; MinParams:3; MaxParams:3; Func:fpsDATEDIF), // fekDATEDIF (*) + (Symbol:'DATEDIF'; MinParams:3; MaxParams:3; Func:fpsDATEDIF), // fekDATEDIF ( * ) (Symbol:'DATEVALUE'; MinParams:1; MaxParams:1; Func:fpsDATEVALUE), // fekDATEVALUE (Symbol:'DAY'; MinParams:1; MaxParams:1; Func:fpsDAY), // fekDAY (Symbol:'HOUR'; MinParams:1; MaxParams:1; Func:fpsHOUR), // fekHOUR @@ -1445,7 +1468,7 @@ var (Symbol:'PROPER'; MinParams:1; MaxParams:1; Func:nil), // fekPROPER (Symbol:'REPLACE'; MinParams:4; MaxParams:4; Func:fpsREPLACE), // fekREPLACE (Symbol:'RIGHT'; MinParams:1; MaxParams:2; Func:fpsRIGHT), // fekRIGHT - (Symbol:'SUBSTITUTE';MinParams:3; MaxParams:4; Func:fpsSUBSTITUTE), // fekSUBSTITUTE (*) + (Symbol:'SUBSTITUTE';MinParams:3; MaxParams:4; Func:fpsSUBSTITUTE), // fekSUBSTITUTE ( * ) (Symbol:'TRIM'; MinParams:1; MaxParams:1; Func:fpsTRIM), // fekTRIM (Symbol:'UPPER'; MinParams:1; MaxParams:1; Func:fpsUPPER), // fekUPPER { lookup/reference } @@ -1454,8 +1477,8 @@ var (Symbol:'ROW'; MinParams:0; MaxParams:1; Func:fpsROW), // fekROW (Symbol:'ROWS'; MinParams:1; MaxParams:1; Func:fpsROWS), // fekROWS { info } - (Symbol:'CELL'; MinParams:1; MaxParams:2; Func:fpsCELLINFO), // fekCELLINFO (*) - (Symbol:'INFO'; MinParams:1; MaxParams:1; Func:fpsINFO), // fekINFO (*) + (Symbol:'CELL'; MinParams:1; MaxParams:2; Func:fpsCELLINFO), // fekCELLINFO ( * ) + (Symbol:'INFO'; MinParams:1; MaxParams:1; Func:fpsINFO), // fekINFO ( * ) (Symbol:'ISBLANK'; MinParams:1; MaxParams:1; Func:fpsISBLANK), // fekIsBLANK (Symbol:'ISERR'; MinParams:1; MaxParams:1; Func:fpsISERR), // fekIsERR (Symbol:'ISERROR'; MinParams:1; MaxParams:1; Func:fpsISERROR), // fekIsERROR @@ -1484,7 +1507,7 @@ procedure RegisterFormulaFunc(AFormulaKind: TFEKind; AFunc: Pointer); begin FEProps[AFormulaKind].Func := TsFormulaFunc(AFunc); end; - + *) {@@ Registers a new reader/writer pair for a given spreadsheet file format @@ -1615,8 +1638,7 @@ end; } procedure InitCell(out ACell: TCell); begin - ACell.RPNFormulaValue := nil; - ACell.FormulaValue.FormulaStr := ''; + ACell.FormulaValue := ''; ACell.UTF8StringValue := ''; ACell.NumberFormatStr := ''; FillChar(ACell, SizeOf(ACell), 0); @@ -1643,9 +1665,7 @@ end; function HasFormula(ACell: PCell): Boolean; begin Result := Assigned(ACell) and ( - (ACell^.SharedFormulaBase <> nil) or - (Length(ACell^.RPNFormulaValue) > 0) or - (Length(ACell^.FormulaValue.FormulaStr) > 0) + (ACell^.SharedFormulaBase <> nil) or (Length(ACell^.FormulaValue) > 0) ); end; @@ -1723,7 +1743,29 @@ begin end; {@@ - Helper method for clearing the records in a spreadsheet. + Helper function which constructs an rpn formula from the cell's string + formula. This is needed, for example, when writing a formula to xls biff + file format. +} +function TsWorksheet.BuildRPNFormula(ACell: PCell): TsRPNFormula; +var + parser: TsSpreadsheetParser; +begin + if not HasFormula(ACell) then begin + SetLength(Result, 0); + exit; + end; + parser := TsSpreadsheetParser.Create(self); + try + parser.Expression := ACell^.FormulaValue; + Result := parser.RPNFormula; + finally + parser.Free; + end; +end; + +{@@ + Helper method for calculation of the formulas in a spreadsheet. } procedure TsWorksheet.CalcFormulaCallback(data, arg: pointer); var @@ -1736,35 +1778,103 @@ begin if (cell = nil) or (cell^.ContentType = cctError) then exit; - // Cell contains an RPN formula --> calculate the formula - if (Length(cell^.RPNFormulaValue) > 0) or - ((cell^.SharedFormulaBase <> nil) and (Length(cell^.SharedFormulaBase^.RPNFormulaValue) > 0)) - then - CalcRPNFormula(cell); - - // Here should be other case of string formula: - { - else - if (Length(cell^.FormulaValue.FormulaStr) > 0) or - ((cell^.SharedFormulaBase <> nil) and (Length(cell^.SharedFormulaBase^.FomrulaValue.FormulaStr) > 0)) - then - CalcStringFormula(cell); - } + if HasFormula(cell) or HasFormula(cell^.SharedFormulaBase) then + CalcFormula(cell); end; {@@ - Helper method marking all cells with formulas as "not calculated". This flag - is needed for recursive calculation of the entire worksheet. -} -procedure TsWorksheet.CalcStateCallback(data, arg: Pointer); -var - cell: PCell; -begin - Unused(arg); - cell := PCell(data); + Calculates the formula in a cell + Should not be called by itself because the result may depend on other cells + which may have not yet been calculated. It is better to call CalcFormulas + instead. - if Length(cell^.RPNFormulaValue) > 0 then - cell^.CalcState := csNotCalculated; + @param ACell Cell containing the formula. +} +procedure TsWorksheet.CalcFormula(ACell: PCell); +var + parser: TsSpreadsheetParser; + res: TsExpressionResult; + rpnFormula: TsRPNFormula; + cell: PCell; + i: Integer; + r, c: Cardinal; + fe: TsFormulaElement; +begin + ACell^.CalcState := csCalculating; + + parser := TsSpreadsheetParser.Create(self); + try + parser.Expression := ACell^.FormulaValue; + (* + // Check whether all used cells are already calculated. + rpnFormula := parser.RPNFormula; + for i:=0 to High(rpnFormula) do begin + fe := rpnFormula[i]; + case fe.ElementKind of + fekCell, fekCellRef: + begin + cell := FindCell(fe.Row, fe.Col); + if cell <> nil then + case cell^.CalcState of + csNotCalculated: CalcFormula(cell); + csCalculating : raise Exception.Create(lpCircularReference); + end; + end; + fekCellRange: + begin + for r := fe.Row to fe.Row2 do + for c := fe.Col to fe.Col2 do begin + cell := FindCell(r, c); + if cell <> nil then + case cell^.CalcState of + csNotCalculated: CalcFormula(cell); + csCalculating : raise Exception.Create(lpCircularReference); + end; + end; + end; + { + fekCellOffset: + begin + if ACell^.SharedFormulaBase = nil then begin + ACell^.WriteErrorValue(ACell, errIllegalRef); + exit; + end; + if (rfRelRow in fe.RelFlags) + then r := ACell^.Row + SmallInt(fe.Row) + else r := ACell^.SharedFormulaBase^.Row; + if (rfRelCol in fe.RelFlags) + then c := ACell^.Col + SmallInt(fe.Col) + else c := ACell^.SharedFormulaBase^.Col; + cell := FindCell(r, c); + if cell <> nil then begin + case cell^.CalcState of + csNotCalculated: CalcFormula(cell); + csCalculating : raise Exception.Create(lpCircularReference); + end; + end else begin + WriteErrorValue(ACell, errIllegalRef); + exit; + end; + end; + } + end; + end; + *) + parser.EvaluateExpression(res); + case res.ResultType of + rtEmpty : WriteBlank(ACell); + rtError : WriteErrorValue(ACell, res.ResError); + rtInteger : WriteNumber(ACell, res.ResInteger); + rtFloat : WriteNumber(ACell, res.ResFloat); + rtDateTime : WriteDateTime(ACell, res.ResDateTime); + rtString : WriteUTF8Text(ACell, res.ResString); + rtBoolean : WriteBoolValue(ACell, res.ResBoolean); + end; + finally + parser.Free; + end; + + ACell^.CalcState := csCalculated; end; {@@ @@ -1798,142 +1908,18 @@ begin end; {@@ - Calculates the rpn formula assigned to a cell. - Should not be called by itself because the result may depend on other cells - which may have not yet been calculated. It is better to call CalcFormulas - instead. - - @param ACell Cell containing the rpn formula. + Helper method marking all cells with formulas as "not calculated". This flag + is needed for recursive calculation of the entire worksheet. } -procedure TsWorksheet.CalcRPNFormula(ACell: PCell); +procedure TsWorksheet.CalcStateCallback(data, arg: Pointer); var - i: Integer; - args: TsArgumentStack; - func: TsFormulaFunc; - val: TsArgument; - fe: TsFormulaElement; cell: PCell; - r,c: Cardinal; - formula: TsRPNFormula; begin - if (ACell^.ContentType = cctError) then - exit; + Unused(arg); + cell := PCell(data); - if (ACell^.SharedFormulaBase = nil) and (Length(ACell^.RPNFormulaValue) = 0) then - exit; - - if (ACell^.SharedFormulaBase <> nil) and (Length(ACell^.SharedFormulaBase^.RPNFormulaValue) = 0) - then exit; - - ACell^.CalcState := csCalculating; - - args := TsArgumentStack.Create; - try - // Take care of shared formulas - if ACell^.SharedFormulaBase = nil then - formula := ACell^.RPNFormulaValue - else - formula := ACell^.SharedFormulaBase^.RPNFormulaValue; - - for i := 0 to Length(formula) - 1 do begin - fe := formula[i]; // "fe" means "formula element" - case fe.ElementKind of - fekCell, fekCellRef: - begin - cell := FindCell(fe.Row, fe.Col); - if cell <> nil then - case cell^.CalcState of - csNotCalculated: CalcRPNFormula(cell); - csCalculating : raise Exception.Create(lpCircularReference); - end; - args.PushCell(cell, self); - end; - fekCellRange: - begin - for r := fe.Row to fe.Row2 do - for c := fe.Col to fe.Col2 do begin - cell := FindCell(r, c); - if cell <> nil then - case cell^.CalcState of - csNotCalculated: CalcRPNFormula(cell); - csCalculating : raise Exception.Create(lpCircularReference); - end; - end; - args.PushCellRange(fe.Row, fe.Col, fe.Row2, fe.Col2, self); - end; - fekCellOffset: - begin - if ACell^.SharedFormulaBase = nil then begin - WriteErrorValue(ACell, errIllegalRef); - exit; - end; - if (rfRelRow in fe.RelFlags) - then r := ACell^.Row + SmallInt(fe.Row) - else r := ACell^.SharedFormulaBase^.Row; - if (rfRelCol in fe.RelFlags) - then c := ACell^.Col + SmallInt(fe.Col) - else c := ACell^.SharedFormulaBase^.Col; - cell := FindCell(r, c); - if cell <> nil then begin - case cell^.CalcState of - csNotCalculated: CalcRPNFormula(cell); - csCalculating : raise Exception.Create(lpCircularReference); - end; - args.PushCell(cell, self); - end else begin - WriteErrorValue(ACell, errIllegalRef); - exit; - end; - end; - fekNum: - args.PushNumber(fe.DoubleValue, self); - fekInteger: - args.PushNumber(1.0*fe.IntValue, self); - fekString: - args.PushString(fe.StringValue, self); - fekBool: - args.PushBool(fe.DoubleValue <> 0.0, self); - fekMissingArg: - args.PushMissing(self); - fekParen: ; // visual effect only - fekErr: - exit; - else - func := FEProps[fe.ElementKind].Func; - if not Assigned(func) then begin - // calculation of function not implemented - WriteErrorValue(ACell, errFormulaNotSupported); - exit; - end; - if args.Count < fe.ParamsNum then begin - // not enough parameters - WriteErrorValue(ACell, errArgError); - exit; - end; - // Result of function - val := func(args, fe.ParamsNum); - // Push result on stack for usage by next function or as final result - args.Push(val, self); - end; // case - end; // for - - { When all formula elements have been processed the stack contains the - final result. } - if args.Count = 1 then begin - val := args.Pop; - case val.ArgumentType of - atNumber: WriteNumber(ACell, val.NumberValue); - atBool : WriteBoolValue(ACell, val.BoolValue); - atString: WriteUTF8Text(ACell, val.StringValue); - atError : WriteErrorValue(ACell, val.ErrorValue); - atEmpty : WriteBlank(ACell); - end; - end else - WriteErrorValue(ACell, errArgError); - finally - ACell^.CalcState := csCalculated; - args.Free; - end; + if HasFormula(cell) then + cell^.CalcState := csNotCalculated; end; {@@ @@ -1958,14 +1944,16 @@ var cellNode: TAVLTreeNode; fe: TsFormulaElement; i: Integer; + rpnFormula: TsRPNFormula; begin cellNode := FCells.FindLowest; while Assigned(cellNode) do begin cell := PCell(cellNode.Data); - if Length(cell^.RPNFormulaValue) > 0 then - for i := 0 to Length(cell^.RPNFormulaValue)-1 do + if HasFormula(cell) then begin + rpnFormula := BuildRPNFormula(cell); + for i := 0 to Length(rpnFormula)-1 do begin - fe := cell^.RPNFormulaValue[i]; + fe := rpnFormula[i]; case fe.ElementKind of fekCell, fekCellRef: if (fe.Row = ARow) and (fe.Col = ACol) then @@ -1982,8 +1970,10 @@ begin end; end; end; + end; cellNode := FCells.FindSuccessor(cellNode); end; + SetLength(rpnFormula, 0); end; {@@ @@ -2689,18 +2679,15 @@ end; is returned as a string in Excel syntax. @param ACell Pointer to the cell considered - @return Formula string in Excel syntax. + @return Formula string in Excel syntax (does not contain a leading "=") } function TsWorksheet.ReadFormulaAsString(ACell: PCell): String; begin Result := ''; if ACell = nil then exit; - if HasFormula(ACell) then begin - Result := ReadRPNFormulaAsString(ACell); - if Result = '' then - Result := ACell^.FormulaValue.FormulaStr; - end; + if HasFormula(ACell) then + Result := ACell^.FormulaValue; end; {@@ @@ -2737,152 +2724,24 @@ end; {@@ - If a cell contains an RPN formula an Excel-like formula string is constructed - and returned. + Converts an RPN formula (as read from an xls biff file, for example) to a + string formula. - @param ACell Pointer to the cell considered - @return Formula string in Excel syntax. + @param AFormula Array of rpn formula tokens + @return Formula string in Excel syntax (without leading "=") } -function TsWorksheet.ReadRPNFormulaAsString(ACell: PCell): String; +function TsWorksheet.ConvertRPNFormulaToStringFormula(const AFormula: TsRPNFormula): String; var - fs: TFormatSettings; - elem: TsFormulaElement; - i, j: Integer; - L: TStringList; - s: String; - ptr: Pointer; - fek: TFEKind; - formula: TsRPNFormula; - r,c: Cardinal; + parser: TsSpreadsheetParser; begin Result := ''; - if ACell = nil then - exit; - fs := Workbook.FormatSettings; - L := TStringList.Create; + parser := TsSpreadsheetParser.Create(self); try - // Take care of shared formulas - if ACell^.SharedFormulaBase = nil then - formula := ACell^.RPNFormulaValue - else - formula := ACell^.SharedFormulaBase^.RPNFormulaValue; - - if Length(formula) = 0 then - exit; - - // We store the cell values and operation codes in a stringlist which serves - // as kind of stack. Therefore, we do not destroy the original formula array. - // We reverse the order of the items because in the next step stringlist - // items will subsequently be deleted, and this is much easier when going - // in reverse direction. - for i := Length(formula)-1 downto 0 do begin - elem := formula[i]; - ptr := Pointer(elem.ElementKind); - case elem.ElementKind of - fekNum: - L.AddObject(Format('%g', [elem.DoubleValue], fs), ptr); - fekInteger: - L.AddObject(IntToStr(elem.IntValue), ptr); - fekString: - L.AddObject('"' + elem.StringValue + '"', ptr); - fekBool: - L.AddObject(IfThen(boolean(elem.IntValue), 'FALSE', 'TRUE'), ptr); - fekCell, - fekCellRef: - L.AddObject(GetCellString(elem.Row, elem.Col, elem.RelFlags), ptr); - fekCellRange: - L.AddObject(GetCellRangeString(elem.Row, elem.Col, elem.Row2, elem.Col2, elem.RelFlags), ptr); - fekCellOffset: - begin - if rfRelRow in elem.RelFlags - then r := ACell^.Row + SmallInt(elem.Row) - else r := elem.Row; - if rfRelCol in elem.RelFlags - then c := ACell^.Col + SmallInt(elem.Col) - else c := elem.Col; - L.AddObject(GetCellString(r, c, elem.RelFlags), ptr); - end - // Operations: - else - L.AddObject(FEProps[elem.ElementKind].Symbol, ptr); - end; - end; - - // Now we construct the string from the parts stored in the stringlist. - // Every item processed is deleted from the list for error detection. - // In order not to confuse indexes we start at the end of the list and - // work forward. - i := L.Count-1; - while (L.Count > 0) and (i >= 0) do begin - fek := TFEKind(PtrInt(L.Objects[i])); - case fek of - fekAdd, fekSub, fekMul, fekDiv, fekPower, fekConcat, - fekEqual, fekNotEqual, fekLess, fekLessEqual, fekGreater, fekGreaterEqual: - if i+2 < L.Count then begin - L.Strings[i] := Format('%s%s%s', [L[i+2], L[i], L[i+1]]); - L.Delete(i+2); - L.Delete(i+1); - L.Objects[i] := pointer(fekString); - end else begin - Result := '=' + lpErrArgError; - exit; - end; - fekUPlus, fekUMinus: - if i+1 < L.Count then begin - L.Strings[i] := L[i]+L[i+1]; - L.Delete(i+1); - L.Objects[i] := Pointer(fekString); - end else begin - Result := '=' + lpErrArgError; - exit; - end; - fekPercent: - if i+1 < L.Count then begin - L.Strings[i] := L[i+1]+L[i]; - L.Delete(i+1); - L.Objects[i] := Pointer(fekString); - end else begin - Result := '=' + lpErrArgError; - exit; - end; - fekParen: - if i+1 < L.Count then begin - L.Strings[i] := Format('(%s)', [L[i+1]]); - L.Delete(i+1); - L.Objects[i] := pointer(fekString); - end else begin - Result := '=' + lpErrArgError; - exit; - end; - else - if fek >= fekAdd then begin - elem := formula[Length(formula) - 1 - i]; - s := ''; - for j:= i+elem.ParamsNum downto i+1 do begin - if j < L.Count then begin - s := s + fs.ListSeparator + ' ' + L[j]; - L.Delete(j); - end else begin - Result := '=' + lpErrArgError; - exit; - end; - end; - Delete(s, 1, 2); - L.Strings[i] := Format('%s(%s)', [L[i], s]); - L.Objects[i] := pointer(fekString); - end; - end; - dec(i); - end; - - if L.Count > 1 then - Result := '=' + lpErrArgError // too many arguments - else - Result := '=' + L[0]; - + parser.RPNFormula := AFormula; + Result := parser.BuildStringFormula; finally - L.Free; + parser.Free; end; end; @@ -2979,8 +2838,8 @@ begin end; Result := GetCell(ARow, ACol); Result.SharedFormulaBase := ASharedFormulaBase; - if ((Length(Result^.RPNFormulaValue) > 0) or (Length(Result^.FormulaValue.FormulaStr) > 0)) - and ((ASharedFormulaBase.Row <> ARow) or (ASharedFormulaBase.Col <> ACol)) + if HasFormula(Result) and + ((ASharedFormulaBase.Row <> ARow) or (ASharedFormulaBase.Col <> ACol)) then raise Exception.CreateFmt('Cell %s uses a shared formula, but contains an own formula.', [GetCellString(ARow, ACol)]); @@ -3702,36 +3561,7 @@ end; @param ARow The row of the cell @param ACol The column of the cell - @param AFormula The formula to be written - @return Pointer to the cell -} -function TsWorksheet.WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula): PCell; -begin - Result := GetCell(ARow, ACol); - WriteFormula(Result, AFormula); -end; - -{@@ - Writes a formula to a given cell - - @param ACell Pointer to the cell - @param AFormula Formula to be written -} -procedure TsWorksheet.WriteFormula(ACell: PCell; AFormula: TsFormula); -begin - if ACell = nil then - exit; - ACell^.ContentType := cctFormula; - ACell^.FormulaValue := AFormula; - ChangedCell(ACell^.Row, ACell^.Col); -end; - -{@@ - Writes a formula to a given cell - - @param ARow The row of the cell - @param ACol The column of the cell - @param AFormula The formula string to be written + @param AFormula The formula string to be written. A leading "=" will be removed. @return Pointer to the cell } function TsWorksheet.WriteFormula(ARow, ACol: Cardinal; AFormula: string): PCell; @@ -3744,14 +3574,17 @@ end; Writes a formula to a given cell @param ACell Pointer to the cell - @param AFormula Formula string to be written + @param AFormula Formula string to be written. A leading '=' will be removed. } -procedure TsWorksheet.WriteFormula(ACell: PCell; AFormula: String); +procedure TsWorksheet.WriteFormula(ACell: PCell; AFormula: string); begin if ACell = nil then exit; ACell^.ContentType := cctFormula; - ACell^.FormulaValue.FormulaStr := AFormula; + if (AFormula <> '') and (AFormula[1] = '=') then + ACell^.FormulaValue := Copy(AFormula, 2, Length(AFormula)) + else + ACell^.FormulaValue := AFormula; ChangedCell(ACell^.Row, ACell^.Col); end; @@ -3887,7 +3720,8 @@ end; {@@ Writes an RPN formula to a cell. An RPN formula is an array of tokens - describing the calculation to be performed. + describing the calculation to be performed. In addition,the RPN formula is + converted to a string formula. @param ACell Pointer to the cell @param AFormula Array of TsFormulaElements. The array can be created by @@ -3902,8 +3736,9 @@ begin if ACell = nil then exit; - ACell^.ContentType := cctRPNFormula; - ACell^.RPNFormulaValue := AFormula; + ACell^.ContentType := cctFormula; + ACell^.FormulaValue := ConvertRPNFormulaToStringFormula(AFormula); + ChangedCell(ACell^.Row, ACell^.Col); end; @@ -4762,6 +4597,7 @@ var cell: PCell; col: PtrInt; fe: TsFormulaElement; + formula: TsRPNFormula; i: Integer; begin col := PtrInt(arg); @@ -4771,11 +4607,15 @@ begin if cell^.Col >= col then inc(cell^.Col); - // Update rpn formulas - for i:=0 to Length(cell^.RPNFormulaValue)-1 do begin - fe := cell^.RPNFormulaValue[i]; // "fe" means "formula element" + // Update formulas + // (1) create an rpn formula + formula := BuildRPNFormula(cell); + // (2) update cell addresses affected by the insertion of a column + for i:=0 to Length(formula)-1 do begin + fe := Formula[i]; // "fe" means "formula element" case fe.ElementKind of - fekCell, fekCellRef: if fe.Col >= col then inc(fe.Col); + fekCell, fekCellRef: + if fe.Col >= col then inc(fe.Col); fekCellRange: begin if fe.Col >= col then inc(fe.Col); @@ -4783,6 +4623,8 @@ begin end; end; end; + // (3) convert rpn formula back to string formula + cell^.FormulaValue := ConvertRPNFormulaToStringFormula(formula); end; {@@ @@ -4824,6 +4666,7 @@ var row: PtrInt; i: Integer; fe: TsFormulaElement; + formula: TsRPNFormula; begin row := PtrInt(arg); cell := PCell(data); @@ -4833,10 +4676,14 @@ begin inc(cell^.Row); // Update rpn formulas - for i:=0 to Length(cell^.RPNFormulaValue)-1 do begin - fe := cell^.RPNFormulaValue[i]; // "fe" means "formula element" + // (1) create an rpn formula + formula := BuildRPNFormula(cell); + // (2) update cell addresses affected by the insertion of a column + for i:=0 to Length(formula)-1 do begin + fe := formula[i]; // "fe" means "formula element" case fe.ElementKind of - fekCell, fekCellRef: if fe.Row >= row then inc(fe.Row); + fekCell, fekCellRef: + if fe.Row >= row then inc(fe.Row); fekCellRange: begin if fe.Row >= row then inc(fe.Row); @@ -4844,6 +4691,8 @@ begin end; end; end; + // (3) convert rpn formula back to string formula + cell^.FormulaValue := ConvertRPNFormulaToStringFormula(formula); end; {@@ @@ -6965,50 +6814,6 @@ begin NumFormatList.Sort; end; -{@@ - Expands a formula, separating it in it's constituent parts, - so that it is already partially parsed and it is easier to - convert it into the format supported by the writer module -} -function TsCustomSpreadWriter.ExpandFormula(AFormula: TsFormula): TsExpandedFormula; -var - StrPos: Integer; - ResPos: Integer; -begin - ResPos := -1; - SetLength(Result, 0); - - // The formula needs to start with a "=" character. - if AFormula.FormulaStr[1] <> '=' then raise Exception.Create('Formula doesn''t start with ='); - - StrPos := 2; - - while Length(AFormula.FormulaStr) <= StrPos do - begin - // Checks for cell with the format [Letter][Number] -{ if (AFormula.FormulaStr[StrPos] in [a..zA..Z]) and - (AFormula.FormulaStr[StrPos + 1] in [0..9]) then - begin - Inc(ResPos); - SetLength(Result, ResPos + 1); - Result[ResPos].ElementKind := fekCell; -// Result[ResPos].Col1 := fekCell; - Result[ResPos].Row1 := AFormula.FormulaStr[StrPos + 1]; - - Inc(StrPos); - end - // Checks for arithmetical operations - else} if AFormula.FormulaStr[StrPos] = '+' then - begin - Inc(ResPos); - SetLength(Result, ResPos + 1); - Result[ResPos].ElementKind := fekAdd; - end; - - Inc(StrPos); - end; -end; - {@@ Helper function for the spreadsheet writers. Writes the cell value to the stream. Calls the WriteNumber method of the worksheet for writing a number, @@ -7152,7 +6957,8 @@ end; procedure TsCustomSpreadWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); begin - Unused(AStream, ARow, ACol); + Unused(AStream); + Unused(ARow, ACol, ACell); end; (* {@@ @@ -7405,8 +7211,7 @@ begin end; {@@ - Creates an entry in the RPN array for a number. Integers and floating-point - values can be used likewise. + Creates an entry in the RPN array for a floating point number. @param AValue Number value to be inserted into the formula @param ANext Pointer to the next RPN item in the list @@ -7459,10 +7264,35 @@ end; } function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem; begin + { if FEProps[AToken].MinParams <> FEProps[AToken].MaxParams then raise Exception.CreateFmt(lpSpecifyNumberOfParams, [FEProps[AToken].Symbol]); + } + Result := NewRPNItem; + Result^.FE.ElementKind := AToken; + Result^.Fe.FuncName := ''; + Result^.Next := ANext; +end; - Result := RPNFunc(AToken, FEProps[AToken].MinParams, ANext); + +{@@ + 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. + + @param AToken Formula element indicating the function to be executed, + see the TFEKind enumeration for possible values. + @param ANext Pointer to the next RPN item in the list + + @see TFEKind +} +function RPNFunc(AFuncName: String; ANext: PRPNItem): PRPNItem; +begin + { + if FEProps[AToken].MinParams <> FEProps[AToken].MaxParams then + raise Exception.CreateFmt(lpSpecifyNumberOfParams, [FEProps[AToken].Symbol]); + } + Result := RPNFunc(AFuncName, 255, ANext); //FEProps[AToken].MinParams, ANext); end; {@@ @@ -7472,27 +7302,29 @@ end; @param AToken Formula element indicating the function to be executed, see the TFEKind enumeration for possible values. - @param ANumParams Number of arguments used in the formula + @param ANumParams Number of arguments used in the formula. If -1 then the + fixed number of arguments known from the function definiton + is used. @param ANext Pointer to the next RPN item in the list @see TFEKind } -function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem; +function RPNFunc(AFuncName: String; ANumParams: Byte; ANext: PRPNItem): PRPNItem; begin - if ord(AToken) < ord(fekAdd) then - raise Exception.Create('No basic tokens allowed here.'); - - if (ANumParams < FEProps[AToken].MinParams) or (ANumParams > FEProps[AToken].MaxParams) then - raise Exception.CreateFmt(lpIncorrectParamCount, [ - FEProps[AToken].Symbol, FEProps[AToken].MinParams, FEProps[AToken].MaxParams - ]); - + { + if (ANumParams > -1) then + if (ANumParams < FEProps[AToken].MinParams) or (ANumParams > FEProps[AToken].MaxParams) then + raise Exception.CreateFmt(lpIncorrectParamCount, [ + FEProps[AToken].Symbol, FEProps[AToken].MinParams, FEProps[AToken].MaxParams + ]); + } Result := NewRPNItem; - Result^.FE.ElementKind := AToken; + Result^.FE.ElementKind := fekFunc; + Result^.Fe.FuncName := AFuncName; Result^.FE.ParamsNum := ANumParams; Result^.Next := ANext; end; - + (* {@@ Returns if the function defined by the token requires a fixed number of parameter. @@ -7503,7 +7335,7 @@ begin Result := (FEProps[AElementKind].MinParams = FEProps[AElementKind].MaxParams) and (FEProps[AElementKind].MinParams >= 0); end; - + *) {@@ Creates an RPN formula by a single call using nested RPN items. diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas index 21d5d8265..f33debdfa 100644 --- a/components/fpspreadsheet/fpspreadsheetgrid.pas +++ b/components/fpspreadsheet/fpspreadsheetgrid.pas @@ -2975,10 +2975,6 @@ end; initial column widths and row heights. } procedure TsCustomWorksheetGrid.Setup; -var - i: Integer; - lCol: PCol; - lRow: PRow; begin if (FWorksheet = nil) or (FWorksheet.GetCellCount = 0) then begin if ShowHeaders then begin diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index 7b593f122..5613b2ba8 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -483,6 +483,8 @@ function ParseCellString(const AStr: String; out ACellRow, ACellCol: Cardinal; while (i <= Length(AStr)) do begin if (UpCase(AStr[i]) in LETTERS) then begin ACellCol := Cardinal(ord(UpCase(AStr[i])) - ord('A')) + 1 + ACellCol * 26; + if ACellCol >= MAX_COL_COUNT then // too many columns (dropping this limitation could cause overflow if a too long string is passed + exit; inc(i); end else diff --git a/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi b/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi index d6f438136..ae8bd0e7a 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi +++ b/components/fpspreadsheet/reference/BIFFExplorer/BIFFExplorer.lpi @@ -155,6 +155,7 @@ + diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas index 2ba663ed5..388c04cae 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas @@ -12,18 +12,6 @@ type TBIFFDetailsEvent = procedure(Sender: TObject; ADetails: TStrings) of object; - TBIFF2RichTextRun = packed record // valid up to BIFF5 - IndexToFirstChar: Byte; - FontIndex: Byte; - end; - - TBIFF8RichTextRun = packed record - IndexToFirstChar: Word; - FontIndex: Word; - end; - - TRichTextRuns = array of TBiff8RichTextRun; - TBIFFGrid = class(TStringGrid) private FRecType: Word; @@ -126,11 +114,7 @@ type procedure Click; override; procedure DoExtractDetails; procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; - out AString: String; out ANumBytes: Integer; out AOffsetToAsianPhoneticBlock: Integer; - out AsianPhoneticBlockSize: DWord; out ARichTextRuns: TRichTextRuns; - AIgnoreCompressedFlag: Boolean = false); overload; - procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; - out AString: String; out ANumBytes: Integer; AIgnoreCompressedFlag: Boolean = false); overload; + out AString: String; out ANumBytes: Integer; IgnoreCompressedFlag: Boolean = false); procedure PopulateGrid; procedure ShowInRow(var ARow: Integer; var AOffs: LongWord; ASize: Word; AValue,ADescr: String); procedure ShowRowColData(var ABufIndex: LongWord); @@ -147,7 +131,7 @@ type implementation uses - StrUtils, Math, lazutf8, + StrUtils, Math, fpsutils, beBIFFUtils; @@ -198,32 +182,19 @@ end; procedure TBIFFGrid.ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; - out AString: String; out ANumBytes: Integer; out AOffsetToAsianPhoneticBlock: Integer; - out AsianPhoneticBlockSize: DWord; out ARichTextRuns: TRichTextRuns; - AIgnoreCompressedFlag: Boolean = false); + out AString: String; out ANumBytes: Integer; IgnoreCompressedFlag: Boolean = false); var - i: Integer; ls: Integer; sa: ansiString; sw: WideString; w: Word; optn: Byte; - bytesPerChar: Byte; - containsAsianPhonetics: Boolean; - containsRichText: Boolean; - richTextCount: Word = 0; - savedBufIndex: Integer; begin - AString := ''; - ANumBytes := 0; - AOffsetToAsianPhoneticBlock := -1; - AsianPhoneticBlockSize := 0; - SetLength(ARichTextRuns, 0); - - if Length(FBuffer) = 0 then + if Length(FBuffer) = 0 then begin + AString := ''; + ANumBytes := 0; exit; - - savedBufIndex := ABufIndex; + end; if ALenBytes = 1 then ls := FBuffer[ABufIndex] else begin @@ -232,48 +203,18 @@ begin end; if AUnicode then begin optn := FBuffer[ABufIndex + ALenBytes]; - if (optn and $01 = 0) and (not AIgnoreCompressedFlag) then - bytesPerChar := 1 - else - bytesPerChar := 2; - containsAsianPhonetics := (optn and $04 <> 0); - containsRichText := (optn and $08 <> 0); - ABufIndex := ABufIndex + ALenBytes + 1; - if containsRichText then begin - Move(FBuffer[ABufIndex], richTextCount, 2); - richTextCount := WordLEToN(richTextCount); - inc(ABufIndex, 2); - end; - if containsAsianPhonetics then begin - Move(FBuffer[ABufIndex], AsianPhoneticBlockSize, 4); - AsianPhoneticBlockSize := DWordLEToN(AsianPhoneticBlockSize); - inc(ABufIndex, 4); - end; - if bytesPerChar = 1 then begin + if (optn and $01 = 0) and (not IgnoreCompressedFlag) + then begin // compressed --> 1 byte per character SetLength(sa, ls); - Move(FBuffer[ABufIndex], sa[1], ls*SizeOf(AnsiChar)); - inc(ABufIndex, ls*SizeOf(AnsiChar)); - AString := AnsiToUTF8(sa); + ANumbytes := ls*SizeOf(AnsiChar) + ALenBytes + 1; + Move(FBuffer[ABufIndex + ALenBytes + 1], sa[1], ls*SizeOf(AnsiChar)); + AString := sa; end else begin SetLength(sw, ls); - Move(FBuffer[ABufIndex], sw[1], ls*SizeOf(WideChar)); - inc(ABufIndex, ls*SizeOf(WideChar)); + ANumBytes := ls*SizeOf(WideChar) + ALenBytes + 1; + Move(FBuffer[ABufIndex + ALenBytes + 1], sw[1], ls*SizeOf(WideChar)); AString := UTF8Encode(WideStringLEToN(sw)); end; - if containsRichText then begin - SetLength(ARichTextRuns, richTextCount); - Move(FBuffer[ABufIndex], ARichTextRuns[0], richTextCount*SizeOf(TBiff8RichTextRun)); - for i:=0 to richTextCount-1 do begin - ARichTextRuns[i].IndexToFirstchar := WordLEToN(ARichTextRuns[i].IndexToFirstChar); - ARichTextRuns[i].FontIndex := WordLEToN(ARichTextRuns[i].FontIndex); - end; - inc(ABufIndex, richTextCount*SizeOf(word)); - end; - if containsAsianPhonetics then begin - AOffsetToAsianPhoneticBlock := ABufIndex; - inc(ABufIndex, AsianPhoneticBlockSize); - end; - ANumBytes := ABufIndex - savedBufIndex; end else begin SetLength(sa, ls); ANumBytes := ls*SizeOf(AnsiChar) + ALenBytes; @@ -282,17 +223,6 @@ begin end; end; -procedure TBIFFGrid.ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; - out AString: String; out ANumBytes: Integer; AIgnoreCompressedFlag: Boolean = false); -var - asianPhoneticBlockOffset: Integer; - asianPhoneticBlockSize: DWord; - richTextRuns: TRichTextRuns; -begin - ExtractString(ABufIndex, ALenBytes, AUnicode, AString, ANumBytes, - asianPhoneticBlockOffset, asianPhoneticBlockSize, richTextRuns, - AIgnoreCompressedFlag); -end; function TBIFFGrid.GetStringType: String; begin @@ -1575,7 +1505,6 @@ begin '(relict of BIFF5)'); end else begin ExtractString(FBufferIndex, 2, true, s, numBytes); - if Row = FCurrRow then begin FDetails.Add('Encoded URL without sheet name:'#13); case s[1] of @@ -4225,12 +4154,7 @@ var numBytes: Integer; s: String; total1, total2: DWord; - i, j: Integer; - asianPhoneticBlockOffset: Integer; - asianPhoneticBlockSize: DWord; - richTextRuns: TRichTextRuns; - dw: DWord; - b: Byte; + i: Integer; begin numBytes := 4; Move(FBuffer[FBufferIndex], total1, numBytes); @@ -4246,47 +4170,9 @@ begin 'Number of following strings'); for i:=1 to total2 do begin - ExtractString(FBufferIndex, 2, true, s, numBytes, asianPhoneticBlockOffset, - asianPhoneticBlockSize, richTextRuns); - if FFormat = sfExcel8 then begin - if Row = FCurrRow then begin - FDetails.Add('Wide string info:'#13); - FDetails.Add('2 length bytes: ' + IntToStr(UTF8Length(s))); - b := FBuffer[FBufferIndex+2]; - FDetails.Add('Options byte: ' + IntToStr(b)); - if b and $01 = 0 - then FDetails.Add(' Bit 1 = 0: compressed characters (8-bit characters)') - else FDetails.Add(' Bit 1 = 1: uncompressed characters (16-bit characters)'); - if b and $04 = 0 - then FDetails.Add(' Bit 4 = 0: Does not contain Asian phonetic settings') - else FDetails.Add(' Bit 4 = 1: Contains Asian phonetic settings'); - if b and $08 = 0 - then FDetails.Add(' Bit 8 = 0: Does not contain Rich-Text settings') - else FDetails.Add(' Bit 8 = 1: Contains Rich-Text settings'); - if Length(richTextRuns) > 0 then begin - FDetails.Add('Rich-Text information (2 bytes):'); - FDetails.Add(' ' +IntToStr(Length(richTextRuns)) + ' Rich-Text runs'); - end; - if asianPhoneticBlockSize > 0 then begin - FDetails.Add('Asian phonetic block size information (4 bytes): '); - FDetails.Add(' Block size: ' + IntToStr(AsianPhoneticBlockSize) + ' bytes'); - end; - FDetails.Add('String text: ' + s); - if Length(richTextRuns)>0 then begin - FDetails.Add('Rich text runs:'); - for j:=0 to High(richTextRuns) do - FDetails.Add(Format(' Rich text run #%d: binary data $%.4x --> index of first formatted character %d, font index %d', - [j, DWord(richTextRuns[j]), richTextRuns[j].IndexToFirstChar, richTextRuns[j].FontIndex])); - end; - if asianPhoneticBlockSize>0 then begin - FDetails.Add('Asian phonetic block:'); - FDetails.Add(' Size: ' + IntToStr(asianPhoneticBlockSize)); - FDetails.Add(' (not decoded)'); - end; - end; + ExtractString(FBufferIndex, 2, true, s, numBytes); ShowInRow(FCurrRow, FBufferIndex, numBytes, s, Format('Shared string #%d', [i])); end; - end; end; diff --git a/components/fpspreadsheet/tests/errortests.pas b/components/fpspreadsheet/tests/errortests.pas index d6cc4d630..7cbb6c90c 100644 --- a/components/fpspreadsheet/tests/errortests.pas +++ b/components/fpspreadsheet/tests/errortests.pas @@ -61,15 +61,14 @@ var row, col: Cardinal; row1, row2: Cardinal; col1, col2: Cardinal; - formula: TsFormula; + formula: string; s: String; TempFile: String; ErrList: TStringList; newColor: TsColor; expected: integer; begin - formula.FormulaStr := '=A1'; - formula.DoubleValue := 0.0; + formula := '=A1'; ErrList := TStringList.Create; try diff --git a/components/fpspreadsheet/tests/formulatests.pas b/components/fpspreadsheet/tests/formulatests.pas index 60bad8ec4..0e13ca03e 100644 --- a/components/fpspreadsheet/tests/formulatests.pas +++ b/components/fpspreadsheet/tests/formulatests.pas @@ -17,7 +17,7 @@ uses // Not using Lazarus package as the user may be working with multiple versions // Instead, add .. to unit search path Classes, SysUtils, fpcunit, testutils, testregistry, - fpsallformats, fpspreadsheet, fpsfunc, + fpsallformats, fpspreadsheet, fpsexprparser, xlsbiff8 {and a project requirement for lclbase for utf8 handling}, testsutility; @@ -31,27 +31,42 @@ type procedure SetUp; override; procedure TearDown; override; // Test formula strings - procedure TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat); + procedure TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat; + UseRPNFormula: Boolean); // Test calculation of rpn formulas - procedure TestCalcRPNFormulas(AFormat: TsSpreadsheetformat); + procedure TestCalcFormulas(AFormat: TsSpreadsheetformat; UseRPNFormula: Boolean); published // Writes out numbers & reads back. // If previous read tests are ok, this effectively tests writing. { BIFF2 Tests } - procedure TestWriteRead_BIFF2_FormulaStrings; + procedure Test_Write_Read_FormulaStrings_BIFF2; { BIFF5 Tests } - procedure TestWriteRead_BIFF5_FormulaStrings; + procedure Test_Write_Read_FormulaStrings_BIFF5; { BIFF8 Tests } - procedure TestWriteRead_BIFF8_FormulaStrings; + procedure Test_Write_Read_FormulaStrings_BIFF8; + { OOXML Tests } + procedure Test_Write_Read_FormulaStrings_OOXML; - // Writes out and calculates formulas, read back + // Writes out and calculates rpn formulas, read back { BIFF2 Tests } - procedure TestWriteRead_BIFF2_CalcRPNFormula; + procedure Test_Write_Read_CalcRPNFormula_BIFF2; { BIFF5 Tests } - procedure TestWriteRead_BIFF5_CalcRPNFormula; + procedure Test_Write_Read_CalcRPNFormula_BIFF5; { BIFF8 Tests } - procedure TestWriteRead_BIFF8_CalcRPNFormula; + procedure Test_Write_Read_CalcRPNFormula_BIFF8; + { OOXML Tests } + procedure Test_Write_Read_CalcRPNFormula_OOXML; + + // Writes out and calculates string formulas, read back + { BIFF2 Tests } + procedure Test_Write_Read_CalcStringFormula_BIFF2; + { BIFF5 Tests } + procedure Test_Write_Read_CalcStringFormula_BIFF5; + { BIFF8 Tests } + procedure Test_Write_Read_CalcStringFormula_BIFF8; + { OOXML Tests } + procedure Test_Write_Read_CalcStringFormula_OOXML; end; implementation @@ -59,6 +74,18 @@ implementation uses math, typinfo, lazUTF8, fpsUtils, rpnFormulaUnit; +var + // Array containing the "true" results of the formulas, for comparison + SollValues: array of TsExpressionResult; + +// Helper for statistics tests +const + STATS_NUMBERS: Array[0..4] of Double = (1.0, 1.1, 1.2, 0.9, 0.8); +var + numberArray: array[0..4] of Double; + + + { TSpreadWriteReadFormatTests } procedure TSpreadWriteReadFormulaTests.SetUp; @@ -71,7 +98,10 @@ begin inherited TearDown; end; -procedure TSpreadWriteReadFormulaTests.TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat); +procedure TSpreadWriteReadFormulaTests.TestWriteReadFormulaStrings( + AFormat: TsSpreadsheetFormat; UseRPNFormula: Boolean); +{ If UseRPNFormula is true the test formulas are generated from RPN formulas. + Otherwise they are generated from string formulas. } const SHEET = 'Sheet1'; var @@ -79,20 +109,29 @@ var MyWorkbook: TsWorkbook; Row: Integer; TempFile: string; //write xls/xml to this file and read back from it + formula: String; expected: String; actual: String; cell: PCell; + cellB1: Double; + cellB2: Double; + number: Double; + s: String; + hr, min, sec, msec: Word; + k: Integer; begin TempFile := GetTempFileName; // Create test workbook MyWorkbook := TsWorkbook.Create; try + MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving]; MyWorkSheet:= MyWorkBook.AddWorksheet(SHEET); // Write out all test formulas // All formulas are in column B - WriteRPNFormulaSamples(MyWorksheet, AFormat, true); + {$I testcases_calcrpnformula.inc} +// WriteRPNFormulaSamples(MyWorksheet, AFormat, true, UseRPNFormula); MyWorkBook.WriteToFile(TempFile, AFormat, true); finally MyWorkbook.Free; @@ -113,8 +152,8 @@ begin for Row := 0 to MyWorksheet.GetLastRowIndex do begin cell := MyWorksheet.FindCell(Row, 1); - if (cell <> nil) and (Length(cell^.RPNFormulaValue) > 0) then begin - actual := MyWorksheet.ReadRPNFormulaAsString(cell); + if HasFormula(cell) then begin + actual := MyWorksheet.ReadFormulaAsString(cell); expected := MyWorksheet.ReadAsUTF8Text(Row, 0); CheckEquals(expected, actual, 'Test read formula mismatch, cell '+CellNotation(MyWorkSheet,Row,1)); end; @@ -126,40 +165,46 @@ begin end; end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_FormulaStrings; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF2; begin - TestWriteReadFormulaStrings(sfExcel2); + TestWriteReadFormulaStrings(sfExcel2, true); end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_FormulaStrings; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF5; begin - TestWriteReadFormulaStrings(sfExcel5); + TestWriteReadFormulaStrings(sfExcel5, true); end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_FormulaStrings; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF8; begin - TestWriteReadFormulaStrings(sfExcel8); + TestWriteReadFormulaStrings(sfExcel8, true); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_OOXML; +begin + TestWriteReadFormulaStrings(sfOOXML, true); end; -{ Test calculation of rpn formulas } +{ Test calculation of formulas } -procedure TSpreadWriteReadFormulaTests.TestCalcRPNFormulas(AFormat: TsSpreadsheetFormat); +procedure TSpreadWriteReadFormulaTests.TestCalcFormulas(AFormat: TsSpreadsheetFormat; + UseRPNFormula: Boolean); +{ If UseRPNFormula is TRUE, the test formulas are generated from RPN syntax, + otherwise string formulas are used. } const SHEET = 'Sheet1'; - STATS_NUMBERS: Array[0..4] of Double = (1.0, 1.1, 1.2, 0.9, 0.8); var MyWorksheet: TsWorksheet; MyWorkbook: TsWorkbook; Row: Integer; TempFile: string; //write xls/xml to this file and read back from it - actual: TsArgument; - expected: TsArgument; + actual: TsExpressionResult; + expected: TsExpressionResult; cell: PCell; - sollValues: array of TsArgument; + sollValues: array of TsExpressionResult; formula: String; s: String; - t: TTime; hr,min,sec,msec: Word; ErrorMargin: double; k: Integer; @@ -168,7 +213,8 @@ var the formula calculation as well. The next variables, along with STATS_NUMBERS above, hold the arguments for the direction function calls. } number: Double; - numberArray: array[0..4] of Double; + cellB1: Double; + cellB2: Double; begin ErrorMargin:=0; //1.44E-7; //1.44E-7 for SUMSQ formula @@ -200,6 +246,7 @@ begin // Open the workbook MyWorkbook := TsWorkbook.Create; try + MyWorkbook.Options := Myworkbook.Options + [boReadFormulas]; MyWorkbook.ReadFromFile(TempFile, AFormat); if AFormat = sfExcel2 then MyWorksheet := MyWorkbook.GetFirstWorksheet @@ -214,15 +261,24 @@ begin cell := MyWorksheet.FindCell(Row, 1); if (cell = nil) then fail('Error in test code: Failed to get cell ' + CellNotation(MyWorksheet, Row, 1)); + case cell^.ContentType of - cctBool : actual := CreateBoolArg(cell^.BoolValue); - cctNumber : actual := CreateNumberArg(cell^.NumberValue); - cctError : actual := CreateErrorArg(cell^.ErrorValue); - cctUTF8String : actual := CreateStringArg(cell^.UTF8StringValue); + cctBool : actual := BooleanResult(cell^.BoolValue); + cctNumber : actual := FloatResult(cell^.NumberValue); + cctDateTime : actual := DateTimeResult(cell^.DateTimeValue); + cctUTF8String : actual := StringResult(cell^.UTF8StringValue); + cctError : actual := ErrorResult(cell^.ErrorValue); + cctEmpty : actual := EmptyResult; else fail('ContentType not supported'); end; + expected := SollValues[row]; - CheckEquals(ord(expected.ArgumentType), ord(actual.ArgumentType), + // Cell does not store integers! + if expected.ResultType = rtInteger then expected := FloatResult(expected.ResInteger); + + CheckEquals( + GetEnumName(TypeInfo(TsExpressionResult), ord(expected.ResultType)), + GetEnumName(TypeInfo(TsExpressionResult), ord(actual.ResultType)), 'Test read calculated formula data type mismatch, formula "' + formula + '", cell '+CellNotation(MyWorkSheet,Row,1)); @@ -231,22 +287,22 @@ begin // the file value in the same second. Therefore we neglect the milliseconds. if formula = '=NOW()' then begin // Round soll value to seconds - DecodeTime(expected.NumberValue, hr,min,sec,msec); - expected.NumberValue := EncodeTime(hr, min, sec, 0); + DecodeTime(expected.ResDateTime, hr,min,sec,msec); + expected.ResDateTime := EncodeTime(hr, min, sec, 0); // Round formula value to seconds - DecodeTime(actual.NumberValue, hr,min,sec,msec); - actual.NumberValue := EncodeTime(hr,min,sec,0); + DecodeTime(actual.ResDateTime, hr,min,sec,msec); + actual.ResDateTime := EncodeTime(hr,min,sec,0); end; - case actual.ArgumentType of - atBool: - CheckEquals(BoolToStr(expected.BoolValue), BoolToStr(actual.BoolValue), + case actual.ResultType of + rtBoolean: + CheckEquals(BoolToStr(expected.ResBoolean), BoolToStr(actual.ResBoolean), 'Test read calculated formula result mismatch, formula "' + formula + '", cell '+CellNotation(MyWorkSheet,Row,1)); - atNumber: + rtFloat: {$if (defined(mswindows)) or (FPC_FULLVERSION>=20701)} // FPC 2.6.x and trunk on Windows need this, also FPC trunk on Linux x64 - CheckEquals(expected.NumberValue, actual.NumberValue, ErrorMargin, + CheckEquals(expected.ResFloat, actual.ResFloat, ErrorMargin, 'Test read calculated formula result mismatch, formula "' + formula + '", cell '+CellNotation(MyWorkSheet,Row,1)); {$else} @@ -255,14 +311,14 @@ begin 'Test read calculated formula result mismatch, formula "' + formula + '", cell '+CellNotation(MyWorkSheet,Row,1)); {$endif} - atString: - CheckEquals(expected.StringValue, actual.StringValue, + rtString: + CheckEquals(expected.ResString, actual.ResString, 'Test read calculated formula result mismatch, formula "' + formula + '", cell '+CellNotation(MyWorkSheet,Row,1)); - atError: + rtError: CheckEquals( - GetEnumName(TypeInfo(TsErrorValue), ord(expected.ErrorValue)), - GetEnumname(TypeInfo(TsErrorValue), ord(actual.ErrorValue)), + GetEnumName(TypeInfo(TsErrorValue), ord(expected.ResError)), + GetEnumname(TypeInfo(TsErrorValue), ord(actual.ResError)), 'Test read calculated formula error value mismatch, formula ' + formula + ', cell '+CellNotation(MyWorkSheet,Row,1)); end; @@ -274,19 +330,44 @@ begin end; end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_CalcRPNFormula; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF2; begin - TestCalcRPNFormulas(sfExcel2); + TestCalcFormulas(sfExcel2, true); end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_CalcRPNFormula; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF5; begin - TestCalcRPNFormulas(sfExcel5); + TestCalcFormulas(sfExcel5, true); end; -procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_CalcRPNFormula; +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF8; begin - TestCalcRPNFormulas(sfExcel8); + TestCalcFormulas(sfExcel8, true); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_OOXML; +begin + TestCalcFormulas(sfOOXML, true); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF2; +begin + TestCalcFormulas(sfExcel2, false); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF5; +begin + TestCalcFormulas(sfExcel5, false); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF8; +begin + TestCalcFormulas(sfExcel8, false); +end; + +procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_OOXML; +begin + TestCalcFormulas(sfOOXML, false); end; diff --git a/components/fpspreadsheet/tests/internaltests.pas b/components/fpspreadsheet/tests/internaltests.pas index ad55d1e35..509824630 100644 --- a/components/fpspreadsheet/tests/internaltests.pas +++ b/components/fpspreadsheet/tests/internaltests.pas @@ -350,7 +350,8 @@ begin CheckEquals('$XFE$1',GetCellString(0,16384,[])); // the first column beyond xlsx // Something VERY big, beyond xlsx - s := 'ZZZZ1'; +// s := 'ZZZZ1'; // this is case is no longer possible because max column count has been cut down to 65536 + s := 'CRAA1'; ParseCellString(s, r, c, flags); CheckEquals(s, GetCellString(r, c, flags)); end; diff --git a/components/fpspreadsheet/tests/manualtests.pas b/components/fpspreadsheet/tests/manualtests.pas index bbc513e49..730737ba7 100644 --- a/components/fpspreadsheet/tests/manualtests.pas +++ b/components/fpspreadsheet/tests/manualtests.pas @@ -58,6 +58,8 @@ type // 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. procedure TestRPNFormula; + // Dto, but writes string formulas. +// procedure TestStringFormula; {$ENDIF} // For BIFF8 format, writes all background colors in A1..A16 procedure TestBiff8CellBackgroundColor; @@ -69,8 +71,9 @@ uses fpsUtils, rpnFormulaUnit; const - COLORSHEETNAME='colorsheet'; //for background color tests - RPNSHEETNAME='formula_sheet'; //for rpn formula tests + COLORSHEETNAME='color_sheet'; //for background color tests + RPNSHEETNAME='rpn_formula_sheet'; //for rpn formula tests + FORMULASHEETNAME='formula_sheet'; // for string formula tests OUTPUT_FORMAT = sfExcel8; //change manually if you want to test different formats. To do: automatically output all formats var @@ -195,7 +198,7 @@ begin Worksheet := Workbook.AddWorksheet(COLORSHEETNAME); WorkSheet.WriteUTF8Text(0,1,'TSpreadManualTests.TestBiff8CellBackgroundColor'); - RowOffset:=1; + RowOffset := 1; for i:=0 to Workbook.GetPaletteSize-1 do begin WorkSheet.WriteUTF8Text(i+RowOffset,0,'BACKGROUND COLOR TEST'); Cell := Worksheet.GetCell(i+RowOffset, 0); @@ -218,6 +221,18 @@ begin Worksheet := Workbook.AddWorksheet(RPNSHEETNAME); WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false); end; + (* +procedure TSpreadManualTests.TestStringFormula; +var + Worksheet: TsWorksheet; +begin + if Workbook = nil then + Workbook := TsWorkbook.Create; + + Worksheet := Workbook.AddWorksheet(FORMULASHEETNAME); + WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false, false); +end; +*) {$ENDIF} initialization diff --git a/components/fpspreadsheet/tests/rpnformulaunit.pas b/components/fpspreadsheet/tests/rpnformulaunit.pas index e5f380297..2323fab84 100644 --- a/components/fpspreadsheet/tests/rpnformulaunit.pas +++ b/components/fpspreadsheet/tests/rpnformulaunit.pas @@ -150,7 +150,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange(cellAddr, - RPNFunc(fekCOUNT, 1, // 1 parameter used in COUNT + RPNFunc('COUNT', 1, // 1 parameter used in COUNT nil )))); Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical! @@ -161,7 +161,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange(cellAddr, - RPNFunc(fekCOUNT, 1, + RPNFunc('COUNT', 1, nil )))); Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alph-numerical! @@ -172,7 +172,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange(cellAddr, - RPNFunc(fekCOUNT, 1, + RPNFunc('COUNT', 1, nil )))); Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical! @@ -486,7 +486,7 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=TRUE()'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekTRUE, + RPNFunc('TRUE', nil))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[true]); @@ -494,7 +494,7 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=FALSE()'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekFALSE, + RPNFunc('FALSE', nil))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[false]); @@ -505,8 +505,8 @@ begin Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('C1', RPNCellValue('C1', - RPNFunc(fekEQUAL, - RPNFunc(fekNOT, + RPNFunc(fekEqual, + RPNFunc('NOT', nil)))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[not (cellC1=cellC1)]); @@ -520,7 +520,7 @@ begin RPNNumber(1, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekAND, 2, + RPNFunc('AND', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (1=2)]); @@ -534,7 +534,7 @@ begin RPNNumber(2, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekAND, 2, + RPNFunc('AND', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (2=2)]); @@ -548,7 +548,7 @@ begin RPNNumber(2, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekAND, 2, + RPNFunc('AND', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) and (2=2)]); @@ -562,7 +562,7 @@ begin RPNNumber(1, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekOR, 2, + RPNFunc('OR', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (1=2)]); @@ -576,7 +576,7 @@ begin RPNNumber(2, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekOR, 2, + RPNFunc('OR', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (2=2)]); @@ -590,7 +590,7 @@ begin RPNNumber(2, RPNNumber(2, RPNFunc(fekEQUAL, - RPNFunc(fekOR, 2, + RPNFunc('OR', 2, nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) or (2=2)]); @@ -603,7 +603,7 @@ begin RPNFunc(fekEQUAL, RPNString('correct', RPNString('wrong', - RPNFunc(fekIF, 3, + RPNFunc('IF', 3, nil)))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'wrong')); @@ -616,7 +616,7 @@ begin RPNFunc(fekNotEQUAL, RPNString('correct', RPNString('wrong', - RPNFunc(fekIF, 3, + RPNFunc('IF', 3, nil)))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'wrong')); @@ -628,7 +628,7 @@ begin RPNNumber(1, RPNFunc(fekEQUAL, RPNString('correct', - RPNFunc(fekIF, 2, + RPNFunc('IF', 2, nil))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'FALSE')); @@ -640,7 +640,7 @@ begin RPNNumber(1, RPNFunc(fekNotEQUAL, RPNString('correct', - RPNFunc(fekIF, 2, + RPNFunc('IF', 2, nil))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'FALSE')); @@ -656,7 +656,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=ABS($B1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B1', - RPNFunc(fekABS, + RPNFunc('ABS', nil)))); Worksheet.WriteNumber(Row, 2, abs(cellB1)); @@ -665,7 +665,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=ABS(E$1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('E$1', - RPNFunc(fekABS, + RPNFunc('ABS', nil)))); Worksheet.WriteNumber(Row, 2, abs(cellE1)); @@ -674,7 +674,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=SIGN(F1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('F1', - RPNFunc(fekSIGN, + RPNFunc('SIGN', nil)))); Worksheet.WriteNumber(Row, 2, sign(cellF1)); @@ -683,7 +683,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=SIGN(0)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(0, - RPNFunc(fekSIGN, + RPNFunc('SIGN', nil)))); Worksheet.WriteNumber(Row, 2, sign(0)); @@ -692,7 +692,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=SIGN(G1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('G1', - RPNFunc(fekSIGN, + RPNFunc('SIGN', nil)))); Worksheet.WriteNumber(Row, 2, sign(cellG1)); @@ -700,7 +700,7 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=RAND()'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekRAND, + RPNFunc('RAND', nil))); Worksheet.WriteUTF8Text(Row, 2, '(random number - cannot compare)'); @@ -708,7 +708,7 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=PI()'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, + RPNFunc('PI', nil))); Worksheet.WriteNumber(Row, 2, pi); @@ -718,10 +718,10 @@ begin value := pi/2; Worksheet.WriteUTF8Text(Row, 0, '=DEGREES(PI()/2)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, + RPNFunc('PI', RPNNumber(2, RPNFunc(fekDIV, - RPNFunc(fekDEGREES, + RPNFunc('DEGREES', nil)))))); Worksheet.WriteNumber(Row, 2, value/pi*180); @@ -731,7 +731,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=RADIANS(90)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekRADIANS, + RPNFunc('RADIANS', nil)))); Worksheet.WriteNumber(Row, 2, value/180*pi); end; @@ -740,10 +740,10 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=SIN(PI()/2)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, + RPNFunc('PI', RPNNumber(2, RPNFunc(fekDIV, - RPNFunc(fekSIN, + RPNFunc('SIN', nil)))))); Worksheet.WriteNumber(Row, 2, sin(pi/2)); @@ -753,7 +753,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=ASIN(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekASIN, + RPNFunc('ASIN', nil)))); Worksheet.WriteNumber(Row, 2, arcsin(value)); @@ -761,8 +761,8 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=COS(PI())'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, - RPNFunc(fekCOS, + RPNFunc('PI', + RPNFunc('COS', nil)))); Worksheet.WriteNumber(Row, 2, cos(pi)); @@ -772,7 +772,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=ACOS(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekACOS, + RPNFunc('ACOS', nil)))); Worksheet.WriteNumber(Row, 2, arccos(value)); @@ -780,10 +780,10 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=TAN(PI()/4)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, + RPNFunc('PI', RPNNumber(4, RPNFunc(fekDiv, - RPNFunc(fekTAN, + RPNFunc('TAN', nil)))))); Worksheet.WriteNumber(Row, 2, tan(pi/4)); @@ -793,7 +793,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=ATAN(1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekATAN, + RPNFunc('ATAN', nil)))); Worksheet.WriteNumber(Row, 2, arctan(1.0)); @@ -806,7 +806,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=SINH(3)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekSINH, + RPNFunc('SINH', nil)))); Worksheet.WriteNumber(Row, 2, sinh(value)); @@ -816,7 +816,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=ASINH(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekASINH, + RPNFunc('ASINH', nil)))); Worksheet.WriteNumber(Row, 2, arcsinh(value)); @@ -826,7 +826,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=COSH(3)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekCOSH, + RPNFunc('COSH', nil)))); Worksheet.WriteNumber(Row, 2, cosh(value)); @@ -836,7 +836,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=ACOSH(10)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekACOSH, + RPNFunc('ACOSH', nil)))); Worksheet.WriteNumber(Row, 2, arccosh(value)); @@ -846,7 +846,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=TANH(3)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekTANH, + RPNFunc('TANH', nil)))); Worksheet.WriteNumber(Row, 2, tanh(value)); @@ -856,7 +856,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=ATANH(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekATANH, + RPNFunc('ATANH', nil)))); Worksheet.WriteNumber(Row, 2, arctanh(value)); end; @@ -867,7 +867,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=SQRT(2)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekSQRT, + RPNFunc('SQRT', nil)))); Worksheet.WriteNumber(Row, 2, sqrt(value)); @@ -877,7 +877,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=EXP(2)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekEXP, + RPNFunc('EXP', nil)))); Worksheet.WriteNumber(Row, 2, exp(value)); @@ -887,7 +887,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=LN(2)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekLN, + RPNFunc('LN', nil)))); Worksheet.WriteNumber(Row, 2, ln(value)); @@ -898,7 +898,7 @@ begin Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNNumber(2, - RPNFunc(fekLOG, 2, + RPNFunc('LOG', 2, nil))))); Worksheet.WriteNumber(Row, 2, logn(2.0, value)); @@ -908,7 +908,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=LOG10(100)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekLOG10, + RPNFunc('LOG10', nil)))); Worksheet.WriteNumber(Row, 2, log10(value)); @@ -918,7 +918,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, Format('=LOG10(%.2f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, - RPNFunc(fekLOG10, + RPNFunc('LOG10', nil)))); Worksheet.WriteNumber(Row, 2, log10(value)); @@ -935,7 +935,7 @@ begin Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$F$1', RPNNumber(1, - RPNFunc(fekROUND, + RPNFunc('ROUND', nil))))); Worksheet.WriteNumber(Row, 2, Round(cellF1*10)/10); //RoundTo(cellF1, 1)); @@ -945,7 +945,7 @@ begin Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('G1', RPNNumber(1, - RPNFunc(fekROUND, + RPNFunc('ROUND', nil))))); Worksheet.WriteNumber(Row, 2, Round(cellG1*10)/10); //RoundTo(cellG1, 1)); @@ -954,7 +954,7 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=INT(F1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('F1', - RPNFunc(fekINT, + RPNFunc('INT', nil)))); Worksheet.WriteNumber(Row, 2, trunc(cellF1)); @@ -963,10 +963,10 @@ begin Worksheet.WriteUTF8Text(Row, 0, '=INT(G1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('G1', - RPNFunc(fekINT, + RPNFunc('INT', nil)))); Worksheet.WriteNumber(Row, 2, floor(cellG1)); // is this true? - + (* { ---------- } inc(Row); @@ -2012,6 +2012,7 @@ begin Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A!'); Worksheet.WriteUTF8Text(Row, 3, 'Should be "=1/2", but there are too many operands...'); end; + *) end; end. diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index 873fd93e2..251664efc 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -56,14 +56,17 @@ + + + @@ -72,11 +75,11 @@ + - diff --git a/components/fpspreadsheet/tests/testbiff8_1899.xls b/components/fpspreadsheet/tests/testbiff8_1899.xls index c73994677..c4af4f2e4 100644 Binary files a/components/fpspreadsheet/tests/testbiff8_1899.xls and b/components/fpspreadsheet/tests/testbiff8_1899.xls differ diff --git a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc index a21c0aa9e..6b7531337 100644 --- a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc +++ b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc @@ -15,13 +15,19 @@ // Addition Row := 0; - MyWorksheet.WriteUTF8Text(Row, 0, '=1+1'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.0, - RPNFunc(fekAdd, nil))))); + formula := '1+1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.0, + RPNFunc(fekAdd, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1.0 + 1.0); // B1 = 2 + sollValues[Row] := FloatResult(1 + 1); // B1 = 2 + cellB1 := 1 + 1; // don't refer to the cell contents here because they have not yet been calculated! {!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! DO NOT CHANGE THIS FORMULA - ITS RESULT (-9) IS HARD-CODED IN OTHER TESTS !! @@ -29,13 +35,19 @@ // Subtraction inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=1-10'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNNumber(10, - RPNFunc(fekSub, nil))))); + formula := '1-10'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(10.0, + RPNFunc(fekSub, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, -9); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1-10); // B2 = -9 + sollValues[Row] := FloatResult(1 - 10); // B2 = -9 + cellB2 := 1 - 10; // don't refer to the cell contents here because they have not yet been calculated! {!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! DO NOT CHANGE THIS FORMULA - ITS RESULT (-9) IS HARD-CODED IN OTHER TESTS !! @@ -43,826 +55,1341 @@ // Add cell values - relative addresses inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=B1+B2'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNCellValue('B2', - RPNFunc(fekAdd, nil))))); + formula := 'B1+B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNCellValue('B2', + RPNFunc(fekAdd, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 + cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(-7); + sollValues[Row] := FloatResult(cellB1 + cellB2); // Add cell values - absolute addresses inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=$B$1+$B$2'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('$B$1', - RPNCellValue('$B$2', - RPNFunc(fekAdd, nil))))); + formula := '$B$1+$B$2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('$B$1', + RPNCellValue('$B$2', + RPNFunc(fekAdd, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 + cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(-7); + sollValues[Row] := FloatResult(cellB1 + cellB2); // don't refer to the cell contents here because they have not yet been calculated! // Add cell values - mixed absolute and relative addresses inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=B$1+$B2'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B$1', - RPNCellValue('$B2', - RPNFunc(fekAdd, nil))))); + formula := 'B$1+$B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B$1', + RPNCellValue('$B2', + RPNFunc(fekAdd, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 + cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(-7); + sollValues[Row] := FloatResult(cellB1 + cellB2); // don't refer to the cell contents here because they have not yet been calculated! - // Multiplication + // Subtract cell values - relative addresses inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=10*-3'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(10, - RPNNumber(-3, - RPNFunc(fekMul, nil))))); + formula := 'B1-B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNCellValue('B2', + RPNFunc(fekSub, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 - cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(10*(-3)); + sollValues[Row] := FloatResult(cellB1 - cellB2); + + // Subtract cell values - absolute addresses + inc(Row); + formula := '$B$1-$B$2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('$B$1', + RPNCellValue('$B$2', + RPNFunc(fekSub, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 - cellB2); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(cellB1 - cellB2); + + // Subtract cell values - mixed absolute and relative addresses + inc(Row); + formula := 'B$1-$B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B$1', + RPNCellValue('$B2', + RPNFunc(fekSub, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 - cellB2); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(cellB1 - cellB2); + // don't refer to the cell contents here because they have not yet been calculated! + + // Multiplication w/o Parenthesis + inc(Row); + formula := '10*-3'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(10.0, + RPNNumber(-3.0, + RPNFunc(fekMul, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 10*(-3)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(10*(-3)); // Multiplication w/Parenthesis inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=10*(-3)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(10, - RPNNumber(-3, - RPNParenthesis( - RPNFunc(fekMul, nil)))))); + formula := '10*(-3)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(10.0, + RPNNumber(-3.0, + RPNParenthesis( + RPNFunc(fekMul, nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 10*(-3)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(10*(-3)); + sollValues[Row] := FloatResult(10*(-3)); // Multiplication of cell values inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=B1*B2'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNCellValue('B2', - RPNFunc(fekMul, nil))))); + formula := 'B1*B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNCellValue('B2', + RPNFunc(fekMul, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 * cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2*(-9)); + sollValues[Row] := FloatResult(cellB1 * cellB2); // Division inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=10/200'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(10, - RPNNumber(200, - RPNFunc(fekDiv, nil))))); + formula := '10/200'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(10.0, + RPNNumber(200.0, + RPNFunc(fekDiv, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 10/200); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(10/200); + sollValues[Row] := FloatResult(10/200); // Division: Error case - divide by zero inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=10/0'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(10, - RPNNumber(0, - RPNFunc(fekDiv, nil))))); + formula := '10/0'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(10.0, + RPNNumber(0, + RPNFunc(fekDiv, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteErrorValue(Row, 2, errDivideByZero); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errDivideByZero); + sollValues[Row] := ErrorResult(errDivideByZero); // Division of cell values inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=B1/B2'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNcellValue('B2', - RPNFunc(fekDiv, nil))))); + formula := 'B1/B2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNcellValue('B2', + RPNFunc(fekDiv, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, cellB1 / cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2/(-9)); + sollValues[Row] := FloatResult(cellB1 / cellB2); // Percentage inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=10%'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(10, - RPNFunc(fekPercent, nil)))); + formula := '10%'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(10.0, + RPNFunc(fekPercent, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 10*0.01); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(10*0.01); + sollValues[Row] := FloatResult(10*0.01); // Percentage of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=B1%'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekPercent, nil)))); + formula := 'B1%'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc(fekPercent, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, cellB1*0.01); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2*0.01); + sollValues[Row] := FloatResult(cellB1*0.01); - // Power + // Power symbol inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=power(2.0,0.5)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2.0, - RPNNumber(0.5, - RPNFunc(fekPower, nil))))); + formula := '2^0.5'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2.0, + RPNNumber(0.5, + RPNFunc(fekPower, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, power(2, 0.5)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(power(2, 0.5)); + sollValues[Row] := FloatResult(power(2, 0.5)); - // Power of cell values + // Power symbol - precedence of operations inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=power(B1,B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNCellValue('B2', - RPNFunc(fekPower, nil))))); + formula := '2^0.5*4'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2.0, + RPNNumber(0.5, + RPNFunc(fekPower, + RPNNumber(4, + RPNFunc(fekMul, nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, power(2, 0.5)*4); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(power(2, -9)); + sollValues[Row] := FloatResult(power(2, 0.5)*4); + + // Power symbol - precedence of operators + inc(Row); + formula := '4*2^0.5'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(4.0, + RPNNumber(2.0, + RPNNumber(0.5, + RPNFunc(fekPower, + RPNFunc(fekMul, nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 4 * power(2, 0.5)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(4 * power(2, 0.5)); + + // Power function + if AFormat <> sfExcel2 then begin + // Power of constant + inc(Row); + formula := 'POWER(2,0.5)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2.0, + RPNNumber(0.5, + RPNFunc('POWER', nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, power(2, 0.5)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(power(2, 0.5)); + + // Power of cell values + inc(Row); + formula := 'POWER(B1,B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNCellValue('B2', + RPNFunc('POWER', nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, power(cellB1, cellB2)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(power(cellB1, cellB2)); + end; {$IFDEF ENABLE_CALC_RPN_EXCEPTIONS} // Power: Error case "power( (negative number), (fractional number) )" inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=power(-2.0, 0.5)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-2.0, - RPNNumber(0.5, - RPNFunc(fekPower, nil))))); + formula := 'POWER(-2.0, 0.5)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-2.0, + RPNNumber(0.5, + RPNFunc('POWER', nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + with sollValues[Row] do begin + ResultType := rtError; + ResError := errOverflow; + end; {$ENDIF} // Unary minus inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=-(-1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-1, - RPNParenthesis( - RPNFunc(fekUMinus, nil))))); + formula := '-(-1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNformula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-1.0, + RPNParenthesis( + RPNFunc(fekUMinus, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, -(-1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(-(-1)); // Unary minus of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=-B1'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekUMinus, nil)))); + formula := '-B1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc(fekUMinus, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, -cellB1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(-(2)); + sollValues[Row] := FloatResult(-cellB1); // Unary plus inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=+(-1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-1, - RPNParenthesis( - RPNFunc(fekUPlus, nil))))); + formula := '+(-1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-1.0, + RPNParenthesis( + RPNFunc(fekUPlus, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, -1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(-1); + sollValues[Row] := FloatResult(-1); - // Unary plus of cell value + // Unary plus of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=+(B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellvalue('B2', - RPNFunc(fekUPlus, nil)))); + formula := '+(B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellvalue('B2', + RPNParenthesis(( + RPNFunc(fekUPlus, nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, +cellB2); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(+(-9)); + sollValues[Row] := FloatResult(+cellB2); // String result inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '="Hallo"'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', nil))); + formula := '"Hallo"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', nil))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'Hallo'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('Hallo'); + sollValues[Row] := StringResult('Hallo'); // String concatenation inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '="Hallo"&" world"'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', - RPNString(' world', - RPNFunc(fekConcat, nil))))); + formula := '"Hallo"&" world"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', + RPNString(' world', + RPNFunc(fekConcat, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'Hallo' + ' world'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('Hallo' + ' world'); + sollValues[Row] := StringResult('Hallo' + ' world'); // Equal (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(true=false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNFunc(fekEqual, nil))))); + formula := 'TRUE=FALSE'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc(fekEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true = false); + sollValues[Row] := BooleanResult(true = false); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Equal (strings) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=("Hallo"="world")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', - RPNString('world', - RPNFunc(fekEqual, nil))))); + formula := '"Hallo"="world"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', + RPNString('world', + RPNFunc(fekEqual, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg('Hallo' = 'world'); + sollValues[Row] := BooleanResult('Hallo' = 'world'); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Equal (numbers) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(1=1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.0, - RPNFunc(fekEqual, nil))))); + formula := '1=1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(1, + RPNFunc(fekEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(1=1); + sollValues[Row] := BooleanResult(1 = 1); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Equal (cell) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M1=1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M1', - RPNNumber(1.0, - RPNFunc(fekEqual, nil))))); + formula := 'M1=1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M1', + RPNNumber(1, + RPNFunc(fekEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M1 is "A" + sollValues[Row] := BooleanResult(false); // M1 is "A" + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2=1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNNumber(1.0, - RPNFunc(fekEqual, nil))))); + formula := 'M2=1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNNumber(1.0, + RPNFunc(fekEqual, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); // M2 is 1 + sollValues[Row] := BooleanResult(true); // M2 is 1 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2=N2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNCellValue('N2', - RPNFunc(fekEqual, nil))))); + formula := 'M2=N2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNCellValue('N2', + RPNFunc(fekEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M2 is 1, N2 is 2 + sollValues[Row] := BooleanResult(false); // M2 is 1, N2 is 2 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(true>false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNFunc(fekGreater, nil))))); + formula := 'TRUE>FALSE'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc(fekGreater, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true > false); + sollValues[Row] := BooleanResult(true > false); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater (strings) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=("Hallo">"world")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', - RPNString('world', - RPNFunc(fekGreater, nil))))); + formula := '"Hallo">"world"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', + RPNString('world', + RPNFunc(fekGreater, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg('Hallo' > 'world'); + sollValues[Row] := BooleanResult('Hallo' > 'world'); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater (numbers) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(1>1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.0, - RPNFunc(fekGreater, nil))))); + formula := '1>1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(1, + RPNFunc(fekGreater, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(1>1); + sollValues[Row] := BooleanResult(1 > 1); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater (cell) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2>1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNNumber(1.0, - RPNFunc(fekGreater, nil))))); + formula := 'M2>1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNNumber(1, + RPNFunc(fekGreater, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteBoolvalue(Row, 2, SollValues[Row].ResBoolean); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M2 is 1 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2>N2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNCellValue('N2', - RPNFunc(fekGreater, nil))))); + formula := 'M2>N2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNCellValue('N2', + RPNFunc(fekGreater, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M2 is 1, N2 is 2 + sollValues[Row] := BooleanResult(false); // M2 is 1, N2 is 2 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater equal (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(true>=false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNParenthesis( - RPNFunc(fekGreaterEqual, nil)))))); + formula := 'TRUE>=FALSE'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc(fekGreaterEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true >= false); + sollValues[Row] := BooleanResult(true >= false); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater equal (strings) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=("Hallo">="world")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', - RPNString('world', - RPNParenthesis( - RPNFunc(fekGreaterEqual, nil)))))); + formula := '"Hallo">="world"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', + RPNString('world', + RPNFunc(fekGreaterEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg('Hallo' >= 'world'); + sollValues[Row] := BooleanResult('Hallo' >= 'world'); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater equal (numbers) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(1>=1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.0, - RPNParenthesis( - RPNFunc(fekGreaterEqual, nil)))))); + formula := '1>=1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(1, + RPNFunc(fekGreaterEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(1>=1); + sollValues[Row] := BooleanResult(1 >= 1); + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Greater equal(cell) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2>=1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNNumber(1.0, - RPNFunc(fekGreaterEqual, nil))))); + formula := 'M2>=1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNNumber(1, + RPNFunc(fekGreaterEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); // M2 is 1 + sollValues[Row] := BooleanResult(true); // M2 is 1 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2>=N2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNCellValue('N2', - RPNFunc(fekGreaterEqual, nil))))); + formula := 'M2>=N2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNCellValue('N2', + RPNFunc(fekGreaterEqual, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M2 is 1, N2 is 2 + sollValues[Row] := BooleanResult(false); // M2 is 1, N2 is 2 + Myworksheet.WriteUTF8Text(Row, 2, BoolToStr(SollValues[Row].ResBoolean, 'TRUE','FALSE')); // Less (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(truefalse)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNParenthesis( - RPNFunc(fekNotEqual, nil)))))); + formula := 'TRUE<>FALSE'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc(fekNotEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true <> false); + sollValues[Row] := BooleanResult(true <> false); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); // Not equal (strings) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=("Hallo"<>"world")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo', - RPNString('world', - RPNParenthesis( - RPNFunc(fekNotEqual, nil)))))); + formula := '"Hallo"<>"world"'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo', + RPNString('world', + RPNFunc(fekNotEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg('Hallo' <> 'world'); + sollValues[Row] := BooleanResult('Hallo' <> 'world'); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); // Not equal (numbers) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(1<>1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.0, - RPNParenthesis( - RPNFunc(fekNotEqual, nil)))))); + formula := '1<>1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(1, + RPNFunc(fekNotEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(1<>1); + sollValues[Row] := BooleanResult(1 <> 1); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); // Not equal (cell) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2<>1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNNumber(1.0, - RPNFunc(fekNotEqual, nil))))); + formula := 'M2<>1'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNNumber(1, + RPNFunc(fekNotEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // M2 is 1 + sollValues[Row] := BooleanResult(false); // M2 is 1 + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=(M2<>N2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('M2', - RPNCellValue('N2', - RPNFunc(fekNotEqual, nil))))); + formula := 'M2<>N2'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('M2', + RPNCellValue('N2', + RPNFunc(fekNotEqual, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); // M2 is 1, N2 is 2 + sollValues[Row] := BooleanResult(true); // M2 is 1, N2 is 2 + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + {------------------------------------------------------------------------------} { Math } {------------------------------------------------------------------------------} // ABS inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=abs(-1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-1, - RPNFunc(fekABS, nil)))); + formula := 'ABS(-1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-1, + RPNFunc('ABS', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(abs(-1)); + sollValues[Row] := FloatResult(abs(-1)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ARCCOS - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=acos(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekACOS, nil)))); + formula := 'ACOS(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('ACOS', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arccos(number)); + sollValues[Row] := FloatResult(arccos(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // Don't explicitly use the constant here because this will be "extended" // which is slightly different from the double value used in the formula // ACOS - error result (arccos is not defined outside the interval [-1..1] inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=acos(-2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-2, - RPNFunc(fekACOS, nil)))); + formula := 'ACOS(-2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-2, + RPNFunc('ACOS', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverFlow); + sollValues[Row] := ErrorResult(errOverFlow); + MyWorksheet.WriteErrorValue(Row, 2, errOverflow); // ARCCOSH - valid result inc(Row); number := 1.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=acosh(1.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(number, - RPNFunc(fekACOSH, nil)))); + formula := 'ACOSH(1.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(number, + RPNFunc('ACOSH', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arccosh(number)); + sollValues[Row] := FloatResult(arccosh(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ACOSH - error result (arccos is not defined below 1 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=acosh(0.9)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.9, - RPNFunc(fekACOSH, nil)))); + formula := 'ACOSH(0.9)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.9, + RPNFunc('ACOSH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverFlow); + sollValues[Row] := ErrorResult(errOverFlow); + MyWorksheet.WriteErrorValue(Row, 2, errOverflow); // ASIN inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=asin(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekASIN, nil)))); + formula := 'ASIN(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('ASIN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arcsin(number)); + sollValues[Row] := FloatResult(arcsin(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ASIN - error result (arcsin is not defined outside the interval [-1..1] inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=asin(-2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-2, - RPNFunc(fekASIN, nil)))); + formula := 'ASIN(-2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-2, + RPNFunc('ASIN', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverFlow); + sollValues[Row] := ErrorResult(errOverFlow); + MyWorksheet.WriteErrorValue(Row, 2, errOverflow); // ARCSINH inc(Row); number := 1.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=asinh(1.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.1, - RPNFunc(fekASINH, nil)))); + formula := 'ASINH(1.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.1, + RPNFunc('ASINH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arcsinh(number)); + sollValues[Row] := FloatResult(arcsinh(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ATAN inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=atan(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekATAN, nil)))); + formula := 'ATAN(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('ATAN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arctan(number)); + sollValues[Row] := FloatResult(arctan(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ATANH - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=atanh(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekATANH, nil)))); + formula := 'ATANH(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('ATANH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(arctanh(number)); + sollValues[Row] := FloatResult(arctanh(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // ATANH - error result (arctan is only defined within ]-1,1[ inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=atanh(1.0)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNFunc(fekATANH, nil)))); + formula := 'ATANH(1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNFunc('ATANH', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverFlow); + sollValues[Row] := ErrorResult(errOverFlow); + MyWorksheet.WriteErrorValue(Row, 2, errOverFlow); // COS inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=cos(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekCOS, nil)))); + formula := 'COS(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('COS', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(cos(number)); + sollValues[Row] := FloatResult(cos(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // COSH inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=cosh(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekCOSH, nil)))); + formula := 'COSH(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('COSH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(cosh(number)); + sollValues[Row] := FloatResult(cosh(number)); + MyWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); // DEGREES - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=degrees(1.0)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNFunc(fekDEGREES, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(radToDeg(1.0)); + if AFormat <> sfExcel2 then + begin + inc(Row); + formula := 'DEGREES(1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNFunc('DEGREES', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(radToDeg(1.0)); + myWorksheet.WriteNumber(Row, 2, sollValues[Row].ResFloat); + end; // EXP inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=exp(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekEXP, nil)))); + formula := 'EXP(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('EXP', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, exp(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(exp(number)); + sollValues[Row] := FloatResult(exp(number)); // INT (positive argument) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=int(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekINT, nil)))); + number := 0.1; + formula := 'INT(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('INT', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, floor(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(floor(0.1)); + sollValues[Row] := FloatResult(floor(number)); // INT (negative argument) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=int(-0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNFunc(fekINT, nil)))); + formula := 'INT(-0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNFunc('INT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, floor(-0.1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(floor(-0.1)); + sollValues[Row] := FloatResult(floor(-0.1)); // LN - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=ln(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekLN, nil)))); + formula := 'LN(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('LN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, ln(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(ln(number)); + sollValues[Row] := FloatResult(ln(number)); // LN - error due to argument = 0 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ln(0.0)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.0, - RPNFunc(fekLN, nil)))); + formula := 'LN(0)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.0, + RPNFunc('LN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LN - error due to argument < 0 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ln(-0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNFunc(fekLN, nil)))); + formula := 'LN(-0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNFunc('LN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LOG10 - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=log10(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekLOG10, nil)))); + formula := 'LOG10(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('LOG10', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, log10(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(log10(number)); + sollValues[Row] := FloatResult(log10(number)); // LOG10 - error due to argument = 0 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log10(0.0)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.0, - RPNFunc(fekLOG10, nil)))); + formula := 'LOG10(0)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.0, + RPNFunc('LOG10', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LOG10 - error due to argument < 0 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log10(-0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNFunc(fekLOG10, nil)))); + formula := 'LOG10(-0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNFunc('LOG10', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverFlow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LOG - valid result (2 arguments) inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=log(0.1, 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNNumber(2, - RPNFunc(fekLOG, 2, nil))))); + formula := 'LOG(0.1,2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNNumber(2, + RPNFunc('LOG', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, logn(2, number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(logn(2, number)); + sollValues[Row] := FloatResult(logn(2, number)); + +{ removed until new formula system supports missing arguments... // LOG - valid result (2 arguments, base missing) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log(0.1, )'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNMissingArg( - RPNFunc(fekLOG, 2, nil))))); + formula := 'LOG(0.1, )'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNMissingArg( + RPNFunc('LOG', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); +} // LOG - valid result (1 argument) inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=log(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekLOG, 1, nil)))); + formula := 'LOG(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('LOG', 1, nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, logn(10, number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(logn(10, number)); + sollValues[Row] := FloatResult(logn(10, number)); // LOG - negative base inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log(0.1, -2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNNumber(-2, - RPNFunc(fekLOG, 2, nil))))); + formula := 'LOG(0.1,-2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNNumber(-2, + RPNFunc('LOG', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LOG - negative value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log(-0.1, 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNNumber(2.0, - RPNFunc(fekLOG, 2, nil))))); + formula := 'LOG(-0.1,2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNNumber(2.0, + RPNFunc('LOG', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // LOG of cell inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=log(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekLOG, 1, nil)))); + formula := 'LOG(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('LOG', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, logn(10, 2)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(logn(10, 2)); + sollValues[Row] := FloatResult(logn(10, 2)); // PI inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=pi()'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, nil))); + formula := 'PI()'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('PI', nil))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, pi); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(pi); + sollValues[Row] := FloatResult(pi); - // RADIANS - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=radians(60)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(60, - RPNFunc(fekRADIANS, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(degtorad(60)); + if AFormat <> sfExcel2 then + begin + // RADIANS + inc(Row); + formula := 'RADIANS(60)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(60, + RPNFunc('RADIANS', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, DegToRad(60)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(degtorad(60)); - // RADIANS of cell value - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=radians(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekRADIANS, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(degtorad(2)); + // RADIANS of cell value + inc(Row); + formula := 'RADIANS(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('RADIANS', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, DegToRad(cellB1)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(degtorad(cellB1)); + end; // RAND // Test omitted because we cannot enforce getting the same random number back @@ -870,138 +1397,209 @@ // ROUND inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ROUND(pi(), 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekPI, - RPNNumber(2, - RPNFunc(fekROUND, nil))))); + formula := 'ROUND(PI(),2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('PI', + RPNNumber(2, + RPNFunc('ROUND', nil))))) + else + myWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, RoundTo(pi, 2)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(RoundTo(pi, 2)); + sollValues[Row] := FloatResult(RoundTo(pi, 2)); // SIGN inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sign(-0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNFunc(fekSIGN, nil)))); + number := -0.1; + formula := 'SIGN(-0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNFunc('SIGN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, sign(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sign(-0.1)); + sollValues[Row] := FloatResult(sign(number)); // SIGN of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sign(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekSIGN, nil)))); + formula := 'SIGN(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('SIGN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, sign(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sign(2)); + sollValues[Row] := FloatResult(sign(cellB1)); // SIN inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=sin(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekSIN, nil)))); + formula := 'SIN(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('SIN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, sin(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sin(number)); + sollValues[Row] := FloatResult(sin(number)); - // SIN of cell value + // SIN of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sin(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekSIN, nil)))); + formula := 'SIN(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('SIN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, sin(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sin(2)); + sollValues[Row] := FloatResult(sin(cellB1)); // SINH inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=sinh(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekSINH, nil)))); + formula := 'SINH(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('SINH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, sinh(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sinh(number)); + sollValues[Row] := FloatResult(sinh(number)); // SINH of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sinh(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekSINH, nil)))); + formula := 'SINH(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('SINH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, sinh(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sinh(2)); + sollValues[Row] := FloatResult(sinh(cellB1)); // SQRT - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=sqrt(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekSQRT, nil)))); + formula := 'SQRT(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('SQRT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, sqrt(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sqrt(number)); + sollValues[Row] := FloatResult(sqrt(number)); // SQRT - error (negative argument) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sqrt(-0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(-0.1, - RPNFunc(fekSQRT, nil)))); + formula := 'SQRT(-0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(-0.1, + RPNFunc('SQRT', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, GetErrorValueStr(errOverflow)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateErrorArg(errOverflow); + sollValues[Row] := ErrorResult(errOverflow); // SQRT of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=sqrt(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellvalue('B1', - RPNFunc(fekSQRT, nil)))); + formula := 'SQRT(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellvalue('B1', + RPNFunc('SQRT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, sqrt(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(sqrt(2)); + sollValues[Row] := FloatResult(sqrt(cellB1)); // TAN - valid result inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=tan(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekTAN, nil)))); + formula := 'TAN(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('TAN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, tan(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(tan(number)); + sollValues[Row] := FloatResult(tan(number)); // TAN - error (argument = pi/2) // This test is omitted because it is difficult to exactly hit pi/2. // TAN of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=tan(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekTAN, nil)))); + formula := 'TAN(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('TAN', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, tan(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(tan(2)); + sollValues[Row] := FloatResult(tan(cellB1)); // TANH inc(Row); number := 0.1; - MyWorksheet.WriteUTF8Text(Row, 0, '=tanh(0.1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.1, - RPNFunc(fekTANH, nil)))); + formula := 'TANH(0.1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0.1, + RPNFunc('TANH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, tanh(number)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(tanh(number)); + sollValues[Row] := FloatResult(tanh(number)); // TANH of cell value inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=tanh(B1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('B1', - RPNFunc(fekTANH, nil)))); + formula := 'TANH(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('B1', + RPNFunc('TANH', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, tanh(cellB1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(tanh(2)); + sollValues[Row] := FloatResult(tanh(cellB1)); {------------------------------------------------------------------------------} @@ -1010,221 +1608,351 @@ // DATE inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=DATE(2014, 7, 1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2014, - RPNNumber(7, - RPNNumber(1, - RPNFunc(fekDATE, nil)))))); + formula := 'DATE(2014,7,1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteDateTime(Row, 2, EncodeDate(2014,7,1)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(EncodeDate(2014,7,1)); + sollValues[Row] := DateTimeResult(EncodeDate(2014,7,1)); + + // DATEDIF + if AFormat <> sfExcel2 then begin + inc(Row); + formula := 'DATEDIF(DATE(2014,7,1),DATE(2014,1,1),"D")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', + RPNNumber(2014, + RPNNumber(1, + RPNNumber(1, + RPNFunc('DATE', + RPNString('D', + RPNFUNC('DATEDIF', + nil)))))))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, EncodeDate(2014,7,1)-EncodeDate(2014,1,1)); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(round(EncodeDate(2014,7,1)-EncodeDate(2014,1,1))); + end; // DATEVALUE inc(Row); s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=DATEVALUE("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekDATEVALUE, nil)))); + formula := 'DATEVALUE("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('DATEVALUE', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteDateTimeFormat(Row, 1, nfShortDate); + MyWorksheet.WriteDateTime(Row, 2, StrToDate(s), nfShortDate); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(EncodeDate(2014,7,1)); + sollValues[Row] := DateTimeResult(EncodeDate(2014,7,1)); // DAY / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=DAY(DATE(2014,7,1))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2014, - RPNNumber(7, - RPNNumber(1, - RPNFunc(fekDATE, - RPNFunc(fekDAY, nil))))))); + formula := 'DAY(DATE(2014,7,1))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', + RPNFunc('DAY', nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := IntegerResult(1); // DAY / argument string inc(Row); s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=DAY("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekDAY, nil)))); + formula := 'DAY("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('DAY', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := IntegerResult(1); // HOUR / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=HOUR(TIME(9, 59, 20))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(9, - RPNNumber(59, - RPNNumber(20, - RPNFunc(fekTIME, - RPNFunc(fekHOUR, nil))))))); + formula := 'HOUR(TIME(9,59,20))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(9, + RPNNumber(59, + RPNNumber(20, + RPNFunc('TIME', + RPNFunc('HOUR', nil))))))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 9); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(9); + sollValues[Row] := IntegerResult(9); // HOUR / argument string inc(Row); s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=HOUR("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekHOUR, nil)))); + formula := 'HOUR("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('HOUR', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, 9); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(9); + sollValues[Row] := IntegerResult(9); // MINUTE / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MINUTE(TIME(9, 59, 20))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(9, - RPNNumber(59, - RPNNumber(20, - RPNFunc(fekTIME, - RPNFunc(fekMINUTE, nil))))))); + formula := 'MINUTE(TIME(9,59,20))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(9, + RPNNumber(59, + RPNNumber(20, + RPNFunc('TIME', + RPNFunc('MINUTE', nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, 59); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(59); + sollValues[Row] := IntegerResult(59); // MINUTE / argument string inc(Row); s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=MINUTE("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekMINUTE, nil)))); + formula := 'MINUTE("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('MINUTE', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 59); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(59); + sollValues[Row] := IntegerResult(59); // MONTH / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MONTH(DATE(2014,7,1))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2014, - RPNNumber(7, - RPNNumber(1, - RPNFunc(fekDATE, - RPNFunc(fekMONTH, nil))))))); + formula := 'MONTH(DATE(2014,7,1))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', + RPNFunc('MONTH', nil))))))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 7); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(7); + sollValues[Row] := IntegerResult(7); // MONTH / argument string inc(Row); s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=MONTH("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekMONTH, nil)))); + formula := 'MONTH("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('MONTH', nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 7); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(7); + sollValues[Row] := IntegerResult(7); // NOW inc(Row); + formula := 'NOW()'; // Make sure that, when the file is read we still have the same second // Otherwise there would be a mismatch. repeat - t := now(); - DecodeTime(t, hr, min, sec, msec); + number := now(); + DecodeTime(number, hr, min, sec, msec); until msec < 500; - MyWorksheet.WriteUTF8Text(Row, 0, '=NOW()'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekNOW, nil))); + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('NOW', nil))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteDateTimeFormat(Row, 1, nfShortDateTime); + MyWorksheet.WriteDateTime(Row, 2, number, nfShortDateTime); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(t); + sollValues[Row] := DateTimeResult(number); // SECOND / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SECOND(TIME(9, 59, 20))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(9, - RPNNumber(59, - RPNNumber(20, - RPNFunc(fekTIME, - RPNFunc(fekSECOND, nil))))))); + formula := 'SECOND(TIME(9,59,20))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(9, + RPNNumber(59, + RPNNumber(20, + RPNFunc('TIME', + RPNFunc('SECOND', nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 1, 20); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(20); + sollValues[Row] := IntegerResult(20); // SECOND / argument string inc(Row); s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=SECOND("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekSECOND, nil)))); + formula := 'SECOND("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('SECOND', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 1, 20); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(20); + sollValues[Row] := IntegerResult(20); // TIME inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=TIME(9, 59, 20)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(9, - RPNNumber(59, - RPNNumber(20, - RPNFunc(fekTIME, nil)))))); + formula := 'TIME(9,59,20)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(9, + RPNNumber(59, + RPNNumber(20, + RPNFunc('TIME', nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteDateTimeFormat(Row, 1, nfLongTime); + MyWorksheet.WriteDateTime(Row, 2, EncodeTime(9, 50, 20, 0), nfLongTime); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(EncodeTime(9, 59, 20, 0)); + sollValues[Row] := DateTimeResult(EncodeTime(9, 59, 20, 0)); // TIMEVALUE inc(Row); s := TimeToStr(EncodeTime(9, 59, 20, 0)); // Localization! - MyWorksheet.WriteUTF8Text(Row, 0, '=TIMEVALUE("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekTIMEVALUE, nil)))); + formula := 'TIMEVALUE("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('TIMEVALUE', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteDateTimeFormat(Row, 1, nfLongTime); + MyWorksheet.WriteDateTime(Row, 2, EncodeTime(9, 59, 20, 0), nfLongTime); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(EncodeTime(9, 59, 20, 0)); + sollValues[Row] := DateTimeResult(EncodeTime(9, 59, 20, 0)); // TODAY inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=TODAY()'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekTODAY, nil))); + formula := 'TODAY()'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('TODAY', nil))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteDateTimeFormat(Row, 1, nfShortDate); + Myworksheet.WriteDateTime(Row, 2, Date(), nfShortDate); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(Date()); + sollValues[Row] := DateTimeResult(Date()); // WEEKDAY / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=WEEKDAY(DATE(2014,7,1))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2014, - RPNNumber(7, - RPNNumber(1, - RPNFunc(fekDATE, - RPNFunc(fekWEEKDAY, 1, nil))))))); + formula := 'WEEKDAY(DATE(2014,7,1))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', + RPNFunc('WEEKDAY', 1, nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, DayOfWeek(EncodeDate(2014,7,1))); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(DayOfWeek(EncodeDate(2014,7,1))); + sollValues[Row] := IntegerResult(DayOfWeek(EncodeDate(2014,7,1))); // WEEKDAY / argument string inc(Row); s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=WEEKDAY("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekWEEKDAY, 1, nil)))); + formula := 'WEEKDAY("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('WEEKDAY', 1, nil)))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, DayOfWeek(EncodeDate(2014,7,1))); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(DayOfWeek(EncodeDate(2014,7,1))); + sollValues[Row] := IntegerResult(DayOfWeek(EncodeDate(2014,7,1))); // YEAR / argument number inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=YEAR(DATE(2014,7,1))'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(2014, - RPNNumber(7, - RPNNumber(1, - RPNFunc(fekDATE, - RPNFunc(fekYEAR, nil))))))); + formula := 'YEAR(DATE(2014,7,1))'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(2014, + RPNNumber(7, + RPNNumber(1, + RPNFunc('DATE', + RPNFunc('YEAR', nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2014); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2014); + sollValues[Row] := IntegerResult(2014); // YEAR / argument string inc(Row); s := DateToStr(EncodeDate(2014, 7, 1)); // Localization of the test - MyWorksheet.WriteUTF8Text(Row, 0, '=YEAR("'+s+'")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(s, - RPNFunc(fekYEAR, nil)))); + formula := 'YEAR("'+s+'")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(s, + RPNFunc('YEAR', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2014); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2014); + sollValues[Row] := IntegerResult(2014); {------------------------------------------------------------------------------} @@ -1232,369 +1960,568 @@ {------------------------------------------------------------------------------} // AVEDEV - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AVEDEV(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekAVEDEV, 5, nil)))))))); - SetLength(sollValues, Row+1); - number := mean(STATS_NUMBERS); - for k := 0 to High(STATS_NUMBERS) do numberArray[k] := abs(STATS_NUMBERS[k] - number); - // these values are the absolute deviations from mean (1.0) - sollValues[Row] := CreateNumberArg(mean(numberArray)); - - // AVERAGE - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AVERAGE(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekAVERAGE, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(mean(STATS_NUMBERS)); - - // AVERAGE of cell block - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AVERAGE(B1:B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('B1:B2', - RPNFunc(fekAVERAGE, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(mean([2.0, -9.0])); - - // COUNT - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNT(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekCOUNT, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(5); - - // COUNT of cell range (no empty cells) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNT(B1:B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellrange('B1:B2', - RPNFunc(fekCOUNT, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2); - - // COUNT of cell range (no empty cells, but with non-numeric cells) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNT(A1:B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellrange('A1:B2', - RPNFunc(fekCOUNT, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2); - - // COUNT of cell range (with empty cells) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNT(B1:C2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellrange('B1:C2', - RPNFunc(fekCOUNT, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2); - - // COUNTA - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTA(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekCOUNTA, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(5); - - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTA(1, 1.1, 1.2, 0.9, 0.8, "A", "B")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNString('A', - RPNString('B', - RPNFunc(fekCOUNTA, 7, nil)))))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(7); - - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTA(A1:C2,1,2,"A")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('A1:C2', - RPNNumber(1, - RPNNumber(2, - RPNString('A', - RPNFunc(fekCOUNTA, 4, nil))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(7); - if AFormat <> sfExcel2 then begin - // COUNTBLANK inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTBLANK(A1:D2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('A1:D2', - RPNFunc(fekCOUNTBLANK, nil)))); + formula := 'AVEDEV(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('AVEDEV', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(4); + number := mean(STATS_NUMBERS); + for k := 0 to High(STATS_NUMBERS) do numberArray[k] := abs(STATS_NUMBERS[k] - number); + // these values are the absolute deviations from mean (1.0) + sollValues[Row] := FloatResult(mean(numberArray)); + MyWorksheet.WriteNumber(Row, 2, mean(numberArray)); end; - if AFormat <> sfExcel2 then begin - // COUNTIF - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTIF(M1:N3,1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNNumber(1, // "1" is in M2 - RPNFunc(fekCOUNTIF, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); - - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTIF(M1:N3,">=1")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('>=1', // M2=1, N2=2 --> 2 - RPNFunc(fekCOUNTIF, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2); - - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTIF(M1:N3,"<2")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('<2', // M2=1, N2=2 --> 1 - RPNFunc(fekCOUNTIF, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); - - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTIF(M1:N3,"<>2")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('<>2', // N2=2, 6 other cells --> 5 - RPNFunc(fekCOUNTIF, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(5); - - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=COUNTIF(M1:N3,M1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNCellValue('M1', // M1="A", N1="A" -> 2 - RPNFunc(fekCOUNTIF, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(2); - end; - - // MAX +// AVERAGE inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MAX(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekMAX, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(MaxValue(STATS_NUMBERS)); - - // MAX of cell range (no empty cells) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MAX(B1:B2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('B1:B2', - RPNFunc(fekMAX, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(MaxValue([2.0, -9.0])); - - // MAX of cell range (incl empty cells) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MAX(B1:C2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('B1:C2', - RPNFunc(fekMAX, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(MaxValue([2.0, -9.0])); - - // MIN - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MIN(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekMIN, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(MinValue(STATS_NUMBERS)); - - // PRODUCT - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=PRODUCT(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekPRODUCT, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(STATS_NUMBERS[0]*STATS_NUMBERS[1]*STATS_NUMBERS[2]*STATS_NUMBERS[3]*STATS_NUMBERS[4]); - - // STDEV - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=STDEV(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekSTDEV, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(StdDev(STATS_NUMBERS)); - - // STDEVP - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=STDEVP(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekSTDEVP, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(PopnStdDev(STATS_NUMBERS)); - - // SUM - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUM(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekSUM, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(Sum(STATS_NUMBERS)); - - if AFormat <> sfExcel2 then begin - // SUMSQ - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMSQ(1, 1.1, 1.2, 0.9, 0.8)'); + formula := 'AVERAGE(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1.0, RPNNumber(1.1, RPNNumber(1.2, RPNNumber(0.9, RPNNumber(0.8, - RPNFunc(fekSUMSQ, 5, nil)))))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(SumOfSquares(STATS_NUMBERS)); + RPNFunc('AVERAGE', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, mean(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(mean(STATS_NUMBERS)); + // AVERAGE of cell block + inc(Row); + formula := 'AVERAGE(B1:B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('B1:B2', + RPNFunc('AVERAGE', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, mean([cellB1, cellB2])); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(mean([cellB1, cellB2])); + + // COUNT + inc(Row); + formula := 'COUNT(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('COUNT', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 5); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(5); + + // COUNT of cell range (no empty cells) + inc(Row); + formula := 'COUNT(B1:B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellrange('B1:B2', + RPNFunc('COUNT', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + + // COUNT of cell range (no empty cells, but with non-numeric cells) + inc(Row); + formula := 'COUNT(A1:B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellrange('A1:B2', + RPNFunc('COUNT', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + + // COUNT of cell range (with empty cells) + inc(Row); + formula := 'COUNT(B1:C2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellrange('B1:C2', + RPNFunc('COUNT', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + + // COUNTA + formula := 'COUNTA(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('COUNTA', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 5); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(5); + + formula := 'COUNTA(1,1.1,1.2,0.9,0.8,"A","B")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNString('A', + RPNString('B', + RPNFunc('COUNTA', 7, nil)))))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 7); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(7); + + inc(Row); + formula := 'COUNTA(A1:D2,1,2,"A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('A1:D2', + RPNNumber(1, + RPNNumber(2, + RPNString('A', + RPNFunc('COUNTA', 4, nil))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 9); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(9); + + // COUNTBLANK + if AFormat <> sfExcel2 then begin + inc(Row); + formula := 'COUNTBLANK(A1:D2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('A1:D2', + RPNFunc('COUNTBLANK', nil)))) + else + myworksheet.WriteFormula(row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + end; + + // COUNTIF +{ currently no support for COUNTIF + + if AFormat <> sfExcel2 then begin + inc(Row); + formula := 'COUNTIF(M1:N3,1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNNumber(1, // "1" is in M2 + RPNFunc('COUNTIF', nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 1); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(1); + + inc(Row); + formula := 'COUNTIF(M1:N3,">=1")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('>=1', // M2=1, N2=2 --> 2 + RPNFunc('COUNTIF', nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + + inc(Row); + formula := 'COUNTIF(M1:N3,"<2")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('<2', // M2=1, N2=2 --> 1 + RPNFunc('COUNTIF', nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 1); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(1); + + inc(Row); + formula := 'COUNTIF(M1:N3,"<>2")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('<>2', // N2=2, 6 other cells --> 5 + RPNFunc('COUNTIF', nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 5); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(5); + + inc(Row); + formula := 'COUNTIF(M1:N3,M1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNCellValue('M1', // M1="A", N1="A" -> 2 + RPNFunc('COUNTIF', nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNubmer(Row, 2, 2); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(2); + end; + } + // MAX + inc(Row); + formula := 'MAX(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('MAX', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, MaxValue(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MaxValue(STATS_NUMBERS)); + + // MAX of cell range (no empty cells) + inc(Row); + formula := 'MAX(B1:B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('B1:B2', + RPNFunc('MAX', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, MaxValue([cellB1, cellB2])); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MaxValue([cellB1, cellB2])); + + // MAX of cell range (incl empty cells) + inc(Row); + formula := 'MAX(B1:C2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('B1:C2', + RPNFunc('MAX', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, MaxValue([cellB1, cellB2])); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MaxValue([cellB1, cellB2])); + + // MIN + inc(Row); + formula := 'MIN(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('MIN', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, MinValue(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MinValue(STATS_NUMBERS)); + + // MIN of cell range (no empty cells) + inc(Row); + formula := 'MIN(B1:B2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('B1:B2', + RPNFunc('MIN', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, MinValue([cellB1, cellB2])); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MinValue([cellB1, cellB2])); + + // MIN of cell range (incl empty cells) + inc(Row); + formula := 'MIN(B1:C2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('B1:C2', + RPNFunc('MIN', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, MinValue([cellB1, cellB2])); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(MinValue([cellB1, cellB2])); + + // PRODUCT + inc(Row); + formula := 'PRODUCT(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('PRODUCT', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(STATS_NUMBERS[0]*STATS_NUMBERS[1]*STATS_NUMBERS[2]*STATS_NUMBERS[3]*STATS_NUMBERS[4]); + MyWorksheet.WriteNumber(Row, 2, sollvalues[Row].ResFloat); + + // STDEV + inc(Row); + formula := 'STDEV(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('STDEV', 5, nil)))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, StdDev(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(StdDev(STATS_NUMBERS)); + + // STDEVP + inc(Row); + formula := 'STDEVP(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('STDEVP', 5, nil)))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, PopnStdDev(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(PopnStdDev(STATS_NUMBERS)); + + // SUM + inc(Row); + formula := 'SUM(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('SUM', 5, nil)))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, Sum(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(Sum(STATS_NUMBERS)); + + // SUMSQ + if AFormat <> sfExcel2 then begin + inc(Row); + formula := 'SUMSQ(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('SUMSQ', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, SumOfSquares(STATS_NUMBERS)); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(SumOfSquares(STATS_NUMBERS)); + + { ---- SUMIF currently not supported // SUMIF inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNNumber(1, // "1" is in M2 - RPNFunc(fekSUMIF, 2, nil))))); + formula = 'SUMIF(M1:N3,1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNNumber(1, // "1" is in M2 + RPNFunc('SUMIF', 2, nil))))) + else + MyWorksheet.WriteRPNFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,">=1")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('>=1', // M2=1, N2=2 --> 2 - RPNFunc(fekSUMIF, 2, nil))))); + formula := 'SUMIF(M1:N3,">=1")'; + MyWorksheet.WriteUTF8Text(Row, 0, formul); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('>=1', // M2=1, N2=2 --> 2 + RPNFunc('SUMIF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1 formula); + MyWorksheet.WriteNumber(Row, 2, 1.0+2.0); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1+2); + sollValues[Row] := FloatResult(1.0 + 2.0); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,"<2")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('<2', // M2=1, N2=2 --> 1 - RPNFunc(fekSUMIF, 2, nil))))); + formula := 'SUMIF(M1:N3,"<2")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('<2', // M2=1, N2=2 --> 1 + RPNFunc('SUMIF', 2, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteFormula(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,"<>2")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNString('<>2', // N2=2, 6 other cells --> 5 - RPNFunc(fekSUMIF, 2, nil))))); + formula := 'SUMIF(M1:N3,"<>2")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNString('<>2', // N2=2, 6 other cells --> 5 + RPNFunc('SUMIF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,M1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNCellValue('M1', // M1="A", N1="A" -> 2 - RPNFunc(fekSUMIF, 2, nil))))); + formula := 'SUMIF(M1:N3,M1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNCellValue('M1', // M1="A", N1="A" -> 2 + RPNFunc('SUMIF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 0); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(0); // no numbers! + sollValues[Row] := FloatResult(0); // no numbers! inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUMIF(M1:N3,M2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRange('M1:N3', - RPNCellValue('M2', // M2=1 - RPNFunc(fekSUMIF, 2, nil))))); + formula := 'SUMIF(M1:N3,M2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRange('M1:N3', + RPNCellValue('M2', // M2=1 + RPNFunc('SUMIF', 2, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); + } end; // VAR inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=VAR(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekVAR, 5, nil)))))))); + formula := 'VAR(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('VAR', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, Variance(STATS_NUMBERS)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(Variance(STATS_NUMBERS)); + sollValues[Row] := FloatResult(Variance(STATS_NUMBERS)); // VARP inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=VARP(1, 1.1, 1.2, 0.9, 0.8)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1.0, - RPNNumber(1.1, - RPNNumber(1.2, - RPNNumber(0.9, - RPNNumber(0.8, - RPNFunc(fekVARP, 5, nil)))))))); + formula := 'VARP(1,1.1,1.2,0.9,0.8)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1.0, + RPNNumber(1.1, + RPNNumber(1.2, + RPNNumber(0.9, + RPNNumber(0.8, + RPNFunc('VARP', 5, nil)))))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, PopnVariance(STATS_NUMBERS)); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(PopnVariance(STATS_NUMBERS)); + sollValues[Row] := FloatResult(PopnVariance(STATS_NUMBERS)); {------------------------------------------------------------------------------} @@ -1603,172 +2530,257 @@ // AND of one values (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AND(true)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNFunc(fekAND, 1, nil)))); + formula := 'AND(TRUE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNFunc('AND', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'TRUE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); + sollValues[Row] := BooleanResult(true); // AND of two values (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AND(true,false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNFunc(fekAND, 2, nil))))); + formula := 'AND(TRUE,FALSE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc('AND', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'FALSE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true and false); + sollValues[Row] := BooleanResult(true and false); // AND of three values (bool) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=AND(true,false,true)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNBool(true, - RPNFunc(fekAND, 3, nil)))))); + formula := 'AND(TRUE,FALSE,TRUE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNBool(true, + RPNFunc('AND', 3, nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true and false and true); - - // OR of one values (bool) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=OR(true)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNFunc(fekOR, 1, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); - - // OR of two values (bool) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=OR(true,false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNFunc(fekOR, 2, nil))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true or false); - - // OR of three values (bool) - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=OR(true,false,true)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNBool(false, - RPNBool(true, - RPNFunc(fekOR, 3, nil)))))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true or false or true); + sollValues[Row] := BooleanResult(true and false and true); // function =FALSE() inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=FALSE()'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekFALSE, nil))); + formula := 'FALSE()'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('FALSE', nil))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'FALSE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); - - // function =TRUE() - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=TRUE()'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNFunc(fekTRUE, nil))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); - - // NOT - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=NOT(false)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(false, - RPNFunc(fekNOT, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(not false); + sollValues[Row] := BooleanResult(false); // IF (2 parameters)/strings/case true inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,"A")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNString('A', - RPNFunc(fekIF, 2, nil))))); + formula := 'IF(TRUE,"A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNString('A', + RPNFunc('IF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'A'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('A'); + sollValues[Row] := StringResult('A'); // IF (2 parameters) /floats/case true inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNNumber(1.0, - RPNFunc(fekIF, 2, nil))))); + formula := 'IF(TRUE,1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNNumber(1.0, + RPNFunc('IF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); - // IF (2 parameters)/strings/case falsee + // IF (2 parameters)/strings/case false inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(false,"A")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(false, - RPNString('A', - RPNFunc(fekIF, 2, nil))))); + formula := 'IF(FALSE,"A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(false, + RPNString('A', + RPNFunc('IF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); + sollValues[Row] := BooleanResult(false); - // IF (2 parameters) /floats/case tfalse + // IF (2 parameters) /floats/case false inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(false,1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(false, - RPNNumber(1.0, - RPNFunc(fekIF, 2, nil))))); + formula := 'IF(FALSE,1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(false, + RPNNumber(1.0, + RPNFunc('IF', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteUTF8Text(Row, 2, 'FALSE'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); + sollValues[Row] := BooleanResult(false); // IF (3 parameters)/strings inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,"A","B")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNString('A', - RPNString('B', - RPNFunc(fekIF, 3, nil)))))); + formula := 'IF(TRUE,"A","B")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNString('A', + RPNString('B', + RPNFunc('IF', 3, nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'A'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('A'); + sollValues[Row] := StringResult('A'); // IF (3 parameters) /floats inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1,2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNNumber(1.0, - RPNNumber(2.0, - RPNFunc(fekIF,3, nil)))))); + formula := 'IF(TRUE,1,2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNNumber(1.0, + RPNNumber(2.0, + RPNFunc('IF',3, nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); // IF (3 parameters) /floats / mixed types, case true inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1,"A")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(true, - RPNNumber(1.0, - RPNString('A', - RPNFunc(fekIF,3, nil)))))); + formula := 'IF(TRUE,1,"A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNNumber(1.0, + RPNString('A', + RPNFunc('IF',3, nil)))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteNumber(Row, 2, 1); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(1); + sollValues[Row] := FloatResult(1); // IF (3 parameters) /floats / mixed types, case false inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(false,1,"A")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNBool(false, - RPNNumber(1.0, - RPNString('A', - RPNFunc(fekIF, 3, nil)))))); + formula := 'IF(FALSE,1,"A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(false, + RPNNumber(1.0, + RPNString('A', + RPNFunc('IF', 3, nil)))))) + else + myWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'A'); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('A'); + sollValues[Row] := StringResult('A'); + + // NOT + inc(Row); + formula := 'NOT(FALSE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(false, + RPNFunc('NOT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'TRUE'); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(not false); + + // OR of one values (bool) + inc(Row); + formula := 'OR(TRUE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNFunc('OR', 1, nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteUTF8Text(Row, 2, 'TRUE'); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + + // OR of two values (bool) + inc(Row); + formula := 'OR(TRUE,FALSE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNFunc('OR', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + SetLength(sollValues, Row+1); + sollValues[Row] := Booleanresult(true or false); + + // OR of three values (bool) + inc(Row); + formula := 'OR(TRUE,FALSE,TRUE)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNBool(true, + RPNBool(false, + RPNBool(true, + RPNFunc('OR', 3, nil)))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true or false or true); + + // function =TRUE() + inc(Row); + formula := 'TRUE()'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNFunc('TRUE', nil))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + MyWorksheet.WriteUTF8Text(Row, 2, 'TRUE'); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); {------------------------------------------------------------------------------} @@ -1777,195 +2789,355 @@ // CHAR inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=CHAR(72)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(72, - RPNFunc(fekCHAR, nil)))); + formula := 'CHAR(72)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNInteger(72, + RPNFunc('CHAR', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(char(72)); + sollValues[Row] := StringResult(char(72)); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // CODE inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=CODE("Hallo word")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNFunc(fekCODE, nil)))); + formula := 'CODE("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('CODE', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(ord('H')); + sollValues[Row] := IntegerResult(ord('H')); + Myworksheet.WriteNumber(Row, 2, sollValues[Row].ResInteger); + + // CONCATENATE + inc(Row); + formula := 'CONCATENATE("A","B","C")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('A', + RPNString('B', + RPNString('C', + RPNFunc('CONCATENATE', 3, nil)))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := StringResult('ABC'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // LEFT (2 parameters) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LEFT("Hallo word", 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNNumber(2, - RPNFunc(fekLEFT, 2, nil))))); + formula := 'LEFT("Hallo world",2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNInteger(2, + RPNFunc('LEFT', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('Ha'); + sollValues[Row] := StringResult('Ha'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // LEFT (2 parameters, utf8) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LEFT("Ändern", 3)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Ändern', - RPNNumber(3, - RPNFunc(fekLEFT, 2, nil))))); + formula := 'LEFT("Ändern",3)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Ändern', + RPNInteger(3, + RPNFunc('LEFT', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('Änd'); + sollValues[Row] := StringResult('Änd'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); + { -- at the momement no missing arguments support // LEFT (2 parameters, 1 of them missing) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LEFT("Hallo word", )'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNMissingArg( - RPNFunc(fekLEFT, 2, nil))))); + formula := 'LEFT("Hallo world",)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNMissingArg( + RPNFunc('LEFT', 2, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('H'); - + sollValues[Row] := StringResult('H'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); +} // LEFT (1 parameter) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LEFT("Hallo word")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNFunc(fekLEFT, 1, nil)))); + formula := 'LEFT("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('LEFT', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('H'); + sollValues[Row] := StringResult('H'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); + + // Len + inc(Row); + formula := 'LEN("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('LEN', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := IntegerResult(Length('Hallo world')); + Myworksheet.WriteNumber(Row, 2, sollValues[Row].ResInteger); // Lower case inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LOWER("Hallo word")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNFunc(fekLOWER, nil)))); + formula := 'LOWER("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('LOWER', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(LowerCase('Hallo world')); + sollValues[Row] := StringResult(LowerCase('Hallo world')); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // Lower case / utf8 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=LOWER("Viele Grüße")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Viele Grüße', - RPNFunc(fekLOWER, nil)))); + formula := 'LOWER("Viele Grüße")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Viele Grüße', + RPNFunc('LOWER', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(UTF8LowerCase('Viele Grüße')); + sollValues[Row] := StringResult(UTF8LowerCase('Viele Grüße')); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // MID inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=MID("Hallo word", 3, 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNNumber(3, - RPNNumber(2, - RPNFunc(fekMID, nil)))))); + formula := 'MID("Hallo world",3,2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNInteger(3, + RPNInteger(2, + RPNFunc('MID', nil)))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('ll'); + sollValues[Row] := StringResult('ll'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // REPLACE inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=REPLACE("weather", 2, 2, "he")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('weather', - RPNNumber(2, - RPNNumber(2, - RPNString('he', - RPNFunc(fekREPLACE, nil))))))); + formula := 'REPLACE("weather",2,2,"he")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('weather', + RPNInteger(2, + RPNInteger(2, + RPNString('he', + RPNFunc('REPLACE', nil))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('whether'); + sollValues[Row] := StringResult('whether'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // REPLACE / utf8 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=REPLACE("würde", 2, 1, "u")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('würde', - RPNNumber(2, - RPNNumber(1, - RPNString('u', - RPNFunc(fekREPLACE, nil))))))); + formula := 'REPLACE("würde",2,1,"u")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('würde', + RPNInteger(2, + RPNInteger(1, + RPNString('u', + RPNFunc('REPLACE', nil))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('wurde'); + sollValues[Row] := StringResult('wurde'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // RIGHT (2 parameters) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=RIGHT("Hallo word", 2)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNNumber(2, - RPNFunc(fekRIGHT, 2, nil))))); + formula := 'RIGHT("Hallo world",2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNInteger(2, + RPNFunc('RIGHT', 2, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('ld'); + sollValues[Row] := StringResult('ld'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); +{ --- no missing parameter support now // RIGHT (2 parameters, one of them missing) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=RIGHT("Hallo word", )'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNMissingArg( - RPNFunc(fekRIGHT, 2, nil))))); + formula := 'RIGHT("Hallo world", )'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNMissingArg( + RPNFunc('RIGHT', 2, nil))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('d'); + sollValues[Row] := StringResult('d'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); + } // RIGHT (1 parameter) inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=RIGHT("Hallo word")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNFunc(fekRIGHT, 1, nil)))); + formula := 'RIGHT("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('RIGHT', 1, nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('d'); + sollValues[Row] := StringResult('d'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // SUBSTITUTE inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=SUBSTITUTE("lAzArus", "A", "a")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('lAzArus', - RPNString('A', - RPNString('a', - RPNFunc(fekSUBSTITUTE, 3, nil)))))); + formula := 'SUBSTITUTE("lAzArus","A","a")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('lAzArus', + RPNString('A', + RPNString('a', + RPNFunc('SUBSTITUTE', 3, nil)))))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('lazarus'); + sollValues[Row] := StringResult('lazarus'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); + + // SUBSTITUTE /nth appearance + inc(Row); + formula := 'SUBSTITUTE("lazarus","a","A",2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('lazarus', + RPNString('a', + RPNString('A', + RPNInteger(2, + RPNFunc('SUBSTITUTE', 4, nil))))))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := StringResult('lazArus'); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // Trim inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=TRIM(" Hallo word ")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(' Hallo world ', - RPNFunc(fekTRIM, nil)))); + formula := 'TRIM(" Hallo world ")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString(' Hallo world ', + RPNFunc('TRIM', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(UTF8Trim(' Hallo world ')); + sollValues[Row] := StringResult(UTF8Trim(' Hallo world ')); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // Upper case inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=UPPER("Hallo word")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Hallo world', - RPNFunc(fekUPPER, nil)))); + formula := 'UPPER("Hallo world")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Hallo world', + RPNFunc('UPPER', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(UpperCase('Hallo world')); + sollValues[Row] := StringResult(UpperCase('Hallo world')); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); // Upper case / utf8 inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=UPPER("Viele Grüße")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('Viele Grüße', - RPNFunc(fekUPPER, nil)))); + formula := 'UPPER("Viele Grüße")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('Viele Grüße', + RPNFunc('UPPER', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg(UTF8UpperCase('Viele Grüße')); + sollValues[Row] := StringResult(UTF8UpperCase('Viele Grüße')); + Myworksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); + + // VALUE + inc(Row); + formula := 'VALUE("100")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('100', + RPNFunc('VALUE', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + myWorksheet.WriteNumber(Row, 2, 100); + SetLength(sollValues, Row+1); + sollValues[Row] := FloatResult(100); + + + + + + {------------------------------------------------------------------------------} -{ Lookup / referece functions } +{ Lookup / reference functions } {------------------------------------------------------------------------------} - + (* // COLUMN - MyWorksheet.WriteUTF8Text(Row, 0, '=COLUMN(A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'COLUMN(A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRef('A1', RPNFunc(fekCOLUMN, 1, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(1); - MyWorksheet.WriteUTF8Text(Row, 0, '=COLUMN(C1:D3)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'COLUMN(C1:D3)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('C1:D3', RPNFunc(fekCOLUMN, 1, nil)))); @@ -1973,14 +3145,14 @@ sollValues[Row] := CreateNumberArg(3); // COLUMNS - MyWorksheet.WriteUTF8Text(Row, 0, '=COLUMNS(A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'COLUMNS(A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRef('A1', RPNFunc(fekCOLUMNS, 1, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(1); - MyWorksheet.WriteUTF8Text(Row, 0, '=COLUMNS(C1:D3)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'COLUMNS(C1:D3)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('C1:D3', RPNFunc(fekCOLUMNS, 1, nil)))); @@ -1988,14 +3160,14 @@ sollValues[Row] := CreateNumberArg(2); // ROW - MyWorksheet.WriteUTF8Text(Row, 0, '=ROW(A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'ROW(A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRef('A1', RPNFunc(fekROW, 1, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(1); - MyWorksheet.WriteUTF8Text(Row, 0, '=ROW(C2:D3)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'ROW(C2:D3)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('C2:D3', RPNFunc(fekROW, 1, nil)))); @@ -2003,35 +3175,40 @@ sollValues[Row] := CreateNumberArg(2); // ROWS - MyWorksheet.WriteUTF8Text(Row, 0, '=ROWS(A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'ROWS(A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRef('A1', RPNFunc(fekROWS, 1, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(1); - MyWorksheet.WriteUTF8Text(Row, 0, '=ROWS(C2:D3)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'ROWS(C2:D3)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('C2:D3', RPNFunc(fekROWS, 1, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(2); - + *) {------------------------------------------------------------------------------} { Information functions } {------------------------------------------------------------------------------} - + { cell function will be removed because it requires localized string parameters // CELL - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("address", A1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('address', - RPNCellRef('A1', - RPNFunc(fekCELLINFO, 2, nil))))); + formula := 'CELL("address",A1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('address', + RPNCellRef('A1', + RPNFunc('CELL', 2, nil))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateStringArg('$A$1'); + sollValues[Row] := StringResult('$A$1'); + MyWorksheet.WriteUTF8Text(Row, 2, sollValues[Row].ResString); - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("col", B1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("col", B1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('col', RPNCellRef('B1', @@ -2039,7 +3216,7 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(2); // Excel starts counting at 1 - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("format", B1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("format", B1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('format', RPNCellRef('B1', @@ -2047,7 +3224,7 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateStringArg('G'); - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("prefix", A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("prefix", A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('prefix', RPNCellRef('A1', @@ -2055,7 +3232,7 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateStringArg(''''); - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("row", B1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("row", B1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('row', RPNCellRef('B1', @@ -2063,7 +3240,7 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(1); // Excel starts counting at 1 - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("type", A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("type", A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('type', RPNCellRef('A1', @@ -2071,113 +3248,334 @@ SetLength(sollValues, Row+1); sollValues[Row] := CreateStringArg('l'); - MyWorksheet.WriteUTF8Text(Row, 0, '=CELL("type", B1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'CELL("type", B1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('type', RPNCellRef('B1', RPNFunc(fekCELLINFO, 2, nil))))); SetLength(sollValues, Row+1); sollValues[Row] := CreateStringArg('v'); +} +(* // INFO - MyWorksheet.WriteUTF8Text(Row, 0, '=INFO("directory", A1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'INFO("directory", A1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('directory', RPNFunc(fekINFO, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateStringArg(ExtractFilePath(TempFile)); - MyWorksheet.WriteUTF8Text(Row, 0, '=INFO("numfile")'); + MyWorksheet.WriteUTF8Text(Row, 0, 'INFO("numfile")'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('numfile', RPNFunc(fekINFO, nil)))); SetLength(sollValues, Row+1); sollValues[Row] := CreateNumberArg(MyWorkbook.GetWorksheetCount); - + *) // IsBlank - inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISBLANK(A1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRef('A1', - RPNFunc(fekISBLANK, nil)))); - SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); // cell contains text --> not blank inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISBLANK(G1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRef('G1', - RPNFunc(fekISBLANK, nil)))); + formula := 'ISBLANK(A1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('A1', + RPNFunc('ISBLANK', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); // the cell does not exist --> blank + sollValues[Row] := BooleanResult(false); // cell contains text --> not blank + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISBLANK(H1)'); + formula := 'ISBLANK(G1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('G1', + RPNFunc('ISBLANK', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); // the cell does not exist --> blank + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + inc(Row); + formula := 'ISBLANK(H1)'; MyWorksheet.WriteBlank(0, 7); // A11 is an empty cell - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRef('H1', - RPNFunc(fekISBLANK, nil)))); + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('H1', + RPNFunc('ISBLANK', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); // the cell exists, but it is empty + sollValues[Row] := BooleanResult(true); // the cell exists, but it is empty + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + // IsErr + inc(Row); + formula := 'ISERR(H2)'; + MyWorksheet.WriteFormula(1, 7, '1/0'); // Create an error in H2 + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + myWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('H2', + RPNFunc('ISERR', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); // there is an error in H2 + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + formula := 'ISERR(H3)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + myWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('H3', + RPNFunc('ISERR', nil)))) + else + Myworksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); // no error in H3 + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); // IsError inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISERROR(1/0)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNNumber(0, - RPNFunc(fekDiv, - RPNFunc(fekISERROR, nil)))))); + formula := 'ISERROR(1/0)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(0, + RPNFunc(fekDiv, + RPNFunc('ISERROR', nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); - // IsError inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISERROR(0/1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0, - RPNNumber(1, - RPNFunc(fekDiv, - RPNFunc(fekISERROR, nil)))))); + formula := 'ISERROR(0/1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(0, + RPNNumber(1, + RPNFunc(fekDiv, + RPNFunc('ISERROR', nil)))))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + inc(Row); + formula := 'ISERROR(H2)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('H2', + RPNFunc('ISERROR', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + inc(Row); + formula := 'ISERROR(H3)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('H3', + RPNFunc('ISERROR', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + // IsLogical + inc(Row); + formula := 'ISLOGICAL(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('B1', + RPNFunc('ISLOGICAL', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + // IsNONTEXT + inc(Row); + formula := 'ISNONTEXT(1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNFunc('ISNONTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + inc(Row); + formula := 'ISNONTEXT("A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('A', + RPNFunc('ISNONTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + inc(Row); + formula := 'ISNONTEXT(A1)'; // A1 contains a text + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('A1', + RPNFunc('ISNONTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + inc(Row); + formula := 'ISNONTEXT(B1)'; // B1 contains a number + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('B1', + RPNFunc('ISNONTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + // IsNumber + inc(Row); + formula := 'ISNUMBER(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('B1', + RPNFunc('ISNUMBER', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); // IsRef inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISREF(1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNFunc(fekISREF, nil)))); + formula := 'ISREF(1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNFunc('ISREF', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(false); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISREF(A1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellRef('A1', - RPNFunc(fekISREF, nil)))); + formula := 'ISREF(A1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('A1', + RPNFunc('ISREF', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateBoolArg(true); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=ISREF(A1)'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('A1', // we use a cell value here ! - RPNFunc(fekISREF, nil)))); + formula := 'ISREF(A1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellValue('A1', // we use a cell value here ! + RPNFunc('ISREF', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); // The correct result would be "false" because a cell value is not the same as // a cell reference. But Excel seems to ignore this difference here and // accepts only a "true". - sollValues[Row] := CreateBoolArg(true); + sollValues[Row] := BooleanResult(true); + MyWorksheet.WriteUTF8Text(Row, 2, 'TRUE'); - // VALUE + // IsText inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=VALUE("100")'); - MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('100', - RPNFunc(fekVALUE, nil)))); + formula := 'ISTEXT(1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNFunc('ISTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); SetLength(sollValues, Row+1); - sollValues[Row] := CreateNumberArg(100); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + + inc(Row); + formula := 'ISTEXT("A")'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNString('A', + RPNFunc('ISTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + inc(Row); + formula := 'ISTEXT(A1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('A1', + RPNFunc('ISTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(true); + Myworksheet.WriteUTF8Text(Row, 2, 'TRUE'); + + inc(Row); + formula := 'ISTEXT(B1)'; + MyWorksheet.WriteUTF8Text(Row, 0, formula); + if UseRPNFormula then + MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNCellRef('B1', + RPNFunc('ISTEXT', nil)))) + else + MyWorksheet.WriteFormula(Row, 1, formula); + SetLength(sollValues, Row+1); + sollValues[Row] := BooleanResult(false); + Myworksheet.WriteUTF8Text(Row, 2, 'FALSE'); + {------------------------------------------------------------------------------} { Error cases } @@ -2185,7 +3583,7 @@ {$IFDEF ENABLE_DEFECTIVE_FORMULAS } // Using less parameters than specified inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1)'); + MyWorksheet.WriteUTF8Text(Row, 0, 'IF(true,1)'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNBool(true, RPNNumber(1.0, @@ -2195,7 +3593,7 @@ // Using more parameters than specified inc(Row); - MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1,"A")'); + MyWorksheet.WriteUTF8Text(Row, 0, 'IF(true,1,"A")'); MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNBool(true, RPNNumber(1.0, diff --git a/components/fpspreadsheet/tests/testsutility.pas b/components/fpspreadsheet/tests/testsutility.pas index adb0f2bde..200a5028b 100644 --- a/components/fpspreadsheet/tests/testsutility.pas +++ b/components/fpspreadsheet/tests/testsutility.pas @@ -111,7 +111,10 @@ begin if not(assigned(Worksheet)) then result:='CellNotation: error getting worksheet.' else - result:=WorkSheet.Name+'!'+ColumnToLetter(Column)+inttostr(Row+1) + if Worksheet.Name <> '' then + result := WorkSheet.Name + '!' + ColumnToLetter(Column) + inttostr(Row+1) + else + Result := ColumnToLetter(Column) + IntToStr(Row + 1); end; function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String; @@ -119,7 +122,10 @@ begin if not Assigned(Worksheet) then Result := 'ColNotation: error getting worksheet.' else - Result := WorkSheet.Name + '!' + ColumnToLetter(Column); + if Worksheet.Name <> '' then + Result := WorkSheet.Name + '!' + ColumnToLetter(Column) + else + Result := ColumnToLetter(Column); end; function RowNotation(Worksheet: TsWorksheet; Row: Integer): String; @@ -127,7 +133,10 @@ begin if not Assigned(Worksheet) then Result := 'RowNotation: error getting worksheet.' else - Result := Worksheet.Name + '!' + IntToStr(Row+1); + if Worksheet.Name <> '' then + Result := Worksheet.Name + '!' + IntToStr(Row+1) + else + Result := IntToStr(Row+1); end; end. diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index 07da28268..c354d6357 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -605,7 +605,7 @@ begin end; { Formula token array } - if boReadFormulas in FWorkbook.Options then begin + if (boReadFormulas in FWorkbook.Options) then begin ok := ReadRPNTokenArray(AStream, cell); if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); end; @@ -1230,10 +1230,9 @@ end; } procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream); var - sheet: TsWorksheet; pane: Byte; begin - sheet := Workbook.GetFirstWorksheet; + FWorksheet := Workbook.GetFirstWorksheet; WriteBOF(AStream); WriteFonts(AStream); @@ -1241,21 +1240,21 @@ begin WriteFormats(AStream); WriteXFRecords(AStream); WriteColWidths(AStream); - WriteDimensions(AStream, sheet); - WriteRows(AStream, sheet); + WriteDimensions(AStream, FWorksheet); + WriteRows(AStream, FWorksheet); if (boVirtualMode in Workbook.Options) then WriteVirtualCells(AStream) else begin - WriteRows(AStream, sheet); - WriteCellsToStream(AStream, sheet.Cells); + WriteRows(AStream, FWorksheet); + WriteCellsToStream(AStream, FWorksheet.Cells); end; WriteWindow1(AStream); // { -- currently not working - WriteWindow2(AStream, sheet); - WritePane(AStream, sheet, false, pane); // false for "is not BIFF5 or BIFF8" - WriteSelections(AStream, sheet); + WriteWindow2(AStream, FWorksheet); + WritePane(AStream, FWorksheet, false, pane); // false for "is not BIFF5 or BIFF8" + WriteSelections(AStream, FWorksheet); //} WriteEOF(AStream); end; @@ -1630,11 +1629,6 @@ begin else WriteRPNTokenArray(AStream, AFormula, true, RPNLength); -(* -{ Formula data (RPN token array) } - WriteRPNTokenArray(AStream, AFormula, true, RPNLength); -*) - { Finally write sizes after we know them } FinalPos := AStream.Position; AStream.Position := RecordSizePos; @@ -1665,13 +1659,12 @@ var i: Integer; formula: TsRPNFormula; begin - SetLength(formula, Length(ACell^.SharedFormulaBase^.RPNFormulaValue)); - for i:=0 to Length(formula)-1 do begin - // Copy formula - formula[i] := ACell^.SharedFormulaBase^.RPNFormulaValue[i]; - // Adapt relative cell references + // Create RPN formula from the shared formula base's string formula + formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase); + + // Adapt relative cell references + for i:=0 to Length(formula)-1 do FixRelativeReferences(ACell, formula[i]); - end; // Write adapted copy of shared formula to stream. WriteRPNTokenArray(AStream, formula, true, RPNLength); diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index c2c77f236..452d810f8 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -222,7 +222,7 @@ var implementation uses - fpsStreams; + fpsStreams, fpsExprParser; const { Excel record IDs } @@ -1247,6 +1247,14 @@ begin inherited; end; + +var + + + counter: Integer = 0; + + + function TsSpreadBIFF8Reader.ReadWideString(const AStream: TStream; const ALength: WORD): WideString; var @@ -1264,16 +1272,15 @@ var begin StringFlags:=AStream.ReadByte; Dec(PendingRecordSize); + if StringFlags and 4 = 4 then begin + //Asian phonetics + //Read Asian phonetics Length (not used) + AsianPhoneticBytes:=DWordLEtoN(AStream.ReadDWord); + end; if StringFlags and 8 = 8 then begin //Rich string RunsCounter:=WordLEtoN(AStream.ReadWord); - dec(PendingRecordSize, 2); - end; - if StringFlags and 4 = 4 then begin - //Asian phonetics - //Read Asian phonetics length (not used) - AsianPhoneticBytes:=DWordLEtoN(AStream.ReadDWord); - dec(PendingRecordSize, 4); + dec(PendingRecordSize,2); end; if StringFlags and 1 = 1 Then begin //String is WideStringLE @@ -1290,6 +1297,11 @@ begin end else begin //String is 1 byte per char, this is UTF-16 with the high byte ommited because it is zero //so decompress and then convert + + + inc(Counter); + + lLen:=ALength; SetLength(DecomprStrValue, lLen); for i := 1 to lLen do @@ -1298,7 +1310,7 @@ begin DecomprStrValue[i] := C; Dec(PendingRecordSize); if (PendingRecordSize<=0) and (iINT_EXCEL_ID_CONTINUE then begin @@ -1310,13 +1322,14 @@ begin end; end; end; + Result := DecomprStrValue; end; if StringFlags and 8 = 8 then begin - //Rich string (This only happens in BIFF8) + //Rich string (This only happened in BIFF8) for j := 1 to RunsCounter do begin if (PendingRecordSize<=0) then begin - //A CONTINUE may happen here + //A CONTINUE may happend here RecordType := WordLEToN(AStream.ReadWord); RecordSize := WordLEToN(AStream.ReadWord); if RecordType<>INT_EXCEL_ID_CONTINUE then begin diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 2d90b2b5e..0b0ded825 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -67,176 +67,6 @@ const INT_FONT_WEIGHT_NORMAL = $0190; INT_FONT_WEIGHT_BOLD = $02BC; - { Formula constants TokenID values } - - { Binary Operator Tokens 3.6} - INT_EXCEL_TOKEN_TADD = $03; - 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_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) - INT_EXCEL_TOKEN_TPAREN = $15; // Operator in parenthesis - - { 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; //(unsigned) integer - INT_EXCEL_TOKEN_TNUM = $1F; //floating-point - - { Operand Tokens } - // _R: reference; _V: value; _A: array - 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; - INT_EXCEL_TOKEN_TREFN_R = $2C; - INT_EXCEL_TOKEN_TREFN_V = $4C; - INT_EXCEL_TOKEN_TREFN_A = $6C; - - { 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; - - //VAR: variable number of arguments: - INT_EXCEL_TOKEN_FUNCVAR_R = $22; - INT_EXCEL_TOKEN_FUNCVAR_V = $42; - INT_EXCEL_TOKEN_FUNCVAR_A = $62; - - { Special tokens } - INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula - - { Built-in/worksheet functions } - 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 -// 02H tTbl Multiple operation table -// 15H tParen Parentheses -// 18H tNlr Natural language reference (BIFF8) - INT_EXCEL_TOKEN_TATTR = $19; // tAttr Special attribute -// 1AH tSheet Start of external sheet reference (BIFF2-BIFF4) -// 1BH tEndSheet End of external sheet reference (BIFF2-BIFF4) - { CODEPAGE record constants } WORD_ASCII = 367; WORD_UTF_16 = 1200; // BIFF 8 @@ -475,7 +305,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): Word; +// function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word; // Helper function for writing a string with 8-bit length } function WriteString_8BitLen(AStream: TStream; AString: String): Integer; virtual; @@ -555,11 +385,11 @@ type implementation uses - AVL_Tree, Math, Variants, fpsNumFormatParser; + AVL_Tree, Math, Variants, xlsConst, fpsNumFormatParser, fpsExprParser; -{ Helper table for rpn formulas: - Assignment of FormulaElementKinds (fekXXXX) to EXCEL_TOKEN IDs. } const + { Helper table for rpn formulas: + Assignment of FormulaElementKinds (fekXXXX) to EXCEL_TOKEN IDs. } TokenIDs: array[TFEKind] of Word = ( // Basic operands INT_EXCEL_TOKEN_TREFV, {fekCell} @@ -590,6 +420,9 @@ const INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=} INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>} INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis} + Word(-1) {fekFunc} + ); + (* // Math functions INT_EXCEL_SHEET_FUNC_ABS, {fekABS} @@ -711,7 +544,7 @@ const // Other operations INT_EXCEL_TOKEN_TATTR {fekOpSum} ); - + *) type TBIFF58BlankRecord = packed record RecordID: Word; @@ -1188,7 +1021,6 @@ var err: TsErrorValue; ok: Boolean; cell: PCell; - begin { Index to XF Record } ReadRowColXF(AStream, ARow, ACol, XF); @@ -1247,7 +1079,7 @@ begin if IsDateTime(ResultFormula, nf, nfs, dt) then FWorksheet.WriteDateTime(cell, dt, nf, nfs) else - FWorksheet.WriteNumber(cell, ResultFormula, nf, nfs); //, nd, ncs); + FWorksheet.WriteNumber(cell, ResultFormula, nf, nfs); end; { Formula token array } @@ -1274,7 +1106,7 @@ var begin ARow := WordLEtoN(AStream.ReadWord); fc := WordLEtoN(AStream.ReadWord); - pending := RecordSize - Sizeof(fc) - Sizeof(ARow); + pending := RecordSize - SizeOf(fc) - SizeOf(ARow); if FIsVirtualMode then begin InitCell(ARow, 0, FVirtualCell); cell := @FVirtualCell; @@ -1626,7 +1458,7 @@ begin end; { Reads the array of rpn tokens from the current stream position, creates an - rpn formula and stores it in the cell. } + rpn formula, converts it to a string formula and stores it in the cell. } function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream; ACell: PCell): Boolean; var @@ -1640,9 +1472,11 @@ var r, c, r2, c2: Cardinal; dr, dc: Integer; fek: TFEKind; - func: Word; + exprDef: TsBuiltInExprIdentifierDef; + funcCode: Word; b: Byte; found: Boolean; + formula: TsRPNformula; begin rpnItem := nil; n := ReadRPNTokenArraySize(AStream); @@ -1698,16 +1532,11 @@ begin INT_EXCEL_TOKEN_FUNC_A: // functions with fixed argument count begin - func := ReadRPNFunc(AStream); - found := false; - for fek in TFuncTokens do begin - if (TokenIDs[fek] = func) and FixedParamCount(fek) then begin - rpnItem := RPNFunc(fek, rpnItem); - found := true; - break; - end; - end; - if not found then + funcCode := ReadRPNFunc(AStream); + exprDef := BuiltInIdentifiers.IdentifierByExcelCode(funcCode); + if exprDef <> nil then + rpnItem := RPNFunc(exprDef.Name, rpnItem) + else supported := false; end; @@ -1717,15 +1546,11 @@ begin // functions with variable argument count begin b := AStream.ReadByte; - func := ReadRPNFunc(AStream); - found := false; - for fek in TFuncTokens do - if (TokenIDs[fek] = func) and not FixedParamCount(fek) then begin - rpnItem := RPNFunc(fek, b, rpnItem); - found := true; - break; - end; - if not found then + funcCode := ReadRPNFunc(AStream); + exprDef := BuiltinIdentifiers.IdentifierByExcelCode(funcCode); + if exprDef <> nil then + rpnItem := RPNFunc(exprDef.Name, b, rpnItem) + else supported := false; end; @@ -1751,11 +1576,11 @@ begin end; if not supported then begin DestroyRPNFormula(rpnItem); - SetLength(ACell^.RPNFormulaValue, 0); Result := false; end else begin - ACell^.RPNFormulaValue := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items! + formula := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items! + ACell^.FormulaValue := FWorksheet.ConvertRPNFormulaToStringFormula(formula); Result := true; end; end; @@ -1938,10 +1763,11 @@ begin end else Result := AColor; end; - + (* function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( AElementKind: TFEKind; out ASecondaryID: Word): Word; begin + if AElementKind = fekFunc then if (AElementKind >= Low(TFuncTokens)) and (AElementKind <= High(TFuncTokens)) then begin if FixedParamCount(AElementKind) then @@ -1955,7 +1781,7 @@ begin ASecondaryID := 0; end; end; - + *) procedure TsSpreadBIFFWriter.GetLastRowCallback(ACell: PCell; AStream: TStream); begin Unused(AStream); @@ -2174,12 +2000,17 @@ end; { Writes an Excel FORMULA record. Note: The formula is already stored in the cell. - Since BIFF files contain RPN formulas the method calls WriteRPNFormula. + Since BIFF files contain RPN formulas the string formula of the cell is + converted to an RPN formula and the method calls WriteRPNFormula. } procedure TsSpreadBIFFWriter.WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); +var + formula: TsRPNFormula; begin - WriteRPNFormula(AStream, ARow, ACol, ACell^.RPNFormulaValue, ACell); + formula := FWorksheet.BuildRPNFormula(ACell); + WriteRPNFormula(AStream, ARow, ACol, formula, ACell); + SetLength(formula, 0); end; { Writes a 64-bit floating point NUMBER record. @@ -2586,10 +2417,12 @@ procedure TsSpreadBIFFWriter.WriteRPNTokenArray(AStream: TStream; const AFormula: TsRPNFormula; WriteTokenArraySize: Boolean; var RPNLength: Word); var i: Integer; - tokenID, secondaryID: Word; n: Word; TokenArraySizePos: Int64; FinalPos: Int64; + exprDef: TsExprIdentifierDef; + excelCode: Word; + primaryExcelCode, secondaryExcelCode: Word; begin RPNLength := 0; @@ -2604,12 +2437,22 @@ begin for i := 0 to Length(AFormula) - 1 do begin { Token identifier } - tokenID := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, secondaryID); - AStream.WriteByte(tokenID); + if AFormula[i].ElementKind = fekFunc then begin + exprDef := BuiltinIdentifiers.IdentifierByName(Aformula[i].FuncName); + if exprDef.HasFixedArgumentCount then + primaryExcelCode := INT_EXCEL_TOKEN_FUNC_V + else + primaryExcelCode := INT_EXCEL_TOKEN_FUNCVAR_V; + secondaryExcelCode := exprDef.ExcelCode; + end else begin + primaryExcelCode := TokenIDs[AFormula[i].ElementKind]; + secondaryExcelCode := 0; + end; + AStream.WriteByte(primaryExcelCode); inc(RPNLength); { Token data } - case tokenID of + case primaryExcelCode of { Operand Tokens } INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } // INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset} @@ -2651,6 +2494,12 @@ begin inc(RPNLength, 8); end; + INT_EXCEL_TOKEN_TINT: { fekNum, but integer } + begin + AStream.WriteBuffer(AFormula[i].IntValue, 2); + inc(RPNLength, 2); + end; + INT_EXCEL_TOKEN_TSTR: { fekString } { string constant is stored as widestring in BIFF8, otherwise as ansistring Writing is done by the virtual method WriteString_8bitLen. } @@ -2667,7 +2516,7 @@ begin // Functions with fixed parameter count INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: begin - n := WriteRPNFunc(AStream, secondaryID); + n := WriteRPNFunc(AStream, secondaryExcelCode); inc(RPNLength, n); end; @@ -2675,7 +2524,7 @@ begin INT_EXCEL_TOKEN_FUNCVAR_V: begin AStream.WriteByte(AFormula[i].ParamsNum); - n := WriteRPNFunc(AStream, secondaryID); + n := WriteRPNFunc(AStream, secondaryExcelCode); inc(RPNLength, 1 + n); end; @@ -2933,14 +2782,13 @@ begin // Number of existing formula records AStream.WriteByte((r2-r1+1) * (c2-c1+1)); - // Copy the formula (we don't want to overwrite the cell formulas) + // Create an RPN formula from the shared formula base's string formula // and adjust relative references - SetLength(formula, Length(ACell^.SharedFormulaBase^.RPNFormulaValue)); - for i:=0 to Length(ACell^.SharedFormulaBase^.RPNFormulaValue)-1 do begin - formula[i] := ACell^.SharedFormulaBase^.RPNFormulaValue[i]; + formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase); + for i:=0 to Length(formula)-1 do FixRelativeReferences(ACell, formula[i]); - end; - // Writes the (copied) rpn token array + + // Writes the rpn token array WriteRPNTokenArray(AStream, formula, true, RPNLength); { Write record size at the end after we known it } diff --git a/components/fpspreadsheet/xlsxooxml.pas b/components/fpspreadsheet/xlsxooxml.pas index 24c58c038..b18505353 100755 --- a/components/fpspreadsheet/xlsxooxml.pas +++ b/components/fpspreadsheet/xlsxooxml.pas @@ -114,7 +114,7 @@ type function GetStyleIndex(ACell: PCell): Cardinal; procedure ListAllBorders; procedure ListAllFills; - function PrepareFormula(const AFormula: TsFormula): String; + function PrepareFormula(const AFormula: String): String; procedure ResetStreams; procedure WriteBorderList(AStream: TStream); procedure WriteCols(AStream: TStream; AWorksheet: TsWorksheet); @@ -648,7 +648,7 @@ begin if datanode.NodeName = 'v' then dataStr := GetNodeValue(datanode) else - if datanode.NodeName = 'f' then + if (boReadFormulas in FWorkbook.Options) and (datanode.NodeName = 'f') then begin // Formula to cell formulaStr := GetNodeValue(datanode); @@ -660,12 +660,12 @@ begin s := GetAttrValue(datanode, 'ref'); if (s <>'') then begin - cell^.FormulaValue.FormulaStr := '=' + formulaStr; + cell^.FormulaValue := formulaStr; FWorksheet.UseSharedFormula(s, cell); end; end else - cell^.FormulaValue.FormulaStr := '=' + formulaStr; + cell^.FormulaValue := formulaStr; end; end; datanode := datanode.NextSibling; @@ -2407,10 +2407,11 @@ begin end; { Prepares a string formula for writing } -function TsSpreadOOXMLWriter.PrepareFormula(const AFormula: TsFormula): String; +function TsSpreadOOXMLWriter.PrepareFormula(const AFormula: String): String; begin - Result := AFormula.FormulaStr; + Result := AFormula; if (Result <> '') and (Result[1] = '=') then Delete(Result, 1, 1); + Result := UTF8TextToXMLText(Result) end; { Is called before zipping the individual file parts. Rewinds the streams. } @@ -2426,15 +2427,6 @@ begin ResetStream(FSSharedStrings_complete); for i := 0 to High(FSSheets) do ResetStream(FSSheets[i]); - { - FSContentTypes.Position := 0; - FSRelsRels.Position := 0; - FSWorkbookRels.Position := 0; - FSWorkbook.Position := 0; - FSStyles.Position := 0; - FSSharedStrings_complete.Position := 0; - for stream in FSSheets do stream.Position := 0; - } end; { @@ -2544,6 +2536,7 @@ var r, c, r2, c2: Cardinal; cell: PCell; id: Cardinal; + t, v: String; begin cellPosText := TsWorksheet.CellPosToText(ARow, ACol); lStyleIndex := GetStyleIndex(ACell); @@ -2596,15 +2589,53 @@ begin CellPosText, lStyleIndex, PtrInt(ACell^.SharedFormulaBase) // ID of the shared formula ])); - end else + end else begin // "normal" formula + case ACell^.ContentType of + cctFormula: + begin + t := ''; + v := ''; + end; + cctUTF8String: + begin + t := ' t="str"'; + v := Format('%s', [UTF8TextToXMLText(ACell^.UTF8StringValue)]); + end; + cctNumber: + begin + t := ''; + v := Format('%g', [ACell^.NumberValue], FPointSeparatorSettings); + end; + cctDateTime: + begin + t := ''; + v := Format('%g', [ACell^.DateTimeValue], FPointSeparatorSettings); + end; + cctBool: + begin + t := ' t="b"'; + if ACell^.BoolValue then + v := '1' + else + v := '0'; + end; + cctError: + begin + t := ' t="e"'; + v := Format('%s', [GetErrorValueStr(ACell^.ErrorValue)]); + end; + end; AppendToStream(AStream, Format( - '' + + '' + '%s' + + '%s' + '', [ - CellPosText, lStyleIndex, - PrepareFormula(ACell^.FormulaValue) + CellPosText, lStyleIndex, t, + PrepareFormula(ACell^.FormulaValue), + v ])); + end; end; {*******************************************************************