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

View File

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