You've already forked lazarus-ccr
fpspreadsheet: Introduce recursive calculation of cells depending on non-yet calculated cells. Add demo "test_recursive" in folder "other".
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3271 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
103
components/fpspreadsheet/examples/other/test_recursive_calc.lpi
Normal file
103
components/fpspreadsheet/examples/other/test_recursive_calc.lpi
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<CONFIG>
|
||||||
|
<ProjectOptions>
|
||||||
|
<Version Value="9"/>
|
||||||
|
<PathDelim Value="\"/>
|
||||||
|
<General>
|
||||||
|
<Flags>
|
||||||
|
<MainUnitHasCreateFormStatements Value="False"/>
|
||||||
|
<MainUnitHasTitleStatement Value="False"/>
|
||||||
|
</Flags>
|
||||||
|
<SessionStorage Value="InProjectDir"/>
|
||||||
|
<MainUnit Value="0"/>
|
||||||
|
<Title Value="test_recursive_calc"/>
|
||||||
|
<UseAppBundle Value="False"/>
|
||||||
|
<ResourceType Value="res"/>
|
||||||
|
</General>
|
||||||
|
<i18n>
|
||||||
|
<EnableI18N LFM="False"/>
|
||||||
|
</i18n>
|
||||||
|
<VersionInfo>
|
||||||
|
<StringTable ProductVersion=""/>
|
||||||
|
</VersionInfo>
|
||||||
|
<BuildModes Count="2">
|
||||||
|
<Item1 Name="Debug" Default="True"/>
|
||||||
|
<Item2 Name="Release">
|
||||||
|
<CompilerOptions>
|
||||||
|
<Version Value="11"/>
|
||||||
|
<PathDelim Value="\"/>
|
||||||
|
<SearchPaths>
|
||||||
|
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||||
|
</SearchPaths>
|
||||||
|
<CodeGeneration>
|
||||||
|
<SmartLinkUnit Value="True"/>
|
||||||
|
</CodeGeneration>
|
||||||
|
<Linking>
|
||||||
|
<Debugging>
|
||||||
|
<StripSymbols Value="True"/>
|
||||||
|
</Debugging>
|
||||||
|
<LinkSmart Value="True"/>
|
||||||
|
<Options>
|
||||||
|
<Win32>
|
||||||
|
<GraphicApplication Value="True"/>
|
||||||
|
</Win32>
|
||||||
|
</Options>
|
||||||
|
</Linking>
|
||||||
|
</CompilerOptions>
|
||||||
|
</Item2>
|
||||||
|
</BuildModes>
|
||||||
|
<PublishOptions>
|
||||||
|
<Version Value="2"/>
|
||||||
|
</PublishOptions>
|
||||||
|
<RunParams>
|
||||||
|
<local>
|
||||||
|
<FormatVersion Value="1"/>
|
||||||
|
</local>
|
||||||
|
</RunParams>
|
||||||
|
<RequiredPackages Count="1">
|
||||||
|
<Item1>
|
||||||
|
<PackageName Value="laz_fpspreadsheet"/>
|
||||||
|
</Item1>
|
||||||
|
</RequiredPackages>
|
||||||
|
<Units Count="1">
|
||||||
|
<Unit0>
|
||||||
|
<Filename Value="test_recursive_calc.pas"/>
|
||||||
|
<IsPartOfProject Value="True"/>
|
||||||
|
<UnitName Value="test_recursive_calc"/>
|
||||||
|
</Unit0>
|
||||||
|
</Units>
|
||||||
|
</ProjectOptions>
|
||||||
|
<CompilerOptions>
|
||||||
|
<Version Value="11"/>
|
||||||
|
<PathDelim Value="\"/>
|
||||||
|
<SearchPaths>
|
||||||
|
<IncludeFiles Value="$(ProjOutDir)"/>
|
||||||
|
</SearchPaths>
|
||||||
|
<CodeGeneration>
|
||||||
|
<SmartLinkUnit Value="True"/>
|
||||||
|
</CodeGeneration>
|
||||||
|
<Linking>
|
||||||
|
<Debugging>
|
||||||
|
<DebugInfoType Value="dsDwarf2Set"/>
|
||||||
|
</Debugging>
|
||||||
|
<Options>
|
||||||
|
<Win32>
|
||||||
|
<GraphicApplication Value="True"/>
|
||||||
|
</Win32>
|
||||||
|
</Options>
|
||||||
|
</Linking>
|
||||||
|
</CompilerOptions>
|
||||||
|
<Debugging>
|
||||||
|
<Exceptions Count="3">
|
||||||
|
<Item1>
|
||||||
|
<Name Value="EAbort"/>
|
||||||
|
</Item1>
|
||||||
|
<Item2>
|
||||||
|
<Name Value="ECodetoolError"/>
|
||||||
|
</Item2>
|
||||||
|
<Item3>
|
||||||
|
<Name Value="EFOpenError"/>
|
||||||
|
</Item3>
|
||||||
|
</Exceptions>
|
||||||
|
</Debugging>
|
||||||
|
</CONFIG>
|
@ -0,0 +1,56 @@
|
|||||||
|
{ This demo is a test for recursive calculation of cells. The cell formulas
|
||||||
|
are constructed such that the first cell depends on the second, and the second
|
||||||
|
cell depends on the third one. Only the third cell contains a number.
|
||||||
|
Therefore calculation has to be done recursively until the independent third
|
||||||
|
cell is found. }
|
||||||
|
|
||||||
|
program test_recursive_calc;
|
||||||
|
|
||||||
|
{$mode objfpc}{$H+}
|
||||||
|
|
||||||
|
uses
|
||||||
|
{$IFDEF UNIX}{$IFDEF UseCThreads}
|
||||||
|
cthreads,
|
||||||
|
{$ENDIF}{$ENDIF}
|
||||||
|
Classes, laz_fpspreadsheet
|
||||||
|
{ you can add units after this },
|
||||||
|
math, fpspreadsheet, fpsfunc;
|
||||||
|
|
||||||
|
var
|
||||||
|
workbook: TsWorkbook;
|
||||||
|
worksheet: TsWorksheet;
|
||||||
|
|
||||||
|
begin
|
||||||
|
workbook := TsWorkbook.Create;
|
||||||
|
try
|
||||||
|
worksheet := workbook.AddWorksheet('Calc_test');
|
||||||
|
worksheet.Options := worksheet.Options + [soCalcBeforeSaving];
|
||||||
|
worksheet.WriteColWidth(0, 20);
|
||||||
|
|
||||||
|
// A1
|
||||||
|
worksheet.WriteUTF8Text(0, 0, '=B2+1');
|
||||||
|
// B1
|
||||||
|
worksheet.WriteRPNFormula(0, 1, CreateRPNFormula(
|
||||||
|
RPNCellValue('B2',
|
||||||
|
RPNNumber(1,
|
||||||
|
RPNFunc(fekAdd, nil)))));
|
||||||
|
|
||||||
|
// A2
|
||||||
|
worksheet.WriteUTF8Text(1, 0, '=B3+1');
|
||||||
|
// B2
|
||||||
|
worksheet.WriteRPNFormula(1, 1, CreateRPNFormula(
|
||||||
|
RPNCellValue('B3',
|
||||||
|
RPNNumber(1,
|
||||||
|
RPNFunc(fekAdd, nil)))));
|
||||||
|
|
||||||
|
// A3
|
||||||
|
worksheet.WriteUTF8Text(2, 0, '(not dependent)');
|
||||||
|
// B3
|
||||||
|
worksheet.WriteNumber(2, 1, 1);
|
||||||
|
|
||||||
|
workbook.WriteToFile('test_calc.xls', sfExcel8, true);
|
||||||
|
finally
|
||||||
|
workbook.Free;
|
||||||
|
end;
|
||||||
|
end.
|
||||||
|
|
@ -361,6 +361,9 @@ const
|
|||||||
);
|
);
|
||||||
|
|
||||||
type
|
type
|
||||||
|
{@@ State flags while calculating formulas }
|
||||||
|
TsCalcState = (csNotCalculated, csCalculating, csCalculated);
|
||||||
|
|
||||||
{@@ Cell structure for TsWorksheet
|
{@@ Cell structure for TsWorksheet
|
||||||
The cell record contains information on the location of the cell (row and
|
The cell record contains information on the location of the cell (row and
|
||||||
column index), on the value contained (number, date, text, ...), and on
|
column index), on the value contained (number, date, text, ...), and on
|
||||||
@ -396,6 +399,8 @@ type
|
|||||||
NumberFormat: TsNumberFormat;
|
NumberFormat: TsNumberFormat;
|
||||||
NumberFormatStr: String;
|
NumberFormatStr: String;
|
||||||
RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR
|
RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR
|
||||||
|
{ Status flags }
|
||||||
|
CalcState: TsCalcState;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
{@@ Pointer to a TCell record }
|
{@@ Pointer to a TCell record }
|
||||||
@ -471,8 +476,13 @@ type
|
|||||||
FOptions: TsSheetOptions;
|
FOptions: TsSheetOptions;
|
||||||
FOnChangeCell: TsCellEvent;
|
FOnChangeCell: TsCellEvent;
|
||||||
FOnChangeFont: TsCellEvent;
|
FOnChangeFont: TsCellEvent;
|
||||||
procedure CalcFormulaCallback(data, arg: Pointer);
|
|
||||||
|
{ Setter/Getter }
|
||||||
function GetFormatSettings: TFormatSettings;
|
function GetFormatSettings: TFormatSettings;
|
||||||
|
|
||||||
|
{ Callback procedures called when iterating through all cells }
|
||||||
|
procedure CalcFormulaCallback(data, arg: Pointer);
|
||||||
|
procedure CalcStateCallback(data, arg: Pointer);
|
||||||
procedure RemoveCallback(data, arg: pointer);
|
procedure RemoveCallback(data, arg: pointer);
|
||||||
|
|
||||||
protected
|
protected
|
||||||
@ -1019,6 +1029,7 @@ resourcestring
|
|||||||
lpIllegalNumberFormat = 'Illegal number format.';
|
lpIllegalNumberFormat = 'Illegal number format.';
|
||||||
lpSpecifyNumberOfParams = 'Specify number of parameters for function %s';
|
lpSpecifyNumberOfParams = 'Specify number of parameters for function %s';
|
||||||
lpIncorrectParamCount = 'Funtion %s requires at least %d and at most %d parameters.';
|
lpIncorrectParamCount = 'Funtion %s requires at least %d and at most %d parameters.';
|
||||||
|
lpCircularReference = 'Circular reference found when calculating worksheet formulas';
|
||||||
lpTRUE = 'TRUE';
|
lpTRUE = 'TRUE';
|
||||||
lpFALSE = 'FALSE';
|
lpFALSE = 'FALSE';
|
||||||
lpErrEmptyIntersection = '#NULL!';
|
lpErrEmptyIntersection = '#NULL!';
|
||||||
@ -1431,20 +1442,50 @@ begin
|
|||||||
end;
|
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);
|
||||||
|
|
||||||
|
if Length(cell^.RPNFormulaValue) > 0 then
|
||||||
|
cell^.CalcState := csNotCalculated;
|
||||||
|
end;
|
||||||
|
|
||||||
|
{@@
|
||||||
|
Calculates all rpn formulas of the worksheet.
|
||||||
}
|
}
|
||||||
procedure TsWorksheet.CalcFormulas;
|
procedure TsWorksheet.CalcFormulas;
|
||||||
var
|
var
|
||||||
node: TAVLTreeNode;
|
node: TAVLTreeNode;
|
||||||
begin
|
begin
|
||||||
Node := FCells.FindLowest;
|
// Step 1 - mark all formula cells as "not calculated"
|
||||||
|
node := FCells.FindLowest;
|
||||||
|
while Assigned(node) do begin
|
||||||
|
CalcStateCallback(node.Data, nil);
|
||||||
|
node := FCells.FindSuccessor(node);
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Step 2 - calculate cells. If a not-calculated cell is found it is
|
||||||
|
// calculated and then marked as such.
|
||||||
|
node := FCells.FindLowest;
|
||||||
while Assigned(Node) do begin
|
while Assigned(Node) do begin
|
||||||
CalcFormulaCallback(Node.Data, nil);
|
CalcFormulaCallback(Node.Data, nil);
|
||||||
node := FCells.FindSuccessor(node);
|
node := FCells.FindSuccessor(node);
|
||||||
end;
|
end;
|
||||||
end;
|
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.
|
||||||
}
|
}
|
||||||
procedure TsWorksheet.CalcRPNFormula(ACell: PCell);
|
procedure TsWorksheet.CalcRPNFormula(ACell: PCell);
|
||||||
var
|
var
|
||||||
@ -1461,14 +1502,20 @@ begin
|
|||||||
then
|
then
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
|
ACell^.CalcState := csCalculating;
|
||||||
|
|
||||||
args := TsArgumentStack.Create;
|
args := TsArgumentStack.Create;
|
||||||
try
|
try
|
||||||
for i := 0 to Length(ACell^.RPNFormulaValue) - 1 do begin
|
for i := 0 to Length(ACell^.RPNFormulaValue) - 1 do begin
|
||||||
fe := ACell^.RPNFormulaValue[i]; // "formula element"
|
fe := ACell^.RPNFormulaValue[i]; // fe = "formula element"
|
||||||
case fe.ElementKind of
|
case fe.ElementKind of
|
||||||
fekCell, fekCellRef:
|
fekCell, fekCellRef:
|
||||||
begin
|
begin
|
||||||
cell := FindCell(fe.Row, fe.Col);
|
cell := FindCell(fe.Row, fe.Col);
|
||||||
|
case cell^.CalcState of
|
||||||
|
csNotCalculated: CalcRPNFormula(cell);
|
||||||
|
csCalculating : raise Exception.Create(lpCircularReference);
|
||||||
|
end;
|
||||||
args.PushCell(cell);
|
args.PushCell(cell);
|
||||||
end;
|
end;
|
||||||
fekCellRange: ;
|
fekCellRange: ;
|
||||||
@ -1499,21 +1546,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
// Result of function
|
// Result of function
|
||||||
val := func(args, fe.ParamsNum);
|
val := func(args, fe.ParamsNum);
|
||||||
|
// Push result on stack for usage by next function or as final result
|
||||||
args.Push(val);
|
args.Push(val);
|
||||||
{
|
|
||||||
// Push valid result on stack, exit in case of error
|
|
||||||
case val.ArgumentType of
|
|
||||||
atNumber, atString, atBool, atEmpty:
|
|
||||||
args.Push(val);
|
|
||||||
atError:
|
|
||||||
begin
|
|
||||||
WriteErrorValue(ACell, val.ErrorValue);
|
|
||||||
exit;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
}
|
|
||||||
end; // case
|
end; // case
|
||||||
end; // for
|
end; // for
|
||||||
|
|
||||||
|
{ When all formula elements have been processed the stack contains the
|
||||||
|
final result. }
|
||||||
if args.Count = 1 then begin
|
if args.Count = 1 then begin
|
||||||
val := args.Pop;
|
val := args.Pop;
|
||||||
case val.ArgumentType of
|
case val.ArgumentType of
|
||||||
@ -1525,13 +1564,8 @@ begin
|
|||||||
end;
|
end;
|
||||||
end else
|
end else
|
||||||
WriteErrorValue(ACell, errArgError);
|
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
|
finally
|
||||||
|
ACell^.CalcState := csCalculated;
|
||||||
args.Free;
|
args.Free;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
Reference in New Issue
Block a user