Validating FlexCel recalculation (Delphi)
Note
This demo is available in your FlexCel installation at <FlexCel Install Folder>\Demo\Delphi\Modules\10.API\75.Validate Recalc and also at https://github.com/tmssoftware/TMS-FlexCel.VCL-demos/tree/master/Delphi/Modules/10.API/75.Validate Recalc
Overview
By default, FlexCel recalculates the files it generates before saving. This is not completely necessary for normal xls/x files, because Excel will recalculate the file again when opening. But when natively printing or exporting to PDF, you need the values from the recalculation, because no Excel is involved in the process.
While most files will recalculate fine, if you have complex formulas and need to verify they will recalculate ok, this is the application to use.
Concepts
Button "Validate Recalc": This will verify that flexcel can understand all the formulas on your sheet.
Button "Compare with Excel": This will open a file you saved with Excel, force a recalculation in FlexCel and then compare all formula results with the original ones. You can use this to verify the results are actually what you expect them to be.
Files
UValidateRecalc.pas
unit UValidateRecalc;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ImgList, ActnList, StdCtrls,
Tabs, Grids,ExtCtrls, ComCtrls, ToolWin,
FlexCel.VCLSupport, FlexCel.Core, FlexCel.XlsAdapter;
type
TFValidateRecalc = class(TForm)
ToolBar1: TToolBar;
ToolButton1: TToolButton;
ToolButton2: TToolButton;
ToolButton3: TToolButton;
ToolButton5: TToolButton;
ToolButton4: TToolButton;
ToolButton6: TToolButton;
Actions: TActionList;
ActionValidateRecalc: TAction;
ActionCompareWithExcel: TAction;
ActionInfo: TAction;
ActionClose: TAction;
OpenDialog: TOpenDialog;
ToolbarImages: TImageList;
report: TMemo;
LinkedFileDialog: TOpenDialog;
ToolbarImages_300Scale: TImageList;
ToolbarImages_100Scale: TImageList;
procedure ActionCloseExecute(Sender: TObject);
procedure ActionValidateRecalcExecute(Sender: TObject);
procedure ActionInfoExecute(Sender: TObject);
procedure ActionCompareWithExcelExecute(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
procedure Work_LoadLinkedFile(const sender: TObject;
const e: TLoadLinkedFileEventArgs);
procedure CompareXls(const xls1, xls2: TXlsFile);
function GetErrorType(const f: TUnsupportedFormulaErrorType): string;
{ Private declarations }
public
{ Public declarations }
end;
var
FValidateRecalc: TFValidateRecalc;
implementation
uses Math, IOUtils, UFlexCelHDPI;
{$R *.dfm}
procedure TFValidateRecalc.ActionCloseExecute(Sender: TObject);
begin
Close;
end;
procedure TFValidateRecalc.ActionInfoExecute(Sender: TObject);
begin
ShowMessage('This example will validate the calculations performed by the FlexCel engine.' +
#$000A'It can do it in 2 different ways:'+
#$000A' 1) The button "Validate Recalc" will analyze a file, and report if there is anything that FlexCel doesn''t support on it.' +
#$000A' 2) The button "Compare with Excel" will open a file saved by Excel, recalculate it with FlexCel, compare the values reported by both FlexCel and Excel and report if there are any differences.');
end;
/// <summary>
/// This event is used when there are linked files, to load them on demand.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
procedure TFValidateRecalc.Work_LoadLinkedFile(const sender: TObject; const e: TLoadLinkedFileEventArgs);
var
FilePath: String;
begin
FilePath := TPath.Combine(TPath.GetDirectoryName(OpenDialog.FileName), e.FileName);
//IMPORTANT: DO NOT USE THIS METHOD IN PRODUCTION IF SECURITY IS IMPORTANT.
//This method will access any file in your harddisk, as long as it is linked in the spreadhseet, and
//that could mean an IMPORTANT SECURITY RISK. You should limit the places where the app can search for
//linked files. Look at the "Recalculating Linked Files" in the PDF API Guide for more information.
if TFile.Exists(FilePath) then //If we find the path, just load the file.
begin
e.Xls := TXlsFile.Create;
e.Xls.Open(FilePath);
exit;
end;
//If we couldn't find the file, ask the user for its location.
LinkedFileDialog.FileName := FilePath;
if not LinkedFileDialog.Execute then //if user cancels, e.Xls will be null, so no file will be used and an #errna error will show in the formulas.
exit;
e.Xls := TXlsFile.Create;
try
e.Xls.Open(LinkedFileDialog.FileName);
except
e.Xls.Free;
e.Xls := nil;
raise;
end;
end;
procedure TFValidateRecalc.ActionValidateRecalcExecute(Sender: TObject);
var
Xls: TXlsFile;
Work: TWorkspace;
Usl: TUnsupportedFormulaList;
i: Int32;
FileName: string;
FunctionStr: string;
begin
if not OpenDialog.Execute then
exit;
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ////////Code here is only needed if you have linked files. In this example we don't know, so we will use it /////////
Work := TWorkspace.Create(true); //Create a workspace
try
Xls := TXlsFile.Create;
try
Xls.Open(OpenDialog.FileName);
except
FreeAndNil(Xls);
raise;
end;
Work.Add(ExtractFileName(OpenDialog.FileName), Xls); //Add the original file to it
Work.LoadLinkedFile:= Work_LoadLinkedFile; //Set up an event to load the linked files.
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
report.Text := 'Results on file: ' + OpenDialog.FileName;
Usl := Work.RecalcAndVerify;
try
if Usl.Count = 0 then
begin
report.Lines.Add('**********All formulas supported!**********');
exit;
end;
report.Lines.Add('Issues Found:');
for i := 0 to Usl.Count - 1 do
begin
if Usl[i].FileName = '' then FileName := '' else FileName := ('File: ' + Usl[i].FileName) + ' => ';
report.Lines.Add(' ' + FileName + Usl[i].Cell.CellRef + ': ' + GetErrorType(Usl[i].ErrorType));
if Usl[i].FunctionName <> '' then
begin
FunctionStr := 'Function';
if Usl[i].ErrorType = TUnsupportedFormulaErrorType.ExternalReference then
FunctionStr := 'Linked file not found';
report.Lines.Add(' ->' + FunctionStr + ': ' + Usl[i].FunctionName);
end;
end;
finally
Usl.Free;
end;
finally
Work.Free;
end;
end;
procedure TFValidateRecalc.ActionCompareWithExcelExecute(Sender: TObject);
var
xls1: TXlsFile;
xls2: TXlsFile;
Work: TWorkspace;
begin
if not OpenDialog.Execute then
exit;
Work := nil; xls1 := nil;
try
ActionCompareWithExcel.Enabled := false;
ActionValidateRecalc.Enabled := false;
try
xls1 := TXlsFile.Create;
xls2 := TXlsFile.Create;
try
xls1.Open(OpenDialog.FileName);
xls2.Open(openDialog.FileName);
report.Text := 'Compare with Excel: ' + OpenDialog.FileName;
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ////////Code here is only needed if you have linked files. In this example we don't know, so we will use it /////////
Work := TWorkspace.Create(true); //Create a workspace
Work.Add(TPath.GetFileName(OpenDialog.FileName), xls1); //Add the original file to it
Work.LoadLinkedFile:= Work_LoadLinkedFile; //Set up an event to load the linked files.
// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
CompareXls(xls1, xls2);
finally
FreeAndNil(xls2);
end;
finally
ActionCompareWithExcel.Enabled := true;
ActionValidateRecalc.Enabled := true;
end;
finally
if Work <> nil then FreeAndNil(Work) else FreeAndNil(xls1);
end;
end;
procedure TFValidateRecalc.CompareXls(const xls1: TXlsFile; const xls2: TXlsFile);
var
DiffCount: Int32;
sheet: Int32;
aColCount: Int32;
r: Int32;
c: Int32;
f: TFormula;
ad: TCellAddress;
f2: TFormula;
cell1: TCellValue;
eps: double;
begin
DiffCount := 0;
xls1.Recalc;
for sheet := 1 to xls1.SheetCount do
begin
xls1.ActiveSheet := sheet;
xls2.ActiveSheet := sheet;
aColCount := xls1.ColCount; //ColCount is slow. We will calculate it only once here.
for r := 1 to xls1.RowCount do
for c := 1 to aColCount do
begin
cell1 := xls1.GetCellValue(r, c);
if cell1.IsFormula then
begin
f := cell1.AsFormula;
ad := TCellAddress.Create(r, c);
f2 := xls2.GetCellValue(r, c).AsFormula;
if f.FormulaResult = TCellValue.Empty then
f.FormulaResult := '';
if f2.FormulaResult = TCellValue.Empty then
f2.FormulaResult := '';
eps := 0;
if f.FormulaResult.IsNumber and f2.FormulaResult.IsNumber then
begin
if SameValue(f2.FormulaResult.AsNumber, 0) then
begin
if SameValue(f.FormulaResult.AsNumber, 0) then eps := 1;
end else
eps := f.FormulaResult.AsNumber / (f2.FormulaResult.AsNumber);
if Abs(eps - 1) < 0.001 then
f.FormulaResult := f2.FormulaResult;
end;
if f.FormulaResult <> f2.FormulaResult then
begin
report.Lines.Add('Sheet:' + xls1.SheetName + ' --- Cell:' + ad.CellRef + ' --- Calculated: '
+ f.FormulaResult.ToString + ' Excel: ' + f2.FormulaResult.ToString
+ ' dif: ' + FloatToStr(eps) + ' formula: ' + f.Text);
Application.ProcessMessages;
Inc(DiffCount);
end;
end;
end;
end;
report.Lines.Add('Finished Comparing.');
if DiffCount = 0
then report.Lines.Add('**********No differences found!**********')
else report.Lines.Add(' --->Found ' + IntToStr(DiffCount) + ' differences');
end;
procedure TFValidateRecalc.FormCreate(Sender: TObject);
begin
RegisterForHDPI(Self, nil);
end;
function TFValidateRecalc.GetErrorType(
const f: TUnsupportedFormulaErrorType): string;
begin
case f of
TUnsupportedFormulaErrorType.FormulaTooComplex: exit('FormulaTooComplex');
TUnsupportedFormulaErrorType.MissingFunction: exit('MissingFunction');
TUnsupportedFormulaErrorType.FunctionalityNotImplemented: exit('FunctionalityNotImplemented');
TUnsupportedFormulaErrorType.CircularReference: exit('CircularReference');
TUnsupportedFormulaErrorType.ExternalReference: exit('ExternalReference');
end;
Result := IntToStr(Integer(f));
end;
end.