diff --git a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi index 1d5216e28..e8dbb9b11 100644 --- a/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi +++ b/components/fpspreadsheet/examples/fpsgrid/fpsgrid.lpi @@ -129,7 +129,7 @@ - + @@ -137,10 +137,11 @@ + - + @@ -231,11 +232,10 @@ - - + diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 0c98e5188..467707594 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -75,7 +75,7 @@ type fekCell, fekCellRef, fekCellRange, fekNum, fekInteger, fekString, fekBool, fekErr, fekMissingArg, { Basic operations } - fekAdd, fekSub, fekDiv, fekMul, fekPercent, fekPower, fekUMinus, fekUPlus, + fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, fekConcat, // string concatenation fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, fekParen, @@ -141,14 +141,15 @@ type cctUTF8String, cctDateTime, cctBool, cctError); {@@ Error code values } - TErrorValue = ( + TsErrorValue = ( + errOK, // no error errEmptyIntersection, // #NULL! errDivideByZero, // #DIV/0! errWrongType, // #VALUE! errIllegalRef, // #REF! errWrongName, // #NAME? errOverflow, // #NUM! - errArgNotAvail, // #N/A + errArgError, // #N/A // --- no Excel errors -- errFormulaNotSupported ); @@ -314,7 +315,7 @@ type UTF8StringValue: ansistring; DateTimeValue: TDateTime; BoolValue: Boolean; - StatusValue: Byte; + ErrorValue: TsErrorValue; { Formatting fields } { When adding/deleting formatting fields don't forget to update CopyFormat! } UsedFormattingFields: TsUsedFormattingFields; @@ -419,8 +420,8 @@ type AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload; procedure WriteDateTime(ACell: PCell; AValue: TDateTime; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload; - procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); overload; - procedure WriteErrorValue(ACell: PCell; AValue: TErrorValue); overload; + procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue); overload; + procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload; procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2; @@ -797,7 +798,7 @@ resourcestring lpErrIllegalRef = '#REF!'; lpErrWrongName = '#NAME?'; lpErrOverflow = '#NUM!'; - lpErrArgNotAvail = '#N/A'; + lpErrArgError = '#N/A'; lpErrFormulaNotSupported = ''; var @@ -879,8 +880,8 @@ const { Basic operations } (Symbol:'+'; MinParams:2; MaxParams:2), // fekAdd (Symbol:'-'; MinParams:2; MaxParams:2), // fekSub - (Symbol:'*'; MinParams:2; MaxParams:2), // fekDiv - (Symbol:'/'; MinParams:2; MaxParams:2), // fekMul + (Symbol:'*'; MinParams:2; MaxParams:2), // fekMul + (Symbol:'/'; MinParams:2; MaxParams:2), // fekDiv (Symbol:'%'; MinParams:1; MaxParams:1), // fekPercent (Symbol:'^'; MinParams:2; MaxParams:2), // fekPower (Symbol:'-'; MinParams:1; MaxParams:1), // fekUMinus @@ -1488,14 +1489,14 @@ begin cctBool: Result := IfThen(BoolValue, lpTRUE, lpFALSE); cctError: - case TErrorValue(StatusValue and $0F) of + case TsErrorValue(ErrorValue) of errEmptyIntersection : Result := lpErrEmptyIntersection; errDivideByZero : Result := lpErrDivideByZero; errWrongType : Result := lpErrWrongType; errIllegalRef : Result := lpErrIllegalRef; errWrongName : Result := lpErrWrongName; errOverflow : Result := lpErrOverflow; - errArgNotAvail : Result := lpErrArgNotAvail; + errArgError : Result := lpErrArgError; errFormulaNotSupported: Result := lpErrFormulaNotSupported; end; else @@ -1548,6 +1549,7 @@ end; function TsWorksheet.ReadRPNFormulaAsString(ACell: PCell): String; var + fs: TFormatSettings; formula: TsRPNFormula; elem: TsFormulaElement; i, j: Integer; @@ -1555,18 +1557,12 @@ var s: String; ptr: Pointer; fek: TFEKind; - - procedure Store(s: String); - begin - L.Clear; - L.Add(s); - end; - begin Result := ''; if ACell = nil then exit; + fs := Workbook.FormatSettings; L := TStringList.Create; try for i:=0 to Length(ACell^.RPNFormulaValue)-1 do begin @@ -1574,42 +1570,21 @@ begin ptr := Pointer(elem.ElementKind); case elem.ElementKind of fekNum: - L.AddObject(Format('%g', [elem.DoubleValue]), ptr); + 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(elem.DoubleValue=0, 'TRUE', 'FALSE'), ptr); + 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); // Operations: - fekAdd : L.AddObject('+', ptr); - fekSub : L.AddObject('-', ptr); - fekMul : L.AddObject('*', ptr); - fekDiv : L.AddObject('/', ptr); - fekPower : L.AddObject('^', ptr); - fekConcat : L.AddObject('&', ptr); - fekParen : L.AddObject('', ptr); - fekEqual : L.AddObject('=', ptr); - fekNotEqual : L.AddObject('<>', ptr); - fekLess : L.AddObject('<', ptr); - fekLessEqual : L.AddObject('<=', ptr); - fekGreater : L.AddObject('>', ptr); - fekGreaterEqual: L.AddObject('>=', ptr); - fekPercent : L.AddObject('%', ptr); - fekUPlus : L.AddObject('+', ptr); - fekUMinus : L.AddObject('-', ptr); - fekCellInfo : L.AddObject('CELL', ptr); // That's the function name! else - begin - s := GetEnumName(TypeInfo(TFEKind), integer(elem.ElementKind)); - Delete(s, 1, 3); - L.AddObject(s, ptr); - end; + L.AddObject(FEProps[elem.ElementKind].Symbol, ptr); end; end; @@ -1619,39 +1594,56 @@ begin case fek of fekAdd, fekSub, fekMul, fekDiv, fekPower, fekConcat, fekEqual, fekNotEqual, fekLess, fekLessEqual, fekGreater, fekGreaterEqual: - begin + if i+2 < L.Count then begin L.Strings[i] := Format('%s%s%s', [L[i+2], L[i], L[i+1]]); - L.Objects[i] := pointer(fekString); L.Delete(i+2); L.Delete(i+1); + L.Objects[i] := pointer(fekString); + end else begin + Result := '=' + lpErrArgError; + exit; end; fekUPlus, fekUMinus: - begin + if i+1 < L.Count then begin L.Strings[i] := L[i]+L[i+1]; - L.Objects[i] := Pointer(fekString); L.Delete(i+1); + L.Objects[i] := Pointer(fekString); + end else begin + Result := '=' + lpErrArgError; + exit; end; fekPercent: - begin + if i+1 < L.Count then begin L.Strings[i] := L[i+1]+L[i]; - L.Objects[i] := Pointer(fekString); L.Delete(i+1); + L.Objects[i] := Pointer(fekString); + end else begin + Result := '=' + lpErrArgError; + exit; end; fekParen: - begin + if i+1 < L.Count then begin L.Strings[i] := Format('(%s)', [L[i+1]]); - L.Objects[i] := pointer(fekString); L.Delete(i+1); + L.Objects[i] := pointer(fekString); + end else begin + Result := '=' + lpErrArgError; + exit; end; else if fek >= fekAdd then begin elem := ACell^.RPNFormulaValue[i]; s := ''; for j:= i+elem.ParamsNum downto i+1 do begin - s := s + ',' + L[j]; - L.Delete(j); + 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, 1); + Delete(s, 1, 2); L.Strings[i] := Format('%s(%s)', [L[i], s]); L.Objects[i] := pointer(fekString); end; @@ -1659,7 +1651,10 @@ begin dec(i); end; - Result := '=' + L[0]; + if L.Count > 1 then + Result := '=' + lpErrArgError // too many arguments + else + Result := '=' + L[0]; finally L.Free; @@ -1920,16 +1915,16 @@ end; @param ACol The column of the cell @param AValue The error code value } -procedure TsWorksheet.WriteErrorValue(ARow, ACol: Cardinal; AValue: TErrorValue); +procedure TsWorksheet.WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue); begin WriteErrorValue(GetCell(ARow, ACol), AValue); end; -procedure TsWorksheet.WriteErrorValue(ACell: PCell; AValue: TErrorValue); +procedure TsWorksheet.WriteErrorValue(ACell: PCell; AValue: TsErrorValue); begin if ACell <> nil then begin ACell^.ContentType := cctError; - ACell^.StatusValue := (ACell^.StatusValue and $F0) or ord(AValue); + ACell^.ErrorValue := AValue; ChangedCell(ACell^.Row, ACell^.Col); end; end; @@ -3927,7 +3922,6 @@ begin raise Exception.CreateFmt(lpSpecifyNumberOfParams, [FEProps[AToken].Symbol]); Result := RPNFunc(AToken, FEProps[AToken].MinParams, ANext); - Result^.Next := ANext; end; {@@ diff --git a/components/fpspreadsheet/fpsutils.pas b/components/fpspreadsheet/fpsutils.pas index 8c31c29f9..7cee8c2a6 100644 --- a/components/fpspreadsheet/fpsutils.pas +++ b/components/fpspreadsheet/fpsutils.pas @@ -62,6 +62,8 @@ function GetColString(AColIndex: Integer): String; function GetCellString(ARow,ACol: Cardinal; AFlags: TsRelFlags): String; function GetCellRangeString(ARow1, ACol1, ARow2, ACol2: Cardinal; AFlags: TsRelFlags): String; +function GetErrorValueStr(AErrorValue: TsErrorValue): String; + function UTF8TextToXMLText(AText: ansistring): ansistring; function TwipsToMillimeters(AValue: Integer): Single; @@ -431,19 +433,20 @@ begin Result := Char(AValue + ord('A')); end; +{ Calculates an Excel column name ('A', 'B' etc) from the zero-based column index } function GetColString(AColIndex: Integer): String; +{ Code adapted from: http://stackoverflow.com/questions/12796973/vba-function-to-convert-column-number-to-letter } +var + n: Integer; + c: byte; begin - if AColIndex < 26 then - Result := Letter(AColIndex) - else - if AColIndex < 26*26 then - Result := Letter(AColIndex div 26) + Letter(AColIndex mod 26) - else - if AColIndex < 26*26*26 then - Result := Letter(AColIndex div (26*26)) + Letter((AColIndex mod (26*26)) div 26) - + Letter(AColIndex mod (26*26*26)) - else - Result := 'too big'; + Result := ''; + n := AColIndex + 1; + while (n > 0) do begin + c := (n - 1) mod 26; + Result := char(c + ord('A')) + Result; + n := (n - c) div 26; + end; end; const @@ -469,6 +472,24 @@ begin end; +{ Returns the message text assigned to an error value } +function GetErrorValueStr(AErrorValue: TsErrorValue): String; +begin + case AErrorValue of + errOK : Result := ''; + errEmptyIntersection : Result := '#NULL!'; + errDivideByZero : Result := '#DIV/0!'; + errWrongType : Result := '#VALUE!'; + errIllegalRef : Result := '#REF!'; + errWrongName : Result := '#NAME?'; + errOverflow : Result := '#NUM!'; + errArgError : Result := '#N/A'; + // --- no Excel errors -- + errFormulaNotSupported : Result := '#FORMULA?'; + else Result := '#UNKNOWN ERROR'; + end; +end; + {In XML files some chars must be translated} function UTF8TextToXMLText(AText: ansistring): ansistring; var diff --git a/components/fpspreadsheet/tests/formulatests.pas b/components/fpspreadsheet/tests/formulatests.pas index 9e17dc706..c11d70a73 100644 --- a/components/fpspreadsheet/tests/formulatests.pas +++ b/components/fpspreadsheet/tests/formulatests.pas @@ -26,6 +26,10 @@ type published // Writes out numbers & reads back. // If previous read tests are ok, this effectively tests writing. + { BIFF2 Tests } + procedure TestWriteReadBIFF2_FormulaStrings; + { BIFF5 Tests } + procedure TestWriteReadBIFF5_FormulaStrings; { BIFF8 Tests } procedure TestWriteReadBIFF8_FormulaStrings; end; @@ -33,7 +37,7 @@ type implementation uses - rpnFormulaUnit; + fpsUtils, rpnFormulaUnit; { TSpreadWriteReadFormatTests } @@ -59,21 +63,28 @@ var expected: String; actual: String; cell: PCell; + fs: TFormatSettings; begin TempFile := GetTempFileName; // Create test workbook MyWorkbook := TsWorkbook.Create; +// MyWorkbook.FormatSettings.DecimalSeparator := '.'; +// MyWorkbook.FormatSettings.ShortDateFormat := 'yyyy-mm-dd'; MyWorkSheet:= MyWorkBook.AddWorksheet(SHEET); // Write out all test formulas // All formulas are in column B - WriteRPNFormulaSamples(MyWorksheet, AFormat); + WriteRPNFormulaSamples(MyWorksheet, AFormat, true); MyWorkBook.WriteToFile(TempFile, AFormat, true); MyWorkbook.Free; // Open the spreadsheet MyWorkbook := TsWorkbook.Create; + MyWorkbook.ReadFormulas := true; +// MyWorkbook.FormatSettings.DecimalSeparator := '.'; +// MyWorkbook.FormatSettings.ShortDateFormat := 'yyyy-mm-dd'; + MyWorkbook.ReadFromFile(TempFile, AFormat); if AFormat = sfExcel2 then MyWorksheet := MyWorkbook.GetFirstWorksheet @@ -86,7 +97,7 @@ begin if (cell <> nil) and (Length(cell^.RPNFormulaValue) > 0) then begin actual := MyWorksheet.ReadRPNFormulaAsString(cell); expected := MyWorksheet.ReadAsUTF8Text(Row, 0); - CheckEquals(actual, expected, 'Test read formula mismatch, cell '+CellNotation(MyWorkSheet,Row,Col)); + CheckEquals(expected, actual, 'Test read formula mismatch, cell '+CellNotation(MyWorkSheet,Row,1)); end; end; @@ -95,12 +106,21 @@ begin DeleteFile(TempFile); end; +procedure TSpreadWriteReadFormulaTests.TestWriteReadBIFF2_FormulaStrings; +begin + TestWriteReadFormulaStrings(sfExcel2); +end; + +procedure TSpreadWriteReadFormulaTests.TestWriteReadBIFF5_FormulaStrings; +begin + TestWriteReadFormulaStrings(sfExcel5); +end; + procedure TSpreadWriteReadFormulaTests.TestWriteReadBIFF8_FormulaStrings; begin TestWriteReadFormulaStrings(sfExcel8); end; - initialization // Register so these tests are included in a full run RegisterTest(TSpreadWriteReadFormulaTests); diff --git a/components/fpspreadsheet/tests/manualtests.pas b/components/fpspreadsheet/tests/manualtests.pas index 4c749e749..3f696e441 100644 --- a/components/fpspreadsheet/tests/manualtests.pas +++ b/components/fpspreadsheet/tests/manualtests.pas @@ -212,7 +212,7 @@ begin Workbook := TsWorkbook.Create; Worksheet := Workbook.AddWorksheet(RPNSHEETNAME); - WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT); + WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false); end; {$ENDIF} diff --git a/components/fpspreadsheet/tests/rpnformulaunit.pas b/components/fpspreadsheet/tests/rpnformulaunit.pas index ea525dd8d..1d4721a8a 100644 --- a/components/fpspreadsheet/tests/rpnformulaunit.pas +++ b/components/fpspreadsheet/tests/rpnformulaunit.pas @@ -5,7 +5,8 @@ interface uses SysUtils, fpspreadsheet,fpsutils; -procedure WriteRPNFormulaSamples(Worksheet: TsWorksheet; AFormat: TsSpreadsheetFormat); +procedure WriteRPNFormulaSamples(Worksheet: TsWorksheet; + AFormat: TsSpreadsheetFormat; IncludeErrors: Boolean); implementation @@ -15,7 +16,8 @@ uses const FALSE_TRUE: array[Boolean] of String = ('FALSE', 'TRUE'); -procedure WriteRPNFormulaSamples(Worksheet: TsWorksheet; AFormat: TsSpreadSheetFormat); +procedure WriteRPNFormulaSamples(Worksheet: TsWorksheet; + AFormat: TsSpreadSheetFormat; IncludeErrors: Boolean); const cellB1 = 1.0; cellC1 = 2.0; @@ -31,12 +33,23 @@ var value: Double; r,c: integer; celladdr: String; + fs: TFormatSettings; + ls: char; begin if Worksheet = nil then exit; + fs := Worksheet.Workbook.FormatSettings; + ls := fs.ListSeparator; + { When fpspreadsheet creates a formula string it uses the list separator of + the formatting to separate arguments. Therefore, we insert the list separator + in the expected formula strings as well. Unfortunately, these strings look + a bit inconvenient here: + '=SUM(A1, B1)' becomes Format('=SUM(A1%s B1)', [ls]) } + Worksheet.WriteUTF8Text(0, 0, SBaseCells); Worksheet.WriteUsedFormatting(0, 0, [uffBold]); + Worksheet.WriteNumber(0,1, cellB1); Worksheet.WriteNumber(0,2, cellC1); Worksheet.WriteNumber(0,3, cellD1); @@ -58,8 +71,8 @@ begin // Numbers inc(Row); - value := 1.2345; - Worksheet.WriteUTF8Text(Row, 0, '=1.2345'); + value := 1.2; + Worksheet.WriteUTF8Text(Row, 0, '='+Format('%.1f', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, nil)) @@ -271,7 +284,7 @@ begin // String concatenation inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '="Hello " & "world"'); + Worksheet.WriteUTF8Text(Row, 0, '="Hello "&"world"'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('Hello ', RPNString('world', @@ -495,9 +508,9 @@ begin nil)))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[not (cellC1=cellC1)]); - // Logical AND - case false/false + // Logical AND - case false/false =AND(1=0, 1=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=AND(1=0,1=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=AND(1=0%s 1=2)',[ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(0, @@ -509,9 +522,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (1=2)]); - // Logical AND - case false/true + // Logical AND - case false/true =AND(1=0, 2=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=AND(1=0,2=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=AND(1=0%s 2=2)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(0, @@ -523,9 +536,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (2=2)]); - // Logical AND - case true/true + // Logical AND - case true/true =AND(1=1, 2=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=AND(1=1,2=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=AND(1=1%s 2=2)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(1, @@ -537,9 +550,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) and (2=2)]); - // Logical OR - case false/false + // Logical OR - case false/false =OR(1=0, 1=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=OR(1=0,1=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=OR(1=0%s 1=2)',[ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(0, @@ -551,9 +564,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (1=2)]); - // Logical OR - case false/true + // Logical OR - case false/true =OR(1=0, 2=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=OR(1=0,2=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=OR(1=0%s 2=2)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(0, @@ -565,9 +578,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (2=2)]); - // Logical OR - case true/true + // Logical OR - case true/true =OR(1=1, 2=2) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=OR(1=1,2=2)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=OR(1=1%s 2=2)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1, RPNNumber(1, @@ -579,9 +592,9 @@ begin nil))))))))); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) or (2=2)]); - // IF - case true + // IF - case true =IF(B1=1, "correct", "wrong") inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=IF(B1=1,"correct","wrong")'); + Worksheet.WriteUTF8Text(Row, 0, Format('=IF(B1=1%s "correct"%s "wrong")', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('B1', RPNNumber(1, @@ -592,9 +605,9 @@ begin nil)))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'wrong')); - // IF - case false + // IF - case false =IF(B1<>1, "correct", "wrong") inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=IF(B1<>1,"correct","wrong")'); + Worksheet.WriteUTF8Text(Row, 0, Format('=IF(B1<>1%s "correct"%s "wrong")', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('B1', RPNNumber(1, @@ -605,9 +618,9 @@ begin nil)))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'wrong')); - // IF - case true (2 params) + // IF - case true (2 params) =IF(B1=1, "correct") inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=IF(B1=1,"correct")'); + Worksheet.WriteUTF8Text(Row, 0, Format('=IF(B1=1%s "correct")', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('B1', RPNNumber(1, @@ -617,9 +630,9 @@ begin nil))))))); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'FALSE')); - // IF - case false (2 params) + // IF - case false (2 params) =IF(B1<>1, "correct") inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=IF(B1<>1,"correct")'); + Worksheet.WriteUTF8Text(Row, 0, Format('=IF(B1<>1%s "correct")', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('B1', RPNNumber(1, @@ -732,10 +745,10 @@ begin nil)))))); Worksheet.WriteNumber(Row, 2, sin(pi/2)); - // arcsin(0.5) + // arcsin(0.5) =ASIN(0.5) inc(Row); value := 0.5; - Worksheet.WriteUTF8Text(Row, 0, '=ASIN(0.5)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ASIN(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekASIN, @@ -751,10 +764,10 @@ begin nil)))); Worksheet.WriteNumber(Row, 2, cos(pi)); - // arccos(0.5) + // arccos(0.5) =ACOS(0.5) inc(Row); value := 0.5; - Worksheet.WriteUTF8Text(Row, 0, '=ACOS(0.5)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ACOS(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekACOS, @@ -795,10 +808,10 @@ begin nil)))); Worksheet.WriteNumber(Row, 2, sinh(value)); - // arcsinh + // arcsinh =ASIN(0.5) inc(Row); value := 0.5; - Worksheet.WriteUTF8Text(Row, 0, '=ASINH(0.5)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ASINH(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekASINH, @@ -817,8 +830,8 @@ begin // arccosh inc(Row); - value := 10.0; - Worksheet.WriteUTF8Text(Row, 0, '=ACOSH(10.0)'); + value := 10; + Worksheet.WriteUTF8Text(Row, 0, '=ACOSH(10)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekACOSH, @@ -835,10 +848,10 @@ begin nil)))); Worksheet.WriteNumber(Row, 2, tanh(value)); - // arctanh + // arctanh =ATANH(0.5) inc(Row); value := 0.5; - Worksheet.WriteUTF8Text(Row, 0, '=ATANH(0.5)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ATANH(%.1f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekATANH, @@ -876,20 +889,16 @@ begin nil)))); Worksheet.WriteNumber(Row, 2, ln(value)); - // log to any basis - if AFormat <> sfExcel2 then begin - // This test is not working in Excel 2. - // Not clear if this is correct, need to debug later - inc(Row); - value := 256; - Worksheet.WriteUTF8Text(Row, 0, '=LOG(256,2)'); - Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(value, - RPNNumber(2, - RPNFunc(fekLOG, 2, - nil))))); - Worksheet.WriteNumber(Row, 2, logn(2.0, value)); - end; + // log to any basis =LOG(256, 2) + inc(Row); + value := 256; + Worksheet.WriteUTF8Text(Row, 0, Format('=LOG(256%s 2)', [ls])); + Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(value, + RPNNumber(2, + RPNFunc(fekLOG, 2, + nil))))); + Worksheet.WriteNumber(Row, 2, logn(2.0, value)); // log10(100) inc(Row); @@ -904,7 +913,7 @@ begin // log10(0.01) inc(Row); value := 0.01; - Worksheet.WriteUTF8Text(Row, 0, '=LOG10(0.01)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=LOG10(%.2f)', [value], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(value, RPNFunc(fekLOG10, @@ -918,9 +927,9 @@ begin Worksheet.WriteUTF8Text(Row, 0, 'Rounding'); Worksheet.WriteUsedFormatting(Row, 0, [uffBold]); - // Round positive number to 1 decimal + // Round positive number to 1 decimal =ROUND($F$1, 1) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=ROUND($F$1,1)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ROUND($F$1%s 1)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$F$1', RPNNumber(1, @@ -928,9 +937,9 @@ begin nil))))); Worksheet.WriteNumber(Row, 2, Round(cellF1*10)/10); //RoundTo(cellF1, 1)); - // Round negative number to 1 decimal + // Round negative number to 1 decimal =ROUND(G1, 1) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=ROUND(G1,1)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=ROUND(G1%s 1)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('G1', RPNNumber(1, @@ -1036,7 +1045,7 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, '=ISERR(G1)'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNCellValue('G2', + RPNCellValue('G1', RPNFunc(fekIsERR, nil)))); Worksheet.WriteUTF8Text(Row, 2, 'FALSE'); @@ -1160,41 +1169,41 @@ begin nil)))); Worksheet.WriteUTF8Text(Row, 2, 'TRUE'); - // Cell information + // Cell information =CELL("Address", B80) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CELL("Address",B80)'); + Worksheet.WriteUTF8Text(Row, 0, Format('=CELL("Address"%s B80)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('address', + RPNString('Address', RPNCellRef('B80', // note: CellRef instead of CellValue! RPNFunc(fekCELLINFO, 2, nil))))); - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CELL("Filename",B80)'); + inc(Row); // =CELL("Filename", B80) + Worksheet.WriteUTF8Text(Row, 0, Format('=CELL("Filename"%s B80)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('filename', + RPNString('Filename', RPNCellRef('B80', // note: CellRef instead of CellValue! RPNFunc(fekCELLINFO, 2, nil))))); - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CELL("Row",B80)'); + inc(Row); // =CELL("Row", B80) + Worksheet.WriteUTF8Text(Row, 0, Format('=CELL("Row"%s B80)',[ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString('row', + RPNString('Row', RPNCellRef('B80', // note: CellRef instead of CellValue! RPNFunc(fekCELLINFO, 2, nil))))); - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CELL("format",B80)'); + inc(Row); // =CELL("format", B80) + Worksheet.WriteUTF8Text(Row, 0, Format('=CELL("format"%s B80)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('format', RPNCellRef('B80', // note: CellRef instead of CellValue! RPNFunc(fekCELLINFO, 2, nil))))); - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CELL("color",B80)'); + inc(Row); // =CELL("color", B80) + Worksheet.WriteUTF8Text(Row, 0, Format('=CELL("color"%s B80)',[ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('color', RPNCellRef('B80', // note: CellRef instead of CellValue! @@ -1267,8 +1276,8 @@ begin end; // Date (build date from parts) - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=DATE(2014,1,25)'); + inc(Row); // =DATE(2014, 1, 25) + Worksheet.WriteUTF8Text(Row, 0, Format('=DATE(2014%s 1%s 25)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2014, RPNNumber(1, @@ -1279,7 +1288,7 @@ begin // DateValue (string to date number) inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=DATEVALUE("25.01.2014")'); + Worksheet.WriteUTF8Text(Row, 0, '=DATEVALUE("2014-01-25")'); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('2014-01-25', RPNFunc(fekDATEVALUE, @@ -1287,9 +1296,9 @@ begin Worksheet.WriteNumber(Row, 2, EncodeDate(2014,1,25)); // DateDifference - if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=DATEDIF("2010-01-01",DATE(2014;1;25),"M")'); + if AFormat >= sfExcel5 then begin + inc(Row); //=DATEDIF("2010-01-01", DATE(2014, 1, 25), "M") + Worksheet.WriteUTF8Text(Row, 0, Format('=DATEDIF("2010-01-01"%s DATE(2014%s 1%s 25)%s "M")', [ls, ls, ls, ls])); // Note: Dates must be ordered: Date1 < Date2 !!! Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString('2010-01-01', @@ -1304,8 +1313,8 @@ begin end; // Year - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=YEAR(DATE(2014,1,25))'); + inc(Row); // =YEAR(DATE(2014, 1, 25)) + Worksheet.WriteUTF8Text(Row, 0, Format('=YEAR(DATE(2014%s 1%s 25))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2014, RPNNumber(1, @@ -1316,8 +1325,8 @@ begin Worksheet.WriteNumber(Row, 2, 2014); // Month - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=MONTH(DATE(2014,1,25))'); + inc(Row); // =MONTH(DATE(2014, 1, 25)) + Worksheet.WriteUTF8Text(Row, 0, Format('=MONTH(DATE(2014%s 1%s 25))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2014, RPNNumber(1, @@ -1328,8 +1337,8 @@ begin Worksheet.WriteNumber(Row, 2, 1); // Day - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=DAY(DATE(2014,1,25))'); + inc(Row); // =DAY(DATE(2014, 1, 25)) + Worksheet.WriteUTF8Text(Row, 0, Format('=DAY(DATE(2014%s 1%s 25))', [ls,ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2014, RPNNumber(1, @@ -1340,8 +1349,8 @@ begin Worksheet.WriteNumber(Row, 2, 25); // Weekday - 2 params can be used, but not in BIFF2 - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=WEEKDAY(DATE(2014,1,25))'); + inc(Row); // =WEEKDAY(DATE(2014,1,25)) + Worksheet.WriteUTF8Text(Row, 0, Format('=WEEKDAY(DATE(2014%s 1%s 25))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2014, RPNNumber(1, @@ -1360,8 +1369,8 @@ begin Worksheet.WriteNumber(Row, 2, DayOfWeek(EncodeDate(2014,1,25))); // Time (build time from parts) - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=TIME(21, 10, 5)'); + inc(Row); // =TIME(21,10,5) + Worksheet.WriteUTF8Text(Row, 0, Format('=TIME(21%s 10%s 5)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(21, RPNNumber(10, @@ -1380,8 +1389,8 @@ begin Worksheet.WriteNumber(Row, 2, EncodeTime(21, 10, 5, 0)); // Hour - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=HOUR(TIME(21, 10, 5)))'); + inc(Row); // =HOUR(TIME(21, 10, 5)) + Worksheet.WriteUTF8Text(Row, 0, Format('=HOUR(TIME(21%s 10%s 5))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(21, RPNNumber(10, @@ -1392,8 +1401,8 @@ begin Worksheet.WriteNumber(Row, 2, 21); // Minute - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=MINUTE(TIME(21, 10, 5)))'); + inc(Row); // =MINUTE(TIME(21, 10, 5)) + Worksheet.WriteUTF8Text(Row, 0, Format('=MINUTE(TIME(21%s 10%s 5))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(21, RPNNumber(10, @@ -1404,8 +1413,8 @@ begin Worksheet.WriteNumber(Row, 2, 10); // Second - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=SECOND(TIME(21, 10, 5)))'); + inc(Row); // =SECOND(TIME(21, 10, 5)) + Worksheet.WriteUTF8Text(Row, 0, Format('=SECOND(TIME(21%s 10%s 5))', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(21, RPNNumber(10, @@ -1423,8 +1432,8 @@ begin Worksheet.WriteUsedFormatting(Row, 0, [uffBold]); // Count - non-empty cells - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=COUNT($B$1,$C$1,$D$1:F1)'); + inc(Row); // =COUNT($B$1, $C$1, $D$1:F1) + Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT($B$1%s $C$1%s $D$1:F1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1434,8 +1443,8 @@ begin Worksheet.WriteNumber(Row, 2, 5); // 5 cells in total, all not empty // Count - with empty cells & alpha-numeric cells - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=COUNT($B$1,$C$1,$D$1:$F$2,"ABC")'); + inc(Row); // =COUNT($B$1, $C$1, $D$1:$F$2, "ABC") + Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT($B$1%s $C$1%s $D$1:$F$2%s "ABC")', [ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1446,8 +1455,8 @@ begin Worksheet.WriteNumber(Row, 2, 5); // 5 non-empty, 3 empty // CountA - empty cells and constants - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=COUNTA($B$1,$C$1,$D$1:$F$2,"ABC","DEF")'); + inc(Row); //=COUNTA($B$1, $C$1, D$1:$F$2, "ABC", "DEF") + Worksheet.WriteUTF8Text(Row, 0, Format('=COUNTA($B$1%s $C$1%s $D$1:$F$2%s "ABC"%s "DEF")', [ls, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1460,8 +1469,8 @@ begin if AFormat <> sfExcel2 then begin // CountIF - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=COUNTIF(A1:G1,"<=1")'); + inc(Row); // =COUNTIF(A1:G1, "<=1") + Worksheet.WriteUTF8Text(Row, 0, Format('=COUNTIF(A1:G1%s "<=1")', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('A1:G1', RPNString('<=1', @@ -1484,20 +1493,20 @@ begin end; // Sum - non-empty cells - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=SUM($B$1,$C$1,D$1:F$1)'); + inc(Row); // =SUM($B$1, $C$1, D$1:F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=SUM($B$1%s $C$1%s D$1:F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', - RPNCellRange('$D$1:F$1', + RPNCellRange('D$1:F$1', RPNFunc(fekSUM, 3, nil)))))); Worksheet.WriteNumber(Row, 2, cellB1+cellC1+cellD1+cellE1+cellF1); if AFormat <> sfExcel2 then begin // SumIF - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=SUMIF(A1:G1,"<=1")'); + inc(Row); // =SUMIF(A1:G1, "<=1") + Worksheet.WriteUTF8Text(Row, 0, Format('=SUMIF(A1:G1%s "<=1")', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellRange('A1:G1', RPNString('<=1', @@ -1510,8 +1519,8 @@ begin ); // Sum of squares - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=SUMSQ($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =SUMSQ($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=SUMSQ($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1522,8 +1531,8 @@ begin end; // Product - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=PRODUCT($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =PRODUCT($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=PRODUCT($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1533,8 +1542,8 @@ begin Worksheet.WriteNumber(Row, 2, cellB1*cellC1*cellD1*cellE1*cellF1); // Average - non-empty cells - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=AVERAGE($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =AVERAGE($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=AVERAGE($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1544,8 +1553,8 @@ begin Worksheet.WriteNumber(Row, 2, (cellB1+cellC1+cellD1+cellE1+cellF1)/5); // StdDev - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=STDEV($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =STDEV($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=STDEV($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1555,8 +1564,8 @@ begin Worksheet.WriteNumber(Row, 2, stddev([cellB1,cellC1,cellD1,cellE1,cellF1])); // Population StdDev - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=STDEVP($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =STDEVP($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=STDEVP($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1567,8 +1576,8 @@ begin // Average deviation if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=AVEDEV($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =AVEDEV($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=AVEDEV($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1581,8 +1590,8 @@ begin end; // Variance - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=VAR($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =VAR($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=VAR($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1592,8 +1601,8 @@ begin Worksheet.WriteNumber(Row, 2, variance([cellB1,cellC1,cellD1,cellE1,cellF1])); // Population variance - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=VARP($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =VARP($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=VARP($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1603,8 +1612,8 @@ begin Worksheet.WriteNumber(Row, 2, popnvariance([cellB1,cellC1,cellD1,cellE1,cellF1])); // Max - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=MAX($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =MAX($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=MAX($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1614,8 +1623,8 @@ begin Worksheet.WriteNumber(Row, 2, MaxValue([cellB1,cellC1,cellD1,cellE1,cellF1])); // Min - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=MIN($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =MIN($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=MIN($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1626,8 +1635,8 @@ begin // Median if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=MEDIAN($B$1,$C$1,$D$1:$F$1)'); + inc(Row); // =MEDIAN($B$1, $C$1, $D$1:$F$1) + Worksheet.WriteUTF8Text(Row, 0, Format('=MEDIAN($B$1%s $C$1%s $D$1:$F$1)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNCellValue('$B$1', RPNCellValue('$C$1', @@ -1639,11 +1648,12 @@ begin // Beta distribution if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=BETADIST(3,7.5,9,1,4)'); + inc(Row); // =BETADIST(3, 7, 9, 1, 4) + value := 7.5; + Worksheet.WriteUTF8Text(Row, 0, Format('=BETADIST(3%s %.1f%s 9%s 1%s 4)', [ls, value, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(3, - RPNNumber(7.5, + RPNNumber(value, RPNNumber(9, RPNNumber(1, RPNNumber(4, @@ -1655,36 +1665,40 @@ begin // Inverse beta function if AFormat <> sfExcel2 then begin inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=BETAINV(0.3,7.5,9,1,4)'); + value := 0.5; // =BETAINV(0.5, 7, 9, 1, 4) + Worksheet.WriteUTF8Text(Row, 0, Format('=BETAINV(%.1f%s 7%s 9%s 1%s 4)', [value, ls, ls, ls, ls], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.3, - RPNNumber(7.5, + RPNNumber(value, + RPNNumber(7, RPNNumber(9, RPNNumber(1, RPNNumber(4, RPNFunc(fekBETAINV, 5, nil)))))))); - Worksheet.WriteNumber(Row, 2, 2.164759636); // result according to http://www.techonthenet.com/excel/formulas/betainv.php + Worksheet.WriteNumber(Row, 2, 2.304498434); // Result calculated by Excel end; + // Binomial distribution if AFormat <> sfExcel2 then begin inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=BINOMDIST(3,8,0.35,TRUE)'); + value := 0.5; // =BINOMDIST(3, 8, 0.5, TRUE) + Worksheet.WriteUTF8Text(Row, 0, Format('=BINOMDIST(3%s 8%s %.1f%s TRUE)', [ls, ls, value, ls], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(3, RPNNumber(8, - RPNNumber(0.35, + RPNNumber(0.5, RPNBool(true, RPNFunc(fekBINOMDIST, nil))))))); - Worksheet.WriteNumber(Row, 2, 0.706399436); // result according to http://www.techonthenet.com/excel/formulas/binomdist.php + Worksheet.WriteNumber(Row, 2, 0.36328125); // Result calculated by Excel end; + // Chi2 distribution if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CHIDIST(3,9)'); + inc(Row); // =CHIDIST(3,9) + Worksheet.WriteUTF8Text(Row, 0, Format('=CHIDIST(3%s 9)', [ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(3, RPNNumber(9, @@ -1696,19 +1710,20 @@ begin // Inverse of Chi2 distribution if AFormat <> sfExcel2 then begin inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=CHIINV(0.3,7)'); + value := 0.5; // '=CHIINV(0.5, 7) + Worksheet.WriteUTF8Text(Row, 0, Format('=CHIINV(%.1f%s 7)', [value, ls], fs)); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(0.3, + RPNNumber(0.5, RPNNumber(7, RPNFunc(fekCHIINV, nil))))); - Worksheet.WriteNumber(Row, 2, 8.38343064); // result according to http://www.techonthenet.com/excel/formulas/chiinv.php + Worksheet.WriteNumber(Row, 2, 6.345811373); // Result calculated by Excel end; // Permutations if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=PERMUT(21,5)'); + inc(Row); // =PERMUT(21, 5) + Worksheet.WriteUTF8Text(Row, 0, Format('=PERMUT(21%s 5)',[ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(21, RPNNumber(5, @@ -1719,8 +1734,8 @@ begin // Poisson distribution if AFormat <> sfExcel2 then begin - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=POISSON(1400,1500,TRUE)'); + inc(Row); // =POISSON(1400, 1500, TRUE) + Worksheet.WriteUTF8Text(Row, 0, Format('=POISSON(1400%s 1500%s TRUE)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(1400, RPNNumber(1500, @@ -1741,10 +1756,10 @@ begin // that earns 3.5% annually. You are going to deposit $250 at the beginning of // the month, each month, for 2 years. // according to: www.techonthenet.com/excel/formulas/fv.php - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=FV(3.5%/12,2*12,-250,-5000,1)'); + inc(Row); // =FV(3%/12, 2*12, -250, -5000, 1) + Worksheet.WriteUTF8Text(Row, 0, Format('=FV(3%%/12%s 2*12%s -250%s -5000%s 1)', [ls, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(3.5, + RPNNumber(3, RPNFunc(fekPERCENT, RPNNumber(12, RPNFunc(fekDIV, @@ -1759,13 +1774,13 @@ begin Worksheet.WriteUTF8Text(Row, 2, '(not available in FPC)'); // Present value of an investment that pays $250 at the end of every month for - // 2 years. The money paid out will earn 3.5% annually. + // 2 years. The money paid out will earn 3% annually. // according to: www.techonthenet.com/excel/formulas/pv.php // Note the missing argument! - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=PV(3.5%/12,2*12,250,,0)'); + inc(Row); // =PV(3%/12, 2*12, 250, , 0) + Worksheet.WriteUTF8Text(Row, 0, Format('=PV(3%%/12%s 2*12%s 250%s %s 0)', [ls, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(3.5, + RPNNumber(3, RPNFunc(fekPercent, RPNNumber(12, RPNFunc(fekDIV, @@ -1782,8 +1797,8 @@ begin // Interest rate on a $5,000 loan where monthly payments of $250 are made for // 2 years. All payments are made at the end of the period. // Adapted from //www.techonthenet.com/excel/formulas/rate.php - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=RATE(2*12,-250,5000)'); + inc(Row); // =RATE(2*12, -250, 5000) + Worksheet.WriteUTF8Text(Row, 0, Format('=RATE(2*12%s -250%s 5000)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNNumber(2, RPNNumber(12, @@ -1801,10 +1816,10 @@ begin // Number of monthly payments of $150 for a $5,000 investment that earns // 3.5% annually. Payments are due at the end of the period. // Adapted from //www.techonthenet.com/excel/formulas/nper.php - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=NPER(3.5%/12,-150,5000)'); + inc(Row); // =NPER(3%/12, -150, 5000) + Worksheet.WriteUTF8Text(Row, 0, Format('=NPER(3%%/12%s -150%s 5000)', [ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(3.5, + RPNNumber(3, RPNFunc(fekPERCENT, RPNNumber(12, RPNFunc(fekDIV, @@ -1818,10 +1833,10 @@ begin // paid off in 2 years (ie: 2 x 12). All payments are made at the beginning // of the period. // Adapted from //www.techonthenet.com/excel/formulas/pmt.php - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=PMT(3.5%/12,2*12,5000,0,1)'); + inc(Row); // =PMT(3%/12, 2*12, 5000, 0, 1) + Worksheet.WriteUTF8Text(Row, 0, Format('=PMT(3%%/12%s 2*12%s 5000%s 0%s 1)', [ls, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(3.5, + RPNNumber(3, RPNFunc(fekPERCENT, RPNNumber(12, RPNFunc(fekDIV, @@ -1862,8 +1877,8 @@ begin Worksheet.WriteNumber(Row, 2, ord(SHelloWorld[1])); // Left part of string - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, Format('=LEFT("%s",3)', [SHelloWorld])); + inc(Row); // =LEFT("%s", 3) + Worksheet.WriteUTF8Text(Row, 0, Format('=LEFT("%s"%s 3)', [SHelloWorld, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString(SHelloWorld, RPNNumber(3, @@ -1872,8 +1887,8 @@ begin Worksheet.WriteUTF8Text(Row, 2, Copy(SHelloWorld, 1, 3)); // Mid part of string - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, Format('=MID("%s",4,5)', [SHelloWorld])); + inc(Row); // =MID("%s", 4, 5) + Worksheet.WriteUTF8Text(Row, 0, Format('=MID("%s"%s 4%s 5)', [SHelloWorld, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString(SHelloWorld, RPNNumber(4, @@ -1883,8 +1898,8 @@ begin Worksheet.WriteUTF8Text(Row, 2, Copy(SHelloWorld, 4, 5)); // Right part of string - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, Format('=RIGHT("%s",3)', [SHelloWorld])); + inc(Row); // =RIGHT("%s", 3) + Worksheet.WriteUTF8Text(Row, 0, Format('=RIGHT("%s"%s 3)', [SHelloWorld, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString(SHelloWorld, RPNNumber(3, @@ -1923,14 +1938,14 @@ begin inc(Row); Worksheet.WriteUTF8Text(Row, 0, Format('=PROPER("%s")', [uppercase(SHelloWorld)])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNString(SHelloWorld, + RPNString(Uppercase(SHelloWorld), RPNFunc(fekPROPER, nil)))); Worksheet.WriteUTF8Text(Row, 2, 'Hello World!'); // replace - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, Format('=REPLACE("%s",7,5,"Friend")', [SHelloWorld])); + inc(Row); // =REPLACE("%s", 7, 5, "Friend") + Worksheet.WriteUTF8Text(Row, 0, Format('=REPLACE("%s"%s 7%s 5%s "Friend")', [SHelloWorld, ls, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString(SHelloWorld, RPNNumber(7, @@ -1943,8 +1958,8 @@ begin // substitute // Note: the function can have an optional parameter. Therefore, you have // to specify the actual parameter count. - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, Format('=SUBSTITUTE("%s","l",".")', [SHelloWorld])); + inc(Row); // =SUBSTITUTE("%s", "l", ".") + Worksheet.WriteUTF8Text(Row, 0, Format('=SUBSTITUTE("%s"%s "l"%s ".")', [SHelloWorld, ls, ls])); Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( RPNString(SHelloWorld, RPNString('l', @@ -1957,53 +1972,44 @@ begin Worksheet.WriteUTF8Text(Row, 0, 'Errors'); Worksheet.WriteUsedFormatting(Row, 0, [uffBold]); - // Division by 0 + if IncludeErrors then begin // These tests partly produce an error messsage when the file is read by Excel. // In order to avoid confusion they are deactivated by default. // Remove the comment below to see these tests. - (* - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=1/0'); - Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNNumber(0, - RPNFunc(fekDiv, - nil))))); - Worksheet.WriteUTF8Text(Row, 2, 'Error #DIV/0!'); + inc(Row); + Worksheet.WriteUTF8Text(Row, 0, '=1/0'); + Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(0, + RPNFunc(fekDiv, + nil))))); + Worksheet.WriteUTF8Text(Row, 2, 'Error #DIV/0!'); - // Not enough operands for operation - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=1/2 ("2" forgotten from formula)" '); - Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - // here we'd normally put "RPNFormula(2," - but we "forget" it... - RPNFunc(fekDiv, - nil)))); - Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A"'); + // Not enough operands for operation + inc(Row); + Worksheet.WriteUTF8Text(Row, 0, '=#N/A'); + Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + // here we'd normally put "RPNNumbe(2," - but we "forget" it... + RPNFunc(fekDiv, + nil)))); + Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A"'); + Worksheet.WriteUTF8Text(Row, 3, 'Should be "=1/2", but the "2" is forgotten from the formula...'); - // Too many operands given - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=1/2 (too many operands)'); - Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNNumber(2, - RPNNumber(3, // This line is too much - RPNFunc(fekDiv, - nil)))))); - Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A!'); - - // Parameter count not specified - inc(Row); - Worksheet.WriteUTF8Text(Row, 0, '=SUM(1, 2) (parameter count not specified'); - Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( - RPNNumber(1, - RPNNumber(2, - RPNFunc(fekSum, // We "forget" to specify the number of arguments - nil))))); - Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A'); - *) + // Too many operands given + inc(Row); + Worksheet.WriteUTF8Text(Row, 0, '=#N/A'); + Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( + RPNNumber(1, + RPNNumber(2, + RPNNumber(3, // This line is too much + RPNFunc(fekDiv, + nil)))))); + 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/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index cace0eeb2..503944a22 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -521,8 +521,76 @@ begin end; procedure TsSpreadBIFF2Reader.ReadFormula(AStream: TStream); +var + ARow, ACol: Cardinal; + XF: Word; + ok: Boolean; + formulaResult: Double; +// rpnFormula: TsRPNFormula; + Data: array [0..7] of byte; + dt: TDateTime; + nf: TsNumberFormat; + nd: Byte; + ncs: String; + nfs: String; + err: TsErrorValue; + cell: PCell; begin + { BIFF Record row/column/style } + ReadRowColXF(AStream, ARow, ACol, XF); + { Result of the formula result in IEEE 754 floating-point value } + AStream.ReadBuffer(Data, Sizeof(Data)); + + { Recalculation byte - currently not used } + AStream.ReadByte; + + // Now determine the type of the formula result + if (Data[6] = $FF) and (Data[7] = $FF) then + case Data[0] of + 0: // String -> Value is found in next record (STRING) + FIncompleteCell := FWorksheet.GetCell(ARow, ACol); + 1: // Boolean value + FWorksheet.WriteBoolValue(ARow, ACol, Data[2] = 1); + 2: begin // Error value + case Data[2] of + ERR_INTERSECTION_EMPTY : err := errEmptyIntersection; + ERR_DIVIDE_BY_ZERO : err := errDivideByZero; + ERR_WRONG_TYPE_OF_OPERAND: err := errWrongType; + ERR_ILLEGAL_REFERENCE : err := errIllegalRef; + ERR_WRONG_NAME : err := errWrongName; + ERR_OVERFLOW : err := errOverflow; + ERR_ARG_ERROR : err := errArgError; + end; + FWorksheet.WriteErrorValue(ARow, ACol, err); + end; + 3: // Empty cell + FWorksheet.WriteBlank(ARow, ACol); + end + else begin + if SizeOf(Double) <> 8 then + raise Exception.Create('Double is not 8 bytes'); + + // Result is a number or a date/time + Move(Data[0], formulaResult, SizeOf(Data)); + + {Find out what cell type, set content type and value} + ExtractNumberFormat(XF, nf, nd, ncs, nfs); + if IsDateTime(formulaResult, nf, dt) then + FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs) + else + FWorksheet.WriteNumber(ARow, ACol, formulaResult, nf, nd, ncs); + end; + + { Formula token array } + if FWorkbook.ReadFormulas then begin + cell := FWorksheet.FindCell(ARow, ACol); + ok := ReadRPNTokenArray(AStream, cell^.RPNFormulaValue); + if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); + end; + + { Apply formatting to cell } + ApplyCellFormatting(ARow, ACol, XF); end; procedure TsSpreadBIFF2Reader.ReadLabel(AStream: TStream); diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 87eab0a64..ef92088a0 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -325,7 +325,7 @@ const ERR_ILLEGAL_REFERENCE = $17; // #REF! ERR_WRONG_NAME = $1D; // #NAME? ERR_OVERFLOW = $24; // #NUM! - ERR_NOT_AVAILABLE = $2A; // #N/A + ERR_ARG_ERROR = $2A; // #N/A (not enough, or too many, arguments) type TDateMode=(dm1900,dm1904); //DATEMODE values, 5.28 @@ -516,8 +516,8 @@ const // Basic operations INT_EXCEL_TOKEN_TADD, {fekAdd, +} INT_EXCEL_TOKEN_TSUB, {fekSub, -} - INT_EXCEL_TOKEN_TDIV, {fekDiv, /} INT_EXCEL_TOKEN_TMUL, {fekMul, *} + INT_EXCEL_TOKEN_TDIV, {fekDiv, /} INT_EXCEL_TOKEN_TPERCENT, {fekPercent, %} INT_EXCEL_TOKEN_TPOWER, {fekPower, ^} INT_EXCEL_TOKEN_TUMINUS, {fekUMinus, -} @@ -1051,10 +1051,8 @@ var ARow, ACol: Cardinal; XF: WORD; ResultFormula: Double; - RPNFormula: TsRPNFormula; - Data: array [0..7] of BYTE; + Data: array [0..7] of byte; Flags: WORD; -// FormulaSize: BYTE; i: Integer; dt: TDateTime; nf: TsNumberFormat; @@ -1062,7 +1060,7 @@ var ncs: String; nfs: String; resultStr: String; - err: TErrorValue; + err: TsErrorValue; ok: Boolean; cell: PCell; @@ -1072,7 +1070,7 @@ begin { Index to XF Record } ReadRowColXF(AStream, ARow, ACol, XF); - { Result of the formula in IEE 754 floating-point value } + { Result of the formula result in IEEE 754 floating-point value } AStream.ReadBuffer(Data, Sizeof(Data)); { Options flags } @@ -1081,20 +1079,6 @@ begin { Not used } AStream.ReadDWord; - { Formula size } - (* - FormulaSize := WordLEtoN(AStream.ReadWord); - - { Formula data, output as debug info } -{ Write('Formula Element: '); - for i := 1 to FormulaSize do - Write(IntToHex(AStream.ReadByte, 2) + ' '); - WriteLn('');} - - //RPN data not used by now - AStream.Position := AStream.Position + FormulaSize; - *) - // Now determine the type of the formula result if (Data[6] = $FF) and (Data[7] = $FF) then case Data[0] of @@ -1112,7 +1096,7 @@ begin ERR_ILLEGAL_REFERENCE : err := errIllegalRef; ERR_WRONG_NAME : err := errWrongName; ERR_OVERFLOW : err := errOverflow; - ERR_NOT_AVAILABLE : err := errArgNotAvail; + ERR_ARG_ERROR : err := errArgError; end; FWorksheet.WriteErrorValue(ARow, ACol, err); end;