fpspreadsheet: Add workbook option boIgnoreFormulas for writing unsupported formulas (See forum https://forum.lazarus.freepascal.org/index.php/topic,40146.0.html).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6209 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2018-02-20 23:02:45 +00:00
parent 04a727ef5d
commit ef46ccd90d
4 changed files with 262 additions and 65 deletions

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="demo_ignore_formula"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="0"/>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="laz_fpspreadsheet"/>
</Item1>
</RequiredPackages>
<Units Count="1">
<Unit0>
<Filename Value="demo_ignore_formula.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="demo_ignore_formula"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
</CompilerOptions>
<Debugging>
<Exceptions Count="3">
<Item1>
<Name Value="EAbort"/>
</Item1>
<Item2>
<Name Value="ECodetoolError"/>
</Item2>
<Item3>
<Name Value="EFOpenError"/>
</Item3>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -0,0 +1,114 @@
{ This example uses the "ignoreFormula" workbook option to create an ods
file with an external reference.
NOTE: The external reference is not calculated. This will happen when
LibreOffice Calc loads the file. When the file is closed in LOCalc
confirmation must be given to save the file because it has been changed
by LOCalc.
This method does not work with Excel because it writes an additonal
folder and xml files for external links. }
program demo_ignore_formula;
{$mode objfpc}{$H+}
{$DEFINE ODS}
{.$DEFINE XLSX} // <---- NOT WORKING
uses
SysUtils, FileUtil,
fpsTypes, fpsUtils, fpSpreadsheet, fpsOpenDocument, xlsxOOXML;
const
{$IFDEF ODS}
FILE_FORMAT = sfOpenDocument;
MASTER_FILE = 'master.ods';
EXTERNAL_FILE = 'external.ods';
{$ENDIF}
{$IFDEF XLSX}
FILE_FORMAT = sfOOXML;
MASTER_FILE = 'master.xlsx';
EXTERNAL_FILE = 'external.xlsx';
{$ENDIF}
EXTERNAL_SHEET = 'Sheet';
CELL1 = 'A1';
CELL2 = 'B1';
var
book: TsWorkbook;
sheet: TsWorksheet;
cell: PCell;
// example for an external ods reference:
// ='file:///D:/fpspreadsheet/examples/other/external.ods'#$Sheet.A1
function ODS_ExtRef(AFilename, ASheetName, ACellAddr: String): String;
var
i: Integer;
begin
Result := ExpandFileName(AFileName);
for i:=1 to Length(Result) do
if Result[i] = '\' then Result[i] := '/';
Result := Format('''file:///%s''#$%s.%s', [
Result, ASheetName, ACellAddr
]);
end;
// example for an external xlsx reference:
// =[external.xlsx]Sheet!$A$1
function XLSX_ExtRef(AFilename, ASheetName, ACellAddr: String): String;
var
r, c: Cardinal;
flags: TsRelFlags;
begin
ParseCellString(ACellAddr, r, c, flags);
Result := Format('[%s]%s!%s', [
ExtractFileName(AFileName), ASheetName, GetCellString(r, c, [])
]);
end;
function ExtRef(AFileName, ASheetName, ACellAddr: String): String;
begin
{$IFDEF ODS}
Result := ODS_ExtRef(AFileName, ASheetName, ACellAddr);
{$ENDIF}
{$IFDEF XLSX}
Result := XLSX_ExtRef(AFilename, ASheetName, ACellAddr);
{$ENDIF}
end;
begin
// Write external file
book := TsWorkbook.Create;
try
sheet := book.AddWorksheet(EXTERNAL_SHEET);
cell := sheet.GetCell(CELL1);
sheet.WriteNumber(cell, 1000.0);
cell := sheet.GetCell(CELL2);
sheet.WriteText(cell, 'Hallo');
book.WriteToFile(EXTERNAL_FILE, FILE_FORMAT, true);
finally
book.Free;
end;
// Write ods and xlsx master files
book := TsWorkbook.Create;
try
// Instruct fpspreadsheet to leave the formula alone.
book.Options := book.Options + [boIgnoreFormulas];
sheet := book.AddWorksheet('Sheet');
// Write external references
sheet.WriteFormula(0, 0, ExtRef(EXTERNAL_FILE, EXTERNAL_SHEET, CELL1));
sheet.WriteFormula(1, 0, ExtRef(EXTERNAL_FILE, EXTERNAL_SHEET, CELL2));
book.WriteToFile(MASTER_FILE, FILE_FORMAT, true);
finally
book.Free;
end;
end.

View File

@ -7446,8 +7446,10 @@ var
comment: String; comment: String;
r1,c1,r2,c2: Cardinal; r1,c1,r2,c2: Cardinal;
fmt: TsCellFormat; fmt: TsCellFormat;
ignoreFormulas: Boolean;
begin begin
Unused(ARow, ACol); Unused(ARow, ACol);
ignoreFormulas := (boIgnoreFormulas in FWorkbook.Options);
// Style // Style
fmt := FWorkbook.GetCellFormat(ACell^.FormatIndex); fmt := FWorkbook.GetCellFormat(ACell^.FormatIndex);
@ -7473,59 +7475,65 @@ begin
if FWorksheet.HasHyperlink(ACell) then if FWorksheet.HasHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]); FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
// Convert string formula to the format needed by ods: semicolon list separators! if ignoreFormulas then begin
parser := TsSpreadsheetParser.Create(FWorksheet); formula := ACell^.FormulaValue;
try if (formula <> '') and (formula[1] = '=') then Delete(formula, 1, 1);
parser.Dialect := fdOpenDocument; end else
parser.Expression := ACell^.FormulaValue; begin
formula := Parser.LocalizedExpression[FPointSeparatorSettings]; valueStr := '';
finally // Convert string formula to the format needed by ods: semicolon list separators!
parser.Free; parser := TsSpreadsheetParser.Create(FWorksheet);
end; try
parser.Dialect := fdOpenDocument;
parser.Expression := ACell^.FormulaValue;
formula := Parser.LocalizedExpression[FPointSeparatorSettings];
finally
parser.Free;
end;
valueStr := ''; case ACell^.ContentType of
case ACell^.ContentType of cctNumber:
cctNumber: begin
begin valuetype := 'float';
valuetype := 'float'; value := ' office:value="' + Format('%g', [ACell^.NumberValue], FPointSeparatorSettings) + '"';
value := ' office:value="' + Format('%g', [ACell^.NumberValue], FPointSeparatorSettings) + '"'; end;
end; cctDateTime:
cctDateTime: if trunc(ACell^.DateTimeValue) = 0 then
if trunc(ACell^.DateTimeValue) = 0 then begin
begin valuetype := 'time';
valuetype := 'time'; value := ' office:time-value="' + FormatDateTime(ISO8601FormatTimeOnly, ACell^.DateTimeValue) + '"';
value := ' office:time-value="' + FormatDateTime(ISO8601FormatTimeOnly, ACell^.DateTimeValue) + '"'; end
end
else
begin
valuetype := 'date';
if frac(ACell^.DateTimeValue) = 0.0 then
value := ' office:date-value="' + FormatDateTime(ISO8601FormatDateOnly, ACell^.DateTimeValue) + '"'
else else
value := ' office:date-value="' + FormatDateTime(ISO8601FormatExtended, ACell^.DateTimeValue) + '"'; begin
end; valuetype := 'date';
cctUTF8String: if frac(ACell^.DateTimeValue) = 0.0 then
begin value := ' office:date-value="' + FormatDateTime(ISO8601FormatDateOnly, ACell^.DateTimeValue) + '"'
valuetype := 'string'; else
value := ' office:string-value="' + ACell^.UTF8StringValue +'"'; value := ' office:date-value="' + FormatDateTime(ISO8601FormatExtended, ACell^.DateTimeValue) + '"';
valueStr := '<text:p>' + ACell^.UTF8StringValue + '</text:p>'; end;
end; cctUTF8String:
cctBool: begin
begin valuetype := 'string';
valuetype := 'boolean'; value := ' office:string-value="' + ACell^.UTF8StringValue +'"';
value := ' office:boolean-value="' + BoolToStr(ACell^.BoolValue, 'true', 'false') + '"'; valueStr := '<text:p>' + ACell^.UTF8StringValue + '</text:p>';
end; end;
cctError: cctBool:
if HasFormula(ACell) then begin
begin valuetype := 'boolean';
// Open/LibreOffice always writes a float value 0 to the cell value := ' office:boolean-value="' + BoolToStr(ACell^.BoolValue, 'true', 'false') + '"';
valuetype := 'float'; // error as result of a formula end;
value := ' office:value="0"'; cctError:
end else if HasFormula(ACell) then
begin begin
valuetype := 'string" calcext:value-type="error'; // an error "constant" // Open/LibreOffice always writes a float value 0 to the cell
value := ' office:value=""'; valuetype := 'float'; // error as result of a formula
end; value := ' office:value="0"';
end else
begin
valuetype := 'string" calcext:value-type="error'; // an error "constant"
value := ' office:value=""';
end;
end;
end; end;
{ Fix special xml characters } { Fix special xml characters }
@ -7533,7 +7541,7 @@ begin
{ We are writing a very rudimentary formula here without result and result { We are writing a very rudimentary formula here without result and result
data type. Seems to work... } data type. Seems to work... }
if FWorksheet.GetCalcState(ACell) = csCalculated then if not ignoreFormulas or (FWorksheet.GetCalcState(ACell) = csCalculated) then
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:formula="=%s" office:value-type="%s"%s%s%s>' + '<table:table-cell table:formula="=%s" office:value-type="%s"%s%s%s>' +
comment + comment +

View File

@ -675,10 +675,12 @@ type
@param boWriteZoomfactor Instructs the writer to write the current zoom @param boWriteZoomfactor Instructs the writer to write the current zoom
factors of the worksheets to file. factors of the worksheets to file.
@param boAbortReadOnFormulaError Aborts reading if a formula error is @param boAbortReadOnFormulaError Aborts reading if a formula error is
encountered } encountered
@param boIgnoreFormulas Formulas are not checked and not calculated.
Cannot be used for biff formats. }
TsWorkbookOption = (boVirtualMode, boBufStream, boFileStream, TsWorkbookOption = (boVirtualMode, boBufStream, boFileStream,
boAutoCalc, boCalcBeforeSaving, boReadFormulas, boWriteZoomFactor, boAutoCalc, boCalcBeforeSaving, boReadFormulas, boWriteZoomFactor,
boAbortReadOnFormulaError); boAbortReadOnFormulaError, boIgnoreFormulas);
{@@ Set of option flags for the workbook } {@@ Set of option flags for the workbook }
TsWorkbookOptions = set of TsWorkbookOption; TsWorkbookOptions = set of TsWorkbookOption;
@ -1273,6 +1275,9 @@ var
cell: PCell; cell: PCell;
formula: String; formula: String;
begin begin
if (boIgnoreFormulas in Workbook.Options) then
exit;
formula := ACell^.FormulaValue; formula := ACell^.FormulaValue;
ACell^.Flags := ACell^.Flags + [cfCalculating] - [cfCalculated]; ACell^.Flags := ACell^.Flags + [cfCalculating] - [cfCalculated];
@ -1345,6 +1350,9 @@ procedure TsWorksheet.CalcFormulas;
var var
cell: PCell; cell: PCell;
begin begin
if (boIgnoreFormulas in Workbook.Options) then
exit;
// prevent infinite loop due to triggering of formula calculation whenever // prevent infinite loop due to triggering of formula calculation whenever
// a cell changes during execution of CalcFormulas. // a cell changes during execution of CalcFormulas.
inc(FWorkbook.FCalculationLock); inc(FWorkbook.FCalculationLock);
@ -5669,18 +5677,21 @@ begin
if ACell = nil then if ACell = nil then
exit; exit;
// Remove '='; is not stored internally if not (boIgnoreFormulas in Workbook.Options) then
if (AFormula <> '') and (AFormula[1] = '=') then begin
AFormula := Copy(AFormula, 2, Length(AFormula)); // Remove '='; is not stored internally
if (AFormula <> '') and (AFormula[1] = '=') then
AFormula := Copy(AFormula, 2, Length(AFormula));
// Convert "localized" formula to standard format // Convert "localized" formula to standard format
if ALocalized then begin if ALocalized then begin
parser := TsSpreadsheetParser.Create(self); parser := TsSpreadsheetParser.Create(self);
try try
parser.LocalizedExpression[Workbook.FormatSettings] := AFormula; parser.LocalizedExpression[Workbook.FormatSettings] := AFormula;
AFormula := parser.Expression; AFormula := parser.Expression;
finally finally
parser.Free; parser.Free;
end;
end; end;
end; end;