diff --git a/components/fpspreadsheet/fpsfunc.pas b/components/fpspreadsheet/fpsfunc.pas
index 241add651..bd4692828 100644
--- a/components/fpspreadsheet/fpsfunc.pas
+++ b/components/fpspreadsheet/fpsfunc.pas
@@ -86,6 +86,21 @@ function fpsSINH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsSQRT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTAN (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTANH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+{ Date/time functions }
+function fpsDATE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsDATEDIF (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsDATEVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsDAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsHOUR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsMINUTE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsMONTH (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsNOW (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsSECOND (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsTIME (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsTIMEVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsTODAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsWEEKDAY (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsYEAR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ Statistical functions }
function fpsAVEDEV (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsAVERAGE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@@ -114,13 +129,23 @@ function fpsLOWER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsMID (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsREPLACE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsRIGHT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsSUBSTITUTE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsTRIM (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
function fpsUPPER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+{ info functions }
+function fpsISERR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISERROR (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISLOGICAL (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISNA (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISNONTEXT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISNUMBER (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsISTEXT (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+function fpsVALUE (Args: TsArgumentStack; NumArgs: Integer): TsArgument;
implementation
uses
- Math, lazutf8, StrUtils, fpsUtils;
+ Math, lazutf8, StrUtils, DateUtils, fpsUtils;
type
TBoolArray = array of boolean;
@@ -222,6 +247,7 @@ begin
AValue := Arg.BoolValue;
Result := errOK;
end;
+ atError: Result := Arg.ErrorValue;
else Result := errWrongType;
end;
end;
@@ -233,6 +259,7 @@ begin
atNumber : ANumber := Arg.NumberValue;
atString : if not TryStrToFloat(arg.StringValue, ANumber) then Result := errWrongType;
atBool : if Arg.BoolValue then ANumber := 1.0 else ANumber := 0.0;
+ atError : Result := Arg.ErrorValue;
end;
end;
@@ -243,6 +270,7 @@ begin
AString := Arg.StringValue;
Result := errOK;
end;
+ atError : Result := Arg.ErrorValue;
else Result := errWrongType;
end;
end;
@@ -332,6 +360,31 @@ begin
end;
end;
+function PopDateValue(Args: TsArgumentStack; out ADate: TDate;
+ out AErrArg: TsArgument): Boolean;
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ case arg.ArgumentType of
+ atError, atBool, atEmpty:
+ begin
+ Result := false;
+ AErrArg := CreateError(errWrongType);
+ end;
+ atNumber:
+ begin
+ Result := true;
+ ADate := arg.NumberValue;
+ end;
+ atString:
+ begin
+ Result := TryStrToDate(arg.StringValue, ADate);
+ if not Result then AErrArg := CreateError(errWrongType);
+ end
+ end;
+end;
+
{@@
Pops floating point values from the argument stack. Is called when
calculating rpn formulas.
@@ -435,6 +488,32 @@ begin
end;
+function PopTimeValue(Args: TsArgumentStack; out ATime: TTime;
+ out AErrArg: TsArgument): Boolean;
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ case arg.ArgumentType of
+ atError, atBool, atEmpty:
+ begin
+ Result := false;
+ AErrArg := CreateError(errWrongType);
+ end;
+ atNumber:
+ begin
+ Result := true;
+ ATime := frac(arg.NumberValue);
+ end;
+ atString:
+ begin
+ Result := TryStrToTime(arg.StringValue, ATime);
+ if not Result then AErrArg := CreateError(errWrongType);
+ end
+ end;
+end;
+
+
{ Operations }
function fpsAdd(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@@ -759,13 +838,13 @@ begin
base := arg_base.NumberValue;
end;
end;
+ arg_number := Args.Pop;
if base < 0 then begin
Result := CreateError(errOverflow);
exit;
end;
- arg_number := Args.Pop;
if arg_number.ArgumentType <> atNumber then begin
Result := CreateError(errWrongType);
exit;
@@ -872,6 +951,159 @@ begin
end;
+{ Date/time functions }
+
+function fpsDATE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// DATE( year, month, day )
+var
+ data: TFloatArray;
+ d: TDate;
+begin
+ if PopFloatValues(Args, 3, data, Result) then begin
+ d := EncodeDate(round(data[0]), round(data[1]), round(data[2]));
+ Result := CreateNumber(d);
+ end;
+end;
+
+function fpsDATEDIF(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+{ 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
+ data: TStrArray;
+ start_date, end_date: TDate;
+begin
+ if not PopStringValues(Args, 1, data, Result) then exit;
+ if not PopDateValue(Args, end_date, Result) then exit;
+ if not PopDateValue(Args, start_date, Result) then exit;
+
+ if end_date > start_date then
+ Result := CreateError(errOverflow)
+ else if data[0] = 'Y' then
+ Result := CreateNumber(YearsBetween(end_date, start_date))
+ else if data[0] = 'M' then
+ Result := CreateNumber(MonthsBetween(end_date, start_date))
+ else if data[0] = 'D' then
+ Result := CreateNumber(DaysBetween(end_date, start_date))
+ else
+ Result := CreateError(errFormulaNotSupported);
+end;
+
+function fpsDATEVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// DATEVALUE( date ) -- date can be a string or a date/time
+var
+ d: TDate;
+begin
+ if PopDateValue(Args, d, Result) then
+ Result := CreateNumber(d);
+end;
+
+function fpsDAY(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ d: TDate;
+begin
+ if PopDateValue(Args, d, Result) then
+ Result := CreateNumber(DayOf(d));
+end;
+
+function fpsHOUR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ t: TTime;
+begin
+ if PopTimeValue(Args, t, Result) then
+ Result := CreateNumber(HourOf(t));
+end;
+
+function fpsMINUTE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ t: TTime;
+begin
+ if PopTimeValue(Args, t, Result) then
+ Result := CreateNumber(MinuteOf(t));
+end;
+
+function fpsMONTH(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ d: TDate;
+begin
+ if PopDateValue(Args, d, Result) then
+ Result := CreateNumber(MonthOf(d));
+end;
+
+function fpsNOW(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// NOW()
+begin
+ Result := CreateNumber(now);
+end;
+
+function fpsSECOND(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ t: TTime;
+begin
+ if PopTimeValue(Args, t, Result) then
+ Result := CreateNumber(SecondOf(t));
+end;
+
+function fpsTIME(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// TIME( hour, minute, second )
+var
+ data: TFloatArray;
+ t: TTime;
+begin
+ if PopFloatValues(Args, 3, data, Result) then begin
+ t := EncodeTime(round(data[0]), round(data[1]), round(data[2]), 0);
+ Result := CreateNumber(t);
+ end;
+end;
+
+function fpsTIMEVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// TIMEVALUE( time_value )
+var
+ t: TTime;
+begin
+ if PopTimeValue(Args, t, Result) then
+ Result := CreateNumber(t);
+end;
+
+function fpsToday(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// TODAY()
+begin
+ Result := CreateNumber(Date());
+end;
+
+function fpsWEEKDAY(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+{ WEEKDAY( serial_number, [return_value]
+ return_value = 1 - Returns a number from 1 (Sunday) to 7 (Saturday).
+ This is the default if parameter is omitted.
+ = 2 - Returns a number from 1 (Monday) to 7 (Sunday).
+ = 3 - Returns a number from 0 (Monday) to 6 (Sunday). }
+var
+ d: TDate;
+ data: TFloatArray;
+ n: Integer;
+begin
+ n := 1;
+ if NumArgs = 2 then
+ if PopFloatValues(Args, 1, data, Result) then n := round(data[0])
+ else exit;
+ if PopDateValue(Args, d, Result) then
+ Result := CreateNumber(DayOfWeek(d));
+end;
+
+function fpsYEAR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+var
+ d: TDate;
+begin
+ if PopDateValue(Args, d, Result) then
+ Result := CreateNumber(YearOf(d));
+end;
+
+
{ Statistical functions }
function fpsAVEDEV(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
@@ -901,15 +1133,19 @@ begin
end;
function fpsCOUNT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
-{ Count non-missing arguments
+{ counts the number of cells that contain numbers as well as the number of
+ arguments that contain numbers.
COUNT( argument1, [argument2, ... argument_n] )
}
var
n, i: Integer;
+ arg: TsArgument;
begin
n := 0;
- for i:=1 to NumArgs do
- if not Args.Pop.IsMissing then inc(n);
+ for i:=1 to NumArgs do begin
+ arg := Args.Pop;
+ if (not arg.IsMissing) and (arg.ArgumentType = atNumber) then inc(n);
+ end;
Result := CreateNumber(n);
end;
@@ -1221,6 +1457,34 @@ begin
Result := CreateString(UTF8RightStr(s, count));
end;
+function fpsSUBSTITUTE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// SUBSTITUTE( text, old_text, new_text, [nth_appearance] )
+var
+ n: Integer;
+ arg: TsArgument;
+ data: TStrArray;
+ s, s_old, s_new: String;
+begin
+ Result := CreateError(errWrongType);
+ n := -1;
+ if (NumArgs = 4) then begin
+ arg := Args.Pop;
+ if not arg.IsMissing and (arg.ArgumentType <> atNumber) then
+ exit;
+ n := round(arg.NumberValue);
+ end;
+
+ if PopStringValues(Args, 3, data, Result) then begin
+ s := data[0];
+ s_old := data[1];
+ s_new := data[2];
+ if n = -1 then
+ Result := CreateString(UTF8StringReplace(s, s_old, s_new, [rfReplaceAll]))
+ else
+ Result := CreateError(errFormulaNotSupported);
+ end;
+end;
+
function fpsTRIM(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
// TRIM( text )
var
@@ -1239,4 +1503,92 @@ begin
Result := CreateString(UTF8UpperCase(data[0]));
end;
+
+{ Info functions }
+
+function fpsISERR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISERR( value )
+// If value is an error value (except #N/A), this function will return TRUE.
+// Otherwise, it will return FALSE.
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool((arg.ArgumentType = atError) and (arg.ErrorValue <> errArgError));
+end;
+
+function fpsISERROR(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// 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
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool((arg.ArgumentType = atError));
+end;
+
+function fpsISLOGICAL(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISLOGICAL( value )
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool(arg.ArgumentType = atBool);
+end;
+
+function fpsISNA(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISNA( value )
+// If value is a #N/A error value , this function will return TRUE.
+// Otherwise, it will return FALSE.
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool((arg.ArgumentType = atError) and (arg.ErrorValue = errArgError));
+end;
+
+function fpsISNONTEXT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISNONTEXT( value )
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool(arg.ArgumentType <> atString);
+end;
+
+function fpsISNUMBER(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISNUMBER( value )
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool(arg.ArgumentType = atNumber);
+end;
+
+function fpsISTEXT(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// ISTEXT( value )
+var
+ arg: TsArgument;
+begin
+ arg := Args.Pop;
+ Result := CreateBool(arg.ArgumentType = atString);
+end;
+
+function fpsVALUE(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
+// VALUE( text )
+// text is the text value to convert to a number. If text is not a number, the
+// VALUE function will return #VALUE!.
+var
+ data: TStrArray;
+ x: Double;
+begin
+ if PopStringValues(Args, 1, data, Result) then
+ if TryStrToFloat(data[0], x) then
+ Result := CreateNumber(x)
+ else
+ Result := CreateError(errWrongType);
+end;
+
+
end.
diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas
index dd6b64284..8a2cf7a99 100755
--- a/components/fpspreadsheet/fpspreadsheet.pas
+++ b/components/fpspreadsheet/fpspreadsheet.pas
@@ -1094,8 +1094,8 @@ type
end;
const
- FEProps: array[TFEKind] of TFEProp = (
- { Operands }
+ 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
(Symbol:''; MinParams:Byte(-1); MaxParams:Byte(-1); Func:nil), // fekCellRange
@@ -1149,37 +1149,37 @@ const
(Symbol:'TAN'; MinParams:1; MaxParams:1; Func:fpsTAN), // fekTAN
(Symbol:'TANH'; MinParams:1; MaxParams:1; Func:fpsTANH), // fekTANH,
{ date/time }
- (Symbol:'DATE'; MinParams:3; MaxParams:3; Func:nil), // fekDATE
- (Symbol:'DATEDIF'; MinParams:3; MaxParams:3; Func:nil), // fekDATEDIF
- (Symbol:'DATEVALUE'; MinParams:1; MaxParams:1; Func:nil), // fekDATEVALUE
- (Symbol:'DAY'; MinParams:1; MaxParams:1; Func:nil), // fekDAY
- (Symbol:'HOUR'; MinParams:1; MaxParams:1; Func:nil), // fekHOUR
- (Symbol:'MINUTE'; MinParams:1; MaxParams:1; Func:nil), // fekMINUTE
- (Symbol:'MONTH'; MinParams:1; MaxParams:1; Func:nil), // fekMONTH
- (Symbol:'NOW'; MinParams:0; MaxParams:0; Func:nil), // fekNOW
- (Symbol:'SECOND'; MinParams:1; MaxParams:1; Func:nil), // fekSECOND
- (Symbol:'TIME'; MinParams:3; MaxParams:3; Func:nil), // fekTIME
- (Symbol:'TIMEVALUE'; MinParams:1; MaxParams:1; Func:nil), // fekTIMEVALUE
- (Symbol:'TODAY'; MinParams:0; MaxParams:0; Func:nil), // fekTODAY
- (Symbol:'WEEKDAY'; MinParams:1; MaxParams:2; Func:nil), // fekWEEKDAY
- (Symbol:'YEAR'; MinParams:1; MaxParams:1; Func:nil), // fekYEAR
+ (Symbol:'DATE'; MinParams:3; MaxParams:3; Func:fpsDATE), // fekDATE
+ (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
+ (Symbol:'MINUTE'; MinParams:1; MaxParams:1; Func:fpsMINUTE), // fekMINUTE
+ (Symbol:'MONTH'; MinParams:1; MaxParams:1; Func:fpsMONTH), // fekMONTH
+ (Symbol:'NOW'; MinParams:0; MaxParams:0; Func:fpsNOW), // fekNOW
+ (Symbol:'SECOND'; MinParams:1; MaxParams:1; Func:fpsSECOND), // fekSECOND
+ (Symbol:'TIME'; MinParams:3; MaxParams:3; Func:fpsTIME), // fekTIME
+ (Symbol:'TIMEVALUE'; MinParams:1; MaxParams:1; Func:fpsTIMEVALUE), // fekTIMEVALUE
+ (Symbol:'TODAY'; MinParams:0; MaxParams:0; Func:fpsTODAY), // fekTODAY
+ (Symbol:'WEEKDAY'; MinParams:1; MaxParams:2; Func:fpsWEEKDAY), // fekWEEKDAY
+ (Symbol:'YEAR'; MinParams:1; MaxParams:1; Func:fpsYEAR), // fekYEAR
{ statistical }
(Symbol:'AVEDEV'; MinParams:1; MaxParams:30; Func:fpsAVEDEV), // fekAVEDEV
(Symbol:'AVERAGE'; MinParams:1; MaxParams:30; Func:fpsAVERAGE), // fekAVERAGE
- (Symbol:'BETADIST'; MinParams:3; MaxParams:5; Func:nil), // fekBETADIST
- (Symbol:'BETAINV'; MinParams:3; MaxParams:5; Func:nil), // fekBETAINV
- (Symbol:'BINOMDIST'; MinParams:4; MaxParams:4; Func:nil), // fekBINOMDIST
- (Symbol:'CHIDIST'; MinParams:2; MaxParams:2; Func:nil), // fekCHIDIST
- (Symbol:'CHIINV'; MinParams:2; MaxParams:2; Func:nil), // fekCHIINV
+ (Symbol:'BETADIST'; MinParams:3; MaxParams:5; Func:nil), // fekBETADIST
+ (Symbol:'BETAINV'; MinParams:3; MaxParams:5; Func:nil), // fekBETAINV
+ (Symbol:'BINOMDIST'; MinParams:4; MaxParams:4; Func:nil), // fekBINOMDIST
+ (Symbol:'CHIDIST'; MinParams:2; MaxParams:2; Func:nil), // fekCHIDIST
+ (Symbol:'CHIINV'; MinParams:2; MaxParams:2; Func:nil), // fekCHIINV
(Symbol:'COUNT'; MinParams:0; MaxParams:30; Func:fpsCOUNT), // fekCOUNT
(Symbol:'COUNTA'; MinParams:0; MaxParams:30; Func:nil), // fekCOUNTA
- (Symbol:'COUNTBLANK';MinParams:1; MaxParams:1; Func:nil), // fekCOUNTBLANK
- (Symbol:'COUNTIF'; MinParams:2; MaxParams:2; Func:nil), // fekCOUNTIF
+ (Symbol:'COUNTBLANK';MinParams:1; MaxParams:1; Func:nil), // fekCOUNTBLANK
+ (Symbol:'COUNTIF'; MinParams:2; MaxParams:2; Func:nil), // fekCOUNTIF
(Symbol:'MAX'; MinParams:1; MaxParams:30; Func:fpsMAX), // fekMAX
(Symbol:'MEDIAN'; MinParams:1; MaxParams:30; Func:nil), // fekMEDIAN
(Symbol:'MIN'; MinParams:1; MaxParams:30; Func:fpsMIN), // fekMIN
- (Symbol:'PERMUT'; MinParams:2; MaxParams:2; Func:nil), // fekPERMUT
- (Symbol:'POISSON'; MinParams:3; MaxParams:3; Func:nil), // fekPOISSON
+ (Symbol:'PERMUT'; MinParams:2; MaxParams:2; Func:nil), // fekPERMUT
+ (Symbol:'POISSON'; MinParams:3; MaxParams:3; Func:nil), // fekPOISSON
(Symbol:'PRODUCT'; MinParams:0; MaxParams:30; Func:fpsPRODUCT), // fekPRODUCT
(Symbol:'STDEV'; MinParams:1; MaxParams:30; Func:fpsSTDEV), // fekSTDEV
(Symbol:'STDEVP'; MinParams:1; MaxParams:30; Func:fpsSTDEVP), // fekSTDEVP
@@ -1210,7 +1210,7 @@ const
(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:nil), // 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 }
@@ -1222,15 +1222,15 @@ const
(Symbol:'CELL'; MinParams:1; MaxParams:2; Func:nil), // fekCELLINFO
(Symbol:'INFO'; MinParams:1; MaxParams:1; Func:nil), // fekINFO
(Symbol:'ISBLANK'; MinParams:1; MaxParams:1; Func:nil), // fekIsBLANK
- (Symbol:'ISERR'; MinParams:1; MaxParams:1; Func:nil), // fekIsERR
- (Symbol:'ISERROR'; MinParams:1; MaxParams:1; Func:nil), // fekIsERROR
- (Symbol:'ISLOGICAL'; MinParams:1; MaxParams:1; Func:nil), // fekIsLOGICAL
- (Symbol:'ISNA'; MinParams:1; MaxParams:1; Func:nil), // fekIsNA
- (Symbol:'ISNONTEXT'; MinParams:1; MaxParams:1; Func:nil), // fekIsNONTEXT
- (Symbol:'ISNUMBER'; MinParams:1; MaxParams:1; Func:nil), // fekIsNUMBER
+ (Symbol:'ISERR'; MinParams:1; MaxParams:1; Func:fpsISERR), // fekIsERR
+ (Symbol:'ISERROR'; MinParams:1; MaxParams:1; Func:fpsISERROR), // fekIsERROR
+ (Symbol:'ISLOGICAL'; MinParams:1; MaxParams:1; Func:fpsISLOGICAL), // fekIsLOGICAL
+ (Symbol:'ISNA'; MinParams:1; MaxParams:1; Func:fpsISNA), // fekIsNA
+ (Symbol:'ISNONTEXT'; MinParams:1; MaxParams:1; Func:fpsISNONTEXT), // fekIsNONTEXT
+ (Symbol:'ISNUMBER'; MinParams:1; MaxParams:1; Func:fpsISNUMBER), // fekIsNUMBER
(Symbol:'ISREF'; MinParams:1; MaxParams:1; Func:nil), // fekIsRef
- (Symbol:'ISTEXT'; MinParams:1; MaxParams:1; Func:nil), // fekIsTEXT
- (Symbol:'VALUE'; MinParams:1; MaxParams:1; Func:nil), // fekValue
+ (Symbol:'ISTEXT'; MinParams:1; MaxParams:1; Func:fpsISTEXT), // fekIsTEXT
+ (Symbol:'VALUE'; MinParams:1; MaxParams:1; Func:fpsVALUE), // fekValue
{ Other operations }
(Symbol:'SUM'; MinParams:1; MaxParams:1; Func:nil) // fekOpSUM (Unary sum operation). Note: CANNOT be used for summing sell contents; use fekSUM}
);
@@ -1460,14 +1460,18 @@ begin
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);
+ args.Push(val);
+ {
// Push valid result on stack, exit in case of error
case val.ArgumentType of
atNumber, atString, atBool, atEmpty:
@@ -1478,6 +1482,7 @@ begin
exit;
end;
end;
+ }
end; // case
end; // for
if args.Count = 1 then begin
@@ -1486,19 +1491,22 @@ begin
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);
+ {
// This case is a program error --> raise an exception
raise Exception.CreateFmt('Incorrect argument count of the formula in cell %s', [
GetCellString(ACell^.Row, ACell^.Col, [])
]);
+ }
finally
args.Free;
end;
end;
-
{@@
Converts a FPSpreadsheet cell position, which is Row, Col in numbers
and zero based, to a textual representation which is [Col][Row],
diff --git a/components/fpspreadsheet/tests/formulatests.pas b/components/fpspreadsheet/tests/formulatests.pas
index d21c8c317..e48500d4f 100644
--- a/components/fpspreadsheet/tests/formulatests.pas
+++ b/components/fpspreadsheet/tests/formulatests.pas
@@ -6,6 +6,10 @@ unit formulatests;
when the corresponding rpn formula is calculated. }
{.$DEFINE ENABLE_CALC_RPN_EXCEPTIONS}
+{ Deactivate this define to include errors in the structure of the rpn formulas.
+ Note that Excel report a corrupted file when trying to read this file }
+{.DEFINE ENABLE_DEFECTIVE_FORMULAS }
+
interface
@@ -143,6 +147,9 @@ var
cell: PCell;
sollValues: array of TsArgument;
formula: String;
+ s: String;
+ t: TTime;
+ hr,min,sec,msec: Word;
begin
TempFile := GetTempFileName;
diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi
index 86230b550..b35028661 100644
--- a/components/fpspreadsheet/tests/spreadtestgui.lpi
+++ b/components/fpspreadsheet/tests/spreadtestgui.lpi
@@ -47,6 +47,9 @@
+
+
+
@@ -154,6 +157,9 @@
+
+
+
diff --git a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
index 6cc8eb3bb..04883ba45 100644
--- a/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
+++ b/components/fpspreadsheet/tests/testcases_calcrpnformula.inc
@@ -680,6 +680,230 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateNumber(tanh(0.1));
+
+{------------------------------------------------------------------------------}
+{ Date/time functions }
+{------------------------------------------------------------------------------}
+
+ // 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))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(EncodeDate(2014,7,1));
+
+ // 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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(7);
+
+ // NOW
+ inc(Row);
+ // 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);
+ until msec < 500;
+ MyWorksheet.WriteUTF8Text(Row, 0, '=NOW()');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNFunc(fekNOW, nil)));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(t);
+
+ // 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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(EncodeTime(9, 59, 20, 0));
+
+ // TODAY
+ inc(Row);
+ MyWorksheet.WriteUTF8Text(Row, 0, '=TODAY()');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNFunc(fekTODAY, nil)));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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)))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(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))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(2014);
+
+
{------------------------------------------------------------------------------}
{ Statistical functions }
{------------------------------------------------------------------------------}
@@ -1184,6 +1408,17 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateString('d');
+ // 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))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateString('lazarus');
+
// Trim
inc(Row);
MyWorksheet.WriteUTF8Text(Row, 0, '=TRIM(" Hallo word ")');
@@ -1211,3 +1446,67 @@
SetLength(sollValues, Row+1);
sollValues[Row] := CreateString(UTF8UpperCase('Viele Grüße'));
+
+{------------------------------------------------------------------------------}
+{ Information functions }
+{------------------------------------------------------------------------------}
+
+ // IsError
+ inc(Row);
+ MyWorksheet.WriteUTF8Text(Row, 0, '=ISERROR(1/0)');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNNumber(1,
+ RPNNumber(0,
+ RPNFunc(fekDiv,
+ RPNFunc(fekISERROR, nil))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateBool(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))))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateBool(false);
+
+ // VALUE
+ inc(Row);
+ MyWorksheet.WriteUTF8Text(Row, 0, '=VALUE("100")');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNString('100',
+ RPNFunc(fekVALUE, nil))));
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateNumber(100);
+
+{------------------------------------------------------------------------------}
+{ Error cases }
+{------------------------------------------------------------------------------}
+{$IFDEF ENABLE_DEFECTIVE_FORMULAS }
+ // Using less parameters than specified
+ inc(Row);
+ MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1)');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNBool(true,
+ RPNNumber(1.0,
+ RPNFunc(fekIF,3, nil))))); // <-- we have only 2 parameters!
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateError(errArgError);
+
+ // Using more parameters than specified
+ inc(Row);
+ MyWorksheet.WriteUTF8Text(Row, 0, '=IF(true,1,"A")');
+ MyWorksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
+ RPNBool(true,
+ RPNNumber(1.0,
+ RPNString('A',
+ RPNFunc(fekIF,2, nil)))))); // <-- we have 3 parameters, not 2
+ SetLength(sollValues, Row+1);
+ sollValues[Row] := CreateError(errWrongType);
+ { The first idea was that this should report an ArgError, but in fact it is
+ a WrongType error because popping two values from the stack finds a number,
+ but a bool is expected }
+{$ENDIF}