fpspreadsheet: Redo handling for formulas:

- Allow processing of string formulas (conversion to/from rpn formulas, calculation). 
- Drop cell ContentType cctRPNFormula. 
- Drop field RPNFormulaValue of TCell record. 
- Remove all fekXXXX declarations for sheet functions. Function is specified by name now.
- Complete registration mechanism for user-defined formulas.
Adapt all demos
Test cases working
This commit does not yet support: shared formulas, formulas in ods.


git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3506 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-08-30 18:03:22 +00:00
parent a043b77519
commit c87afdcdec
52 changed files with 7168 additions and 4279 deletions

View File

@ -27,10 +27,13 @@
<FormatVersion Value="1"/> <FormatVersion Value="1"/>
</local> </local>
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="2">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
<Item2>
<PackageName Value="LCL"/>
</Item2>
</RequiredPackages> </RequiredPackages>
<Units Count="2"> <Units Count="2">
<Unit0> <Unit0>
@ -43,6 +46,7 @@
<ComponentName Value="Form1"/> <ComponentName Value="Form1"/>
<HasResources Value="True"/> <HasResources Value="True"/>
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
<UnitName Value="main"/>
</Unit1> </Unit1>
</Units> </Units>
</ProjectOptions> </ProjectOptions>

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">

View File

@ -32,6 +32,8 @@ begin
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas, boAutoCalc];
MyWorkbook.ReadFromFile(InputFilename, sfExcel2); MyWorkbook.ReadFromFile(InputFilename, sfExcel2);
MyWorksheet := MyWorkbook.GetFirstWorksheet; MyWorksheet := MyWorkbook.GetFirstWorksheet;
@ -44,9 +46,12 @@ begin
CurCell := MyWorkSheet.GetFirstCell(); CurCell := MyWorkSheet.GetFirstCell();
for i := 0 to MyWorksheet.GetCellCount - 1 do for i := 0 to MyWorksheet.GetCellCount - 1 do
begin begin
WriteLn('Row: ', CurCell^.Row, ' Col: ', CurCell^.Col, ' Value: ', Write('Row: ', CurCell^.Row, ' Col: ', CurCell^.Col, ' Value: ',
UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col)) UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col))
); );
if HasFormula(CurCell) then
Write(' (Formula ', CurCell^.FormulaValue, ')');
WriteLn;
CurCell := MyWorkSheet.GetNextCell(); CurCell := MyWorkSheet.GetNextCell();
end; end;

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">

View File

@ -51,30 +51,31 @@ begin
MyWorksheet.WriteNumber(0, 2, 3.0); MyWorksheet.WriteNumber(0, 2, 3.0);
MyWorksheet.WriteNumber(0, 3, 4.0); MyWorksheet.WriteNumber(0, 3, 4.0);
// Write the formula E1 = ABS(A1) // Write the formula E1 = ABS(A1) as rpn token array
SetLength(MyRPNFormula, 2); SetLength(MyRPNFormula, 2);
MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].ElementKind := fekCell;
MyRPNFormula[0].Col := 0; MyRPNFormula[0].Col := 0;
MyRPNFormula[0].Row := 0; MyRPNFormula[0].Row := 0;
MyRPNFormula[1].ElementKind := fekABS; MyRPNFormula[1].ElementKind := fekFUNC;
MyRPNFormula[1].FuncName := 'ABS';
MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula); MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula);
// Write the formula F1 = ROUND(A1, 0) // Write the formula F1 = ROUND(A1, 0) as rpn token array
SetLength(MyRPNFormula, 3); SetLength(MyRPNFormula, 3);
MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].ElementKind := fekCell;
MyRPNFormula[0].Col := 0; MyRPNFormula[0].Col := 0;
MyRPNFormula[0].Row := 0; MyRPNFormula[0].Row := 0;
MyRPNFormula[1].ElementKind := fekNum; MyRPNFormula[1].ElementKind := fekNum;
MyRPNFormula[1].DoubleValue := 0.0; MyRPNFormula[1].DoubleValue := 0.0;
MyRPNFormula[2].ElementKind := fekROUND; MyRPNFormula[2].ElementKind := fekFUNC;
MyRPNFormula[2].FuncName := 'ROUND';
MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula);
// Write a string formula to G1 = "A" & "B" // Write a string formula to G1 = "A" & "B"
MyWorksheet.WriteRPNFormula(0, 6, CreateRPNFormula( MyWorksheet.WriteFormula(0, 6, '="A"&"B"');
RPNString('A',
RPNSTring('B', // Write string formula to H1 = sin(A1+B1)
RPNFunc(fekConcat, MyWorksheet.WriteFormula(0, 7, '=SIN(A1+B1)');
nil)))));
// Write some string cells // Write some string cells
MyWorksheet.WriteUTF8Text(1, 0, 'First'); MyWorksheet.WriteUTF8Text(1, 0, 'First');

View File

@ -31,6 +31,7 @@ begin
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas];
MyWorkbook.ReadFromFile(InputFilename, sfExcel5); MyWorkbook.ReadFromFile(InputFilename, sfExcel5);
MyWorksheet := MyWorkbook.GetFirstWorksheet; MyWorksheet := MyWorkbook.GetFirstWorksheet;
@ -43,11 +44,12 @@ begin
CurCell := MyWorkSheet.GetFirstCell(); CurCell := MyWorkSheet.GetFirstCell();
for i := 0 to MyWorksheet.GetCellCount - 1 do for i := 0 to MyWorksheet.GetCellCount - 1 do
begin begin
WriteLn('Row: ', CurCell^.Row, Write('Row: ', CurCell^.Row,
' Col: ', CurCell^.Col, ' Value: ', ' Col: ', CurCell^.Col, ' Value: ',
UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col)));
CurCell^.Col)) if HasFormula(CurCell) then
); Write(' - Formula: ', CurCell^.FormulaValue);
WriteLn;
CurCell := MyWorkSheet.GetNextCell(); CurCell := MyWorkSheet.GetNextCell();
end; end;

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">

View File

@ -35,7 +35,9 @@ begin
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
MyWorksheet := MyWorkbook.AddWorksheet(UTF8ToAnsi(Str_Worksheet1)); MyWorksheet := MyWorkbook.AddWorksheet(UTF8ToAnsi(Str_Worksheet1));
MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving];
MyWorksheet.Options := MyWorksheet.Options + [soHasFrozenPanes]; MyWorksheet.Options := MyWorksheet.Options + [soHasFrozenPanes];
MyWorksheet.LeftPaneWidth := 1; MyWorksheet.LeftPaneWidth := 1;
MyWorksheet.TopPaneHeight := 2; MyWorksheet.TopPaneHeight := 2;
@ -139,7 +141,7 @@ begin
end; end;
} }
// Write the formula E1 = A1 + B1 // Write the formula E1 = A1 + B1 as rpn roken array
SetLength(MyRPNFormula, 3); SetLength(MyRPNFormula, 3);
MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].ElementKind := fekCell;
MyRPNFormula[0].Col := 0; MyRPNFormula[0].Col := 0;
@ -150,15 +152,22 @@ begin
MyRPNFormula[2].ElementKind := fekAdd; MyRPNFormula[2].ElementKind := fekAdd;
MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula); MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula);
// Write the formula F1 = ABS(A1) // Write the formula F1 = ABS(A1) as rpn token array
SetLength(MyRPNFormula, 2); SetLength(MyRPNFormula, 2);
MyRPNFormula[0].ElementKind := fekCell; MyRPNFormula[0].ElementKind := fekCell;
MyRPNFormula[0].Col := 0; MyRPNFormula[0].Col := 0;
MyRPNFormula[0].Row := 0; MyRPNFormula[0].Row := 0;
MyRPNFormula[1].ElementKind := fekABS; MyRPNFormula[1].ElementKind := fekFunc;
MyRPNFormula[1].FuncName := 'ABS';
MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula);
r:= 10; // Write formula G1 = "A"&"B" as string formula
MyWorksheet.WriteFormula(0, 6, '="A"&"B"');
// Write formula H1 = sin(A1+B1) as string formula
Myworksheet.WriteFormula(0, 7, '=SIN(A1+B1)');
r := 10;
// Write current date/time to cells B11:B16 // Write current date/time to cells B11:B16
MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDate'); MyWorksheet.WriteUTF8Text(r, 0, 'nfShortDate');
MyWorksheet.WriteDateTime(r, 1, now, nfShortDate); MyWorksheet.WriteDateTime(r, 1, now, nfShortDate);

View File

@ -31,7 +31,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LCL"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">

View File

@ -36,7 +36,7 @@ begin
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas, boAutoCalc]; MyWorkbook.ReadFormulas := true;
MyWorkbook.ReadFromFile(InputFilename, sfExcel8); MyWorkbook.ReadFromFile(InputFilename, sfExcel8);
@ -55,8 +55,8 @@ begin
UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, UTF8ToAnsi(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row,
CurCell^.Col)) CurCell^.Col))
); );
if Length(CurCell^.RPNFormulaValue) > 0 then if HasFormula(CurCell) then
WriteLn(' Formula: ', MyWorkSheet.ReadRPNFormulaAsString(CurCell)) WriteLn(' Formula: ', MyWorkSheet.ReadFormulaAsString(CurCell))
else else
WriteLn; WriteLn;
CurCell := MyWorkSheet.GetNextCell(); CurCell := MyWorkSheet.GetNextCell();

View File

@ -15,64 +15,8 @@
<VersionInfo> <VersionInfo>
<StringTable ProductVersion=""/> <StringTable ProductVersion=""/>
</VersionInfo> </VersionInfo>
<BuildModes Count="3"> <BuildModes Count="1">
<Item1 Name="default" Default="True"/> <Item1 Name="default" Default="True"/>
<Item2 Name="Debug">
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<SearchPaths>
<OtherUnitFiles Value=".."/>
<SrcPath Value=".."/>
</SearchPaths>
<Parsing>
<SyntaxOptions>
<IncludeAssertionCode Value="True"/>
<UseAnsiStrings Value="False"/>
</SyntaxOptions>
</Parsing>
<CodeGeneration>
<Checks>
<IOChecks Value="True"/>
<RangeChecks Value="True"/>
<OverflowChecks Value="True"/>
<StackChecks Value="True"/>
</Checks>
</CodeGeneration>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions>
</Item2>
<Item3 Name="Release">
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<SearchPaths>
<OtherUnitFiles Value=".."/>
<SrcPath Value=".."/>
</SearchPaths>
<Parsing>
<SyntaxOptions>
<UseAnsiStrings Value="False"/>
</SyntaxOptions>
</Parsing>
<CodeGeneration>
<SmartLinkUnit Value="True"/>
<Optimizations>
<OptimizationLevel Value="3"/>
</Optimizations>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
</Debugging>
<LinkSmart Value="True"/>
</Linking>
</CompilerOptions>
</Item3>
</BuildModes> </BuildModes>
<PublishOptions> <PublishOptions>
<Version Value="2"/> <Version Value="2"/>
@ -88,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="laz_fpspreadsheet"/> <PackageName Value="LazUtils"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">
@ -101,8 +45,12 @@
<CompilerOptions> <CompilerOptions>
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<Target>
<Filename Value="excel8write"/>
</Target>
<SearchPaths> <SearchPaths>
<OtherUnitFiles Value=".."/> <OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>
@ -111,7 +59,7 @@
</Parsing> </Parsing>
<Linking> <Linking>
<Debugging> <Debugging>
<DebugInfoType Value="dsDwarf2Set"/> <UseExternalDbgSyms Value="True"/>
</Debugging> </Debugging>
</Linking> </Linking>
</CompilerOptions> </CompilerOptions>

View File

@ -10,8 +10,7 @@ program excel8write;
{$mode delphi}{$H+} {$mode delphi}{$H+}
uses uses
Classes, SysUtils, fpspreadsheet, xlsbiff8, Classes, SysUtils, fpspreadsheet, xlsbiff8;
laz_fpspreadsheet;
const const
Str_First = 'First'; Str_First = 'First';
@ -41,6 +40,7 @@ begin
MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8)); MyWorkbook.UsePalette(@PALETTE_BIFF8, Length(PALETTE_BIFF8));
MyWorkbook.FormatSettings.CurrencyFormat := 2; MyWorkbook.FormatSettings.CurrencyFormat := 2;
MyWorkbook.FormatSettings.NegCurrFormat := 14; MyWorkbook.FormatSettings.NegCurrFormat := 14;
MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving];
MyWorksheet := MyWorkbook.AddWorksheet(Str_Worksheet1); MyWorksheet := MyWorkbook.AddWorksheet(Str_Worksheet1);
MyWorksheet.Options := MyWorksheet.Options - [soShowGridLines]; MyWorksheet.Options := MyWorksheet.Options - [soShowGridLines];
@ -147,28 +147,27 @@ begin
end; end;
} }
// Write the formula E1 = A1 + B1 // Write the string formula E1 = A1 + B1 ...
SetLength(MyRPNFormula, 3); MyWorksheet.WriteFormula(0, 4, 'A1+B1');
MyRPNFormula[0].ElementKind := fekCell; // ... and the rpn formula E2 = A1 + B1
MyRPNFormula[0].Col := 0; MyWorksheet.WriteRPNFormula(1, 4, CreateRPNFormula(
MyRPNFormula[0].Row := 0; RPNCellValue('A1',
MyRPNFormula[1].ElementKind := fekCell; RPNCellValue('B1',
MyRPNFormula[1].Col := 1; RPNFunc(fekAdd,
MyRPNFormula[1].Row := 0; nil)))));
MyRPNFormula[2].ElementKind := fekAdd;
MyWorksheet.WriteRPNFormula(0, 4, MyRPNFormula);
MyWorksheet.WriteFont(0, 4, 'Arial', 10, [fssUnderline], scBlack);
// Write the formula F1 = ABS(A1) // Write the formula F1 = ABS(A1) as string formula ...
SetLength(MyRPNFormula, 2); MyWorksheet.WriteFormula(0, 5, 'ABS(A1)');
MyRPNFormula[0].ElementKind := fekCell; // ... and F2 = ABS(A1) as rpn formula
MyRPNFormula[0].Col := 0; MyWorksheet.WriteRPNFormula(1, 5, CreateRPNFormula(
MyRPNFormula[0].Row := 0; RPNCellValue('A1',
MyRPNFormula[1].ElementKind := fekABS; RPNFunc('ABS',
MyWorksheet.WriteRPNFormula(0, 5, MyRPNFormula); nil))));
// Write a string formula to G1 = "A" & "B" // Write a string formula to G1 = "A" & "B" ...
MyWorksheet.WriteRPNFormula(0, 6, CreateRPNFormula( MyWorksheet.WriteFormula(0, 6, '"A"&"B"');
// ... and again as rpn formula
MyWorksheet.WriteRPNFormula(1, 6, CreateRPNFormula(
RPNString('A', RPNString('A',
RPNSTring('B', RPNSTring('B',
RPNFunc(fekConcat, RPNFunc(fekConcat,

View File

@ -56,7 +56,6 @@
<SearchPaths> <SearchPaths>
<OtherUnitFiles Value="..\.."/> <OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
<SrcPath Value=".."/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>

View File

@ -35,6 +35,7 @@ begin
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas];
MyWorkbook.ReadFromFile(InputFilename, sfOOXML); MyWorkbook.ReadFromFile(InputFilename, sfOOXML);
MyWorksheet := MyWorkbook.GetFirstWorksheet; MyWorksheet := MyWorkbook.GetFirstWorksheet;

View File

@ -57,10 +57,5 @@
<UseAnsiStrings Value="False"/> <UseAnsiStrings Value="False"/>
</SyntaxOptions> </SyntaxOptions>
</Parsing> </Parsing>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions> </CompilerOptions>
</CONFIG> </CONFIG>

View File

@ -40,6 +40,11 @@ begin
MyWorksheet.WriteColWidth(0, 20); MyWorksheet.WriteColWidth(0, 20);
MyWorksheet.WriteRowHeight(0, 4); MyWorksheet.WriteRowHeight(0, 4);
// Write some formulas
Myworksheet.WriteFormula(0, 5, '=A1-B1');
Myworksheet.WriteFormula(0, 6, '=SUM(A1:D1)');
MyWorksheet.WriteFormula(0, 7, '=SIN(A1+B1)');
// Uncomment this to test large XLS files // Uncomment this to test large XLS files
for i := 2 to 2{20} do for i := 2 to 2{20} do
begin begin

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LazUtils"/> <PackageName Value="laz_fpspreadsheet"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">
@ -51,16 +51,12 @@
<SearchPaths> <SearchPaths>
<OtherUnitFiles Value="..\.."/> <OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
<SrcPath Value=".."/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>
<UseAnsiStrings Value="False"/> <UseAnsiStrings Value="False"/>
</SyntaxOptions> </SyntaxOptions>
</Parsing> </Parsing>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions> </CompilerOptions>
</CONFIG> </CONFIG>

View File

@ -11,7 +11,8 @@ program opendocread;
{$mode delphi}{$H+} {$mode delphi}{$H+}
uses uses
Classes, SysUtils, fpspreadsheet, fpsallformats; Classes, SysUtils, fpspreadsheet, fpsallformats,
laz_fpspreadsheet;
var var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LazUtils"/> <PackageName Value="laz_fpspreadsheet"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">
@ -45,22 +45,14 @@
<CompilerOptions> <CompilerOptions>
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<Target>
<Filename Value="opendocwrite"/>
</Target>
<SearchPaths> <SearchPaths>
<OtherUnitFiles Value="..\.."/> <OtherUnitFiles Value=".."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <SrcPath Value=".."/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>
<UseAnsiStrings Value="False"/> <UseAnsiStrings Value="False"/>
</SyntaxOptions> </SyntaxOptions>
</Parsing> </Parsing>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions> </CompilerOptions>
</CONFIG> </CONFIG>

View File

@ -10,7 +10,8 @@ program opendocwrite;
{$mode delphi}{$H+} {$mode delphi}{$H+}
uses uses
Classes, SysUtils, fpspreadsheet, fpsallformats; Classes, SysUtils, fpspreadsheet, fpsallformats,
laz_fpspreadsheet;
var var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;

View File

@ -10,14 +10,6 @@ uses
{ you can add units after this }, { you can add units after this },
TypInfo, fpSpreadsheet, fpsUtils, fpsExprParser; TypInfo, fpSpreadsheet, fpsUtils, fpsExprParser;
function Prepare(AFormula: String): String;
begin
if (AFormula <> '') and (AFormula[1] = '=') then
Result := Copy(AFormula, 2, Length(AFormula)-1)
else
Result := AFormula;
end;
var var
workbook: TsWorkbook; workbook: TsWorkbook;
worksheet: TsWorksheet; worksheet: TsWorksheet;
@ -26,55 +18,89 @@ var
res: TsExpressionResult; res: TsExpressionResult;
formula: TsRPNFormula; formula: TsRPNFormula;
i: Integer; i: Integer;
s: String;
begin begin
workbook := TsWorkbook.Create; workbook := TsWorkbook.Create;
try try
worksheet := workbook.AddWorksheet('Test'); worksheet := workbook.AddWorksheet('Test');
{
worksheet.WriteNumber(0, 0, 2); // A1 worksheet.WriteNumber(0, 0, 1); // A1
worksheet.WriteNumber(0, 1, 2.5); // B1 worksheet.WriteNumber(0, 1, 2.5); // B1
}
{
worksheet.WriteUTF8Text(0, 0, 'Hallo'); // A1 worksheet.WriteUTF8Text(0, 0, 'Hallo'); // A1
worksheet.WriteUTF8Text(0, 1, 'World'); // B1 worksheet.WriteUTF8Text(0, 1, 'World'); // B1
}
//cell := worksheet.WriteFormula(1, 0, '=4+5'); // A2
//cell := worksheet.WriteFormula(1, 0, 'AND(TRUE(), TRUE(), TRUE())');
//cell := worksheet.WriteFormula(1, 0, 'SIN(A1+B1)');
//cell := worksheet.WriteFormula(1, 0, '=TRUE()');
//cell := worksheet.WriteFormula(1, 0, '=1-(4/2)^2*2-1'); // A2
//cell := Worksheet.WriteFormula(1, 0, 'datedif(today(),Date(2014,1,1),"D")');
//cell := Worksheet.WriteFormula(1, 0, 'Day(Date(2014, 1, 12))');
//cell := Worksheet.WriteFormula(1, 0, 'SUM(1,2,3)');
//cell := Worksheet.WriteFormula(1, 0, 'CELL("address",A1)');
cell := Worksheet.WriteFormula(1, 0, 'ISBLANK(A1)');
//cell := worksheet.WriteFormula(1, 0, '=(A1+2)*3'); // A2 WriteLn('A1: ', worksheet.ReadAsUTF8Text(0, 0));
cell := worksheet.WriteFormula(1, 0, 'A1&" "&B1'); WriteLn('B1: ', worksheet.ReadAsUTF8Text(0, 1));
WriteLn('A1 = ', worksheet.ReadAsUTF8Text(0, 0)); parser := TsSpreadsheetParser.Create(worksheet);
WriteLn('B1 = ', worksheet.ReadAsUTF8Text(0, 1));
parser := TsExpressionParser.Create(worksheet);
try try
parser.Builtins := [bcStrings, bcDateTime, bcMath, bcBoolean, bcConversion, bcData, try
bcVaria, bcUser]; parser.Expression := cell^.FormulaValue;
parser.Expression := Prepare(cell^.FormulaValue.FormulaStr); res := parser.Evaluate;
res := parser.Evaluate;
Write('A2 = ', Prepare(cell^.FormulaValue.FormulaStr), ' = '); WriteLn('A2: ', parser.Expression);
case res.ResultType of Write('Result: ');
rtBoolean : WriteLn(BoolToStr(res.ResBoolean)); case res.ResultType of
rtFloat : WriteLn(FloatToStr(res.ResFloat)); rtEmpty : WriteLn('--- empty ---');
rtInteger : WriteLn(IntToStr(res.ResInteger)); rtBoolean : WriteLn(BoolToStr(res.ResBoolean, true));
rtDateTime : WriteLn(FormatDateTime('c', res.ResDateTime)); rtFloat : WriteLn(FloatToStr(res.ResFloat));
rtString : WriteLn(res.ResString); rtInteger : WriteLn(IntToStr(res.ResInteger));
end; rtDateTime : WriteLn(FormatDateTime('c', res.ResDateTime));
rtString : WriteLn(res.ResString);
WriteLn('Reconstructed string formula: ', parser.BuildFormula); rtError : WriteLn(GetErrorValueStr(res.ResError));
WriteLn('RPN formula:');
formula := parser.BuildRPNFormula;
for i:=0 to Length(formula)-1 do begin
Write(' Item ', i, ': token ', GetEnumName(TypeInfo(TFEKind), ord(formula[i].ElementKind)));
case formula[i].ElementKind of
fekCell : Write(' / cell: ' +GetCellString(formula[i].Row, formula[i].Col, formula[i].RelFlags));
fekNum : Write(' / number value: ', FloatToStr(formula[i].DoubleValue));
fekString : Write(' / string value: "', formula[i].StringValue, '"');
fekBool : Write(' / boolean value: ', BoolToStr(formula[i].DoubleValue <> 0));
end; end;
WriteLn;
WriteLn('Reconstructed string formula: ', parser.BuildStringFormula);
formula := parser.RPNFormula;
for i:=0 to Length(formula)-1 do begin
Write(' Item ', i, ': token ', GetEnumName(TypeInfo(TFEKind), ord(formula[i].ElementKind)), ' ', formula[i].FuncName);
case formula[i].ElementKind of
fekCell : Write(' / cell: ' +GetCellString(formula[i].Row, formula[i].Col, formula[i].RelFlags));
fekNum : Write(' / float value: ', FloatToStr(formula[i].DoubleValue));
fekInteger : Write(' / integer value: ', IntToStr(formula[i].IntValue));
fekString : Write(' / string value: "', formula[i].StringValue, '"');
fekBool : Write(' / boolean value: ', BoolToStr(formula[i].DoubleValue <> 0, true));
end;
WriteLn;
end;
finally
parser.Free;
end; end;
except on E:Exception do
begin
WriteLn('Parser/calculation error: ', E.Message);
raise;
end;
end;
parser := TsSpreadsheetParser.Create(worksheet);
try
try
parser.RPNFormula := formula;
s := parser.BuildStringFormula;
WriteLn('String formula, reconstructed from RPN formula: ', s);
except on E:Exception do
begin
WriteLn('RPN/string formula conversion error: ', E.Message);
raise;
end;
end;
finally finally
parser.Free; parser.Free;
end; end;
@ -82,5 +108,6 @@ begin
finally finally
workbook.Free; workbook.Free;
end; end;
end. end.

View File

@ -46,6 +46,7 @@
<Unit1> <Unit1>
<Filename Value="financemath.pas"/> <Filename Value="financemath.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="financemath"/>
</Unit1> </Unit1>
</Units> </Units>
</ProjectOptions> </ProjectOptions>

View File

@ -8,7 +8,7 @@
- PMT() (payment) - PMT() (payment)
- NPER() (number of payment periods) - NPER() (number of payment periods)
The demo writes an xls file which uses these formulas and then displays The demo writes a spreadsheet file which uses these formulas and then displays
the result in a console window. (Open the generated file in Excel or the result in a console window. (Open the generated file in Excel or
Open/LibreOffice and compare). Open/LibreOffice and compare).
} }
@ -24,117 +24,118 @@ uses
{$ENDIF} {$ENDIF}
{$ENDIF} {$ENDIF}
Classes, SysUtils, Classes, SysUtils,
math, fpspreadsheet, xlsbiff8, fpsfunc, financemath; math, fpspreadsheet, fpsallformats, fpsexprparser, financemath;
{ Base data used in this demonstration }
const
INTEREST_RATE = 0.03; // interest rate per period
NUMBER_PAYMENTS = 10; // number of payment periods
REG_PAYMENT = 1000; // regular payment per period
PRESENT_VALUE = 10000.0; // present value of investment
PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ Adaption of financial functions to usage by fpspreadsheet } { Adaption of financial functions to usage by fpspreadsheet }
{ The functions are implemented in the unit "financemath.pas". } { The functions are implemented in the unit "financemath.pas". }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
function fpsFV(Args: TsArgumentStack; NumArgs: Integer): TsArgument; procedure fpsFV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
data: TsArgNumberArray;
begin begin
// Pop the argument from the stack. This can be done by means of PopNumberValues Result.ResFloat := FutureValue(
// which brings the values back in the right order and reports an error ArgToFloat(Args[0]), // interest rate
// in case of non-numerical values. ArgToInt(Args[1]), // number of payments
if Args.PopNumberValues(NumArgs, false, data, Result) then ArgToFloat(Args[2]), // payment
// Call the FutureValue function with the NumberValues of the arguments. ArgToFloat(Args[3]), // present value
Result := CreateNumberArg(FutureValue( TPaymentTime(ArgToInt(Args[4])) // payment type
data[0], // interest rate );
round(data[1]), // number of payments
data[2], // payment
data[3], // present value
TPaymentTime(round(data[4])) // payment type
));
end; end;
function fpsPMT(Args: TsArgumentStack; NumArgs: Integer): TsArgument; procedure fpsPMT(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
data: TsArgNumberArray;
begin begin
if Args.PopNumberValues(NumArgs, false, data, Result) then Result.ResFloat := Payment(
Result := CreateNumberArg(Payment( ArgToFloat(Args[0]), // interest rate
data[0], // interest rate ArgToInt(Args[1]), // number of payments
round(data[1]), // number of payments ArgToFloat(Args[2]), // present value
data[2], // present value ArgToFloat(Args[3]), // future value
data[3], // future value TPaymentTime(ArgToInt(Args[4])) // payment type
TPaymentTime(round(data[4])) // payment type );
));
end; end;
function fpsPV(Args: TsArgumentStack; NumArgs: Integer): TsArgument; procedure fpsPV(var Result: TsExpressionResult; const Args: TsExprParameterArray);
// Present value
var
data: TsArgNumberArray;
begin begin
if Args.PopNumberValues(NumArgs, false, data, Result) then Result.ResFloat := PresentValue(
Result := CreateNumberArg(PresentValue( ArgToFloat(Args[0]), // interest rate
data[0], // interest rate ArgToInt(Args[1]), // number of payments
round(data[1]), // number of payments ArgToFloat(Args[2]), // payment
data[2], // payment ArgToFloat(Args[3]), // future value
data[3], // future value TPaymentTime(ArgToInt(Args[4])) // payment type
TPaymentTime(round(data[4])) // payment type );
));
end; end;
function fpsNPER(Args: TsArgumentStack; NumArgs: Integer): TsArgument; procedure fpsNPER(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
data: TsArgNumberArray;
begin begin
if Args.PopNumberValues(NumArgs, false, data, Result) then Result.ResFloat := NumberOfPeriods(
Result := CreateNumberArg(NumberOfPeriods( ArgToFloat(Args[0]), // interest rate
data[0], // interest rate ArgToFloat(Args[1]), // payment
data[1], // payment ArgToFloat(Args[2]), // present value
data[2], // present value ArgToFloat(Args[3]), // future value
data[3], // future value TPaymentTime(ArgToInt(Args[4])) // payment type
TPaymentTime(round(data[4])) // payment type );
));
end; end;
function fpsRATE(Args: TsArgumentStack; NumArgs: Integer): TsArgument; procedure fpsRATE(var Result: TsExpressionResult; const Args: TsExprParameterArray);
var
data: TsArgNumberArray;
begin begin
if Args.PopNumberValues(NumArgs, false, data, Result) then Result.ResFloat := InterestRate(
Result := CreateNumberArg(InterestRate( ArgToInt(Args[0]), // number of payments
round(data[0]), // number of payment periods ArgToFloat(Args[1]), // payment
data[1], // payment ArgToFloat(Args[2]), // present value
data[2], // present value ArgToFloat(Args[3]), // future value
data[3], // future value TPaymentTime(ArgToInt(Args[4])) // payment type
TPaymentTime(round(data[4])) // payment type );
));
end; end;
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ Write xls file comparing our own calculations with Excel result } { Write xls file comparing our own calculations with Excel result }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
procedure WriteFile(AFileName: String); procedure WriteFile(AFileName: String);
const const
INTEREST_RATE = 0.03; // interest rate per period INT_EXCEL_SHEET_FUNC_PV = 56;
NUMBER_PAYMENTS = 10; // number of payment periods INT_EXCEL_SHEET_FUNC_FV = 57;
REG_PAYMENT = 1000; // regular payment per period INT_EXCEL_SHEET_FUNC_NPER = 58;
PRESENT_VALUE = 10000; // present value of investment INT_EXCEL_SHEET_FUNC_PMT = 59;
PAYMENT_WHEN: TPaymentTime = ptEndOfPeriod; // when is the payment made INT_EXCEL_SHEET_FUNC_RATE = 60;
var var
workbook: TsWorkbook; workbook: TsWorkbook;
worksheet: TsWorksheet; worksheet: TsWorksheet;
fval, pval, pmtval, nperval, rateval: Double; fval, pval, pmtval, nperval, rateval: Double;
formula: String;
fs: TFormatSettings;
begin begin
{ We have to register our financial functions in fpspreadsheet. Otherwise an { We have to register our financial functions in fpspreadsheet. Otherwise an
error code would be displayed in the reading part of this demo for these error code would be displayed in the reading part of this demo for these
formula cells. } formula cells.
RegisterFormulaFunc(fekFV, @fpsFV); The 1st parameter is the data type of the function result ('F'=float)
RegisterFormulaFunc(fekPMT, @fpsPMT); The 2nd parameter shows the data types of the arguments ('F=float, 'I'=integer)
RegisterFormulaFunc(fekPV, @fpsPV); The 3rd parameter is the Excel ID needed when writing to xls files. (see
RegisterFormulaFunc(fekNPER, @fpsNPER); "OpenOffice Documentation of Microsoft Excel File Format", section 3.11)
RegisterFormulaFunc(fekRATE, @fpsRATE); The 4th parameter is the address of the function to be used for calculation. }
RegisterFunction('FV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_FV, @fpsFV);
RegisterFunction('PMT', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PMT, @fpsPMT);
RegisterFunction('PV', 'F', 'FIFFI', INT_EXCEL_SHEET_FUNC_PV, @fpsPV);
RegisterFunction('NPER', 'F', 'FFFFI', INT_EXCEL_SHEET_FUNC_NPER, @fpsNPER);
RegisterFunction('RATE', 'F', 'IFFFI', INT_EXCEL_SHEET_FUNC_RATE, @fpsRATE);
// The formula parser requires a point as decimals separator.
fs := DefaultFormatSettings;
fs.DecimalSeparator := '.';
workbook := TsWorkbook.Create; workbook := TsWorkbook.Create;
try try
workbook.Options := workbook.Options + [boCalcBeforeSaving]; //workbook.Options := workbook.Options + [boCalcBeforeSaving];
worksheet := workbook.AddWorksheet('Financial'); worksheet := workbook.AddWorksheet('Financial');
worksheet.WriteColWidth(0, 40); worksheet.WriteColWidth(0, 40);
@ -167,24 +168,14 @@ begin
worksheet.WriteUTF8Text(9, 0, 'Worksheet calculation using constants'); worksheet.WriteUTF8Text(9, 0, 'Worksheet calculation using constants');
worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(9, 1, CreateRPNFormula( worksheet.WriteNumberFormat(9, 1, nfCurrency, 2, '$');
RPNNumber(INTEREST_RATE, formula := Format('FV(%f,%d,%f,%f,%d)',
RPNNumber(NUMBER_PAYMENTS, [1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, ord(PAYMENT_WHEN)], fs
RPNNumber(REG_PAYMENT, );
RPNNumber(PRESENT_VALUE, worksheet.WriteFormula(9, 1, formula);
RPNNumber(ord(PAYMENT_WHEN),
RPNFunc(fekFV, 5,
nil))))))));
worksheet.WriteUTF8Text(10, 0, 'Worksheet calculation using cell values'); worksheet.WriteUTF8Text(10, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(10, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(10, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(10, 1, CreateRPNFormula( worksheet.WriteFormula(10, 1, 'FV(B2,B3,B4,B5,B6)');
RPNCellValue('B2', // interest rate
RPNCellValue('B3', // number of periods
RPNCellValue('B4', // payment
RPNCellValue('B5', // present value
RPNCellValue('B6', // payment at end or at start
RPNFunc(fekFV, 5, // Call Excel's FV formula
nil))))))));
// present value calculation // present value calculation
pval := PresentValue(INTEREST_RATE, NUMBER_PAYMENTS, REG_PAYMENT, fval, PAYMENT_WHEN); pval := PresentValue(INTEREST_RATE, NUMBER_PAYMENTS, REG_PAYMENT, fval, PAYMENT_WHEN);
@ -194,25 +185,14 @@ begin
worksheet.WriteCurrency(13, 1, pval, nfCurrency, 2, '$'); worksheet.WriteCurrency(13, 1, pval, nfCurrency, 2, '$');
worksheet.WriteUTF8Text(14, 0, 'Worksheet calculation using constants'); worksheet.WriteUTF8Text(14, 0, 'Worksheet calculation using constants');
formula := Format('PV(%f,%d,%f,%f,%d)',
[1.0*INTEREST_RATE, NUMBER_PAYMENTS, 1.0*REG_PAYMENT, fval, ord(PAYMENT_WHEN)], fs
);
worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(14, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(14, 1, CreateRPNFormula( worksheet.WriteFormula(14, 1, formula);
RPNNumber(INTEREST_RATE,
RPNNumber(NUMBER_PAYMENTS,
RPNNumber(REG_PAYMENT,
RPNNumber(fval,
RPNNumber(ord(PAYMENT_WHEN),
RPNFunc(fekPV, 5,
nil))))))));
Worksheet.WriteUTF8Text(15, 0, 'Worksheet calculation using cell values'); Worksheet.WriteUTF8Text(15, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(15, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(15, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(15, 1, CreateRPNFormula( worksheet.WriteFormula(15, 1, 'PV(B2,B3,B4,B11,B6)');
RPNCellValue('B2', // interest rate
RPNCellValue('B3', // number of periods
RPNCellValue('B4', // payment
RPNCellValue('B11', // future value
RPNCellValue('B6', // payment at end or at start
RPNFunc(fekPV, 5, // Call Excel's PV formula
nil))))))));
// payments calculation // payments calculation
pmtval := Payment(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN); pmtval := Payment(INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, PAYMENT_WHEN);
@ -223,24 +203,13 @@ begin
worksheet.WriteUTF8Text(19, 0, 'Worksheet calculation using constants'); worksheet.WriteUTF8Text(19, 0, 'Worksheet calculation using constants');
worksheet.WriteNumberFormat(19, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(19, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(19, 1, CreateRPNFormula( formula := Format('PMT(%g,%d,%g,%g,%d)',
RPNNumber(INTEREST_RATE, [INTEREST_RATE, NUMBER_PAYMENTS, PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
RPNNumber(NUMBER_PAYMENTS, );
RPNNumber(PRESENT_VALUE, worksheet.WriteFormula(19, 1, formula);
RPNNumber(fval,
RPNNumber(ord(PAYMENT_WHEN),
RPNFunc(fekPMT, 5,
nil))))))));
Worksheet.WriteUTF8Text(20, 0, 'Worksheet calculation using cell values'); Worksheet.WriteUTF8Text(20, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(20, 1, nfCurrency, 2, '$'); worksheet.WriteNumberFormat(20, 1, nfCurrency, 2, '$');
worksheet.WriteRPNFormula(20, 1, CreateRPNFormula( worksheet.WriteFormula(20, 1, 'PMT(B2,B3,B5,B11,B6)');
RPNCellValue('B2', // interest rate
RPNCellValue('B3', // number of periods
RPNCellValue('B5', // present value
RPNCellValue('B11', // future value
RPNCellValue('B6', // payment at end or at start
RPNFunc(fekPMT, 5, // Call Excel's PMT formula
nil))))))));
// number of periods calculation // number of periods calculation
nperval := NumberOfPeriods(INTEREST_RATE, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN); nperval := NumberOfPeriods(INTEREST_RATE, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN);
@ -251,24 +220,13 @@ begin
worksheet.WriteUTF8Text(24, 0, 'Worksheet calculation using constants'); worksheet.WriteUTF8Text(24, 0, 'Worksheet calculation using constants');
worksheet.WriteNumberFormat(24, 1, nfFixed, 2); worksheet.WriteNumberFormat(24, 1, nfFixed, 2);
worksheet.WriteRPNFormula(24, 1, CreateRPNFormula( formula := Format('NPER(%g,%g,%g,%g,%d)',
RPNNumber(INTEREST_RATE, [1.0*INTEREST_RATE, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
RPNNumber(REG_PAYMENT, );
RPNNumber(PRESENT_VALUE, worksheet.WriteFormula(24, 1, formula);
RPNNumber(fval,
RPNNumber(ord(PAYMENT_WHEN),
RPNFunc(fekNPER, 5,
nil))))))));
Worksheet.WriteUTF8Text(25, 0, 'Worksheet calculation using cell values'); Worksheet.WriteUTF8Text(25, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(25, 1, nfFixed, 2); worksheet.WriteNumberFormat(25, 1, nfFixed, 2);
worksheet.WriteRPNFormula(25, 1, CreateRPNFormula( worksheet.WriteFormula(25, 1, 'NPER(B2,B4,B5,B11,B6)');
RPNCellValue('B2', // interest rate
RPNCellValue('B4', // payment
RPNCellValue('B5', // present value
RPNCellValue('B11', // future value
RPNCellValue('B6', // payment at end or at start
RPNFunc(fekNPER, 5, // Call Excel's PMT formula
nil))))))));
// interest rate calculation // interest rate calculation
rateval := InterestRate(NUMBER_PAYMENTS, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN); rateval := InterestRate(NUMBER_PAYMENTS, REG_PAYMENT, PRESENT_VALUE, fval, PAYMENT_WHEN);
@ -279,26 +237,15 @@ begin
worksheet.WriteUTF8Text(29, 0, 'Worksheet calculation using constants'); worksheet.WriteUTF8Text(29, 0, 'Worksheet calculation using constants');
worksheet.WriteNumberFormat(29, 1, nfPercentage, 2); worksheet.WriteNumberFormat(29, 1, nfPercentage, 2);
worksheet.WriteRPNFormula(29, 1, CreateRPNFormula( formula := Format('RATE(%d,%g,%g,%g,%d)',
RPNNumber(NUMBER_PAYMENTS, [NUMBER_PAYMENTS, 1.0*REG_PAYMENT, 1.0*PRESENT_VALUE, fval, ord(PAYMENT_WHEN)], fs
RPNNumber(REG_PAYMENT, );
RPNNumber(PRESENT_VALUE, worksheet.WriteFormula(29, 1, formula);
RPNNumber(fval,
RPNNumber(ord(PAYMENT_WHEN),
RPNFunc(fekRATE, 5,
nil))))))));
Worksheet.WriteUTF8Text(30, 0, 'Worksheet calculation using cell values'); Worksheet.WriteUTF8Text(30, 0, 'Worksheet calculation using cell values');
worksheet.WriteNumberFormat(30, 1, nfPercentage, 2); worksheet.WriteNumberFormat(30, 1, nfPercentage, 2);
worksheet.WriteRPNFormula(30, 1, CreateRPNFormula( worksheet.WriteFormula(30, 1, 'RATE(B3,B4,B5,B11,B6)');
RPNCellValue('B3', // number of payments
RPNCellValue('B4', // payment
RPNCellValue('B5', // present value
RPNCellValue('B11', // future value
RPNCellValue('B6', // payment at end or at start
RPNFunc(fekRATE, 5, // Call Excel's PMT formula
nil))))))));
workbook.WriteToFile(AFileName, sfExcel8, true); workbook.WriteToFile(AFileName, true);
finally finally
workbook.Free; workbook.Free;
@ -317,9 +264,8 @@ var
begin begin
workbook := TsWorkbook.Create; workbook := TsWorkbook.Create;
try try
workbook.Options := workbook.Options + [boReadFormulas]; workbook.Options := workbook.Options + [boReadFormulas, boAutoCalc];
workbook.ReadFromFile(AFilename, sfExcel8); workbook.ReadFromFile(AFilename);
worksheet := workbook.GetFirstWorksheet; worksheet := workbook.GetFirstWorksheet;
// Write all cells with contents to the console // Write all cells with contents to the console
@ -340,19 +286,27 @@ begin
WriteLn(s1+': ':50, s2); WriteLn(s1+': ':50, s2);
end; end;
WriteLn;
WriteLn('Press [ENTER] to close...');
ReadLn;
finally finally
workbook.Free; workbook.Free;
end; end;
end; end;
const const
TestFile='test_fv.xls'; TestFile='test_user_formula.xlsx'; // Format depends on extension selected
// !!!! ods not working yet !!!!
begin begin
WriteLn('This demo registers user-defined functions for financial calculations');
WriteLn('and writes and reads the corresponding spreadsheet file.');
WriteLn;
WriteFile(TestFile); WriteFile(TestFile);
ReadFile(TestFile); ReadFile(TestFile);
WriteLn;
WriteLn('Open the file in Excel or OpenOffice/LibreOffice.');
WriteLn('Press [ENTER] to close...');
ReadLn;
end. end.

View File

@ -21,7 +21,7 @@ var
workbook: TsWorkbook; workbook: TsWorkbook;
worksheet: TsWorksheet; worksheet: TsWorksheet;
const const
OutputFile='test_calc.xls'; OutputFile='test_recursive.xls';
begin begin
writeln('Starting program.'); writeln('Starting program.');
@ -35,26 +35,33 @@ begin
// A1 // A1
worksheet.WriteUTF8Text(0, 0, '=B2+1'); worksheet.WriteUTF8Text(0, 0, '=B2+1');
// B1 // B1
worksheet.WriteFormula(0, 1, 'B2+1');
{
worksheet.WriteRPNFormula(0, 1, CreateRPNFormula( worksheet.WriteRPNFormula(0, 1, CreateRPNFormula(
RPNCellValue('B2', RPNCellValue('B2',
RPNNumber(1, RPNInteger(1,
RPNFunc(fekAdd, nil))))); RPNFunc(fekAdd, nil)))));
}
// A2 // A2
worksheet.WriteUTF8Text(1, 0, '=B3+1'); worksheet.WriteUTF8Text(1, 0, '=B3+1');
// B2 // B2
worksheet.WriteFormula(1, 1, 'B3+1');
{
worksheet.WriteRPNFormula(1, 1, CreateRPNFormula( worksheet.WriteRPNFormula(1, 1, CreateRPNFormula(
RPNCellValue('B3', RPNCellValue('B3',
RPNNumber(1, RPNInteger(1,
RPNFunc(fekAdd, nil))))); RPNFunc(fekAdd, nil)))));
}
// A3 // A3
worksheet.WriteUTF8Text(2, 0, '(not dependent)'); worksheet.WriteUTF8Text(2, 0, '(not dependent)');
// B3 // B3
worksheet.WriteNumber(2, 1, 1); worksheet.WriteNumber(2, 1, 1);
workbook.WriteToFile(OutputFile, sfExcel8, true); workbook.WriteToFile(OutputFile, sfExcel8, true);
writeln('Finished. Please open "'+OutputFile+'" in your spreadsheet program.'); writeln('Finished.');
writeln;
writeln('Please open "'+OutputFile+'" in "fpsgrid".');
writeLn('It should show calculation results in cells B1 and B2.');
finally finally
workbook.Free; workbook.Free;
end; end;

View File

@ -97,9 +97,9 @@ begin
{ In case of a database, you would open the dataset before calling this: } { In case of a database, you would open the dataset before calling this: }
t := Now; t := Now;
workbook.WriteToFile('test_virtual.ods', sfOpenDocument, true); //workbook.WriteToFile('test_virtual.ods', sfOpenDocument, true);
//workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true); //workbook.WriteToFile('test_virtual.xlsx', sfOOXML, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel8, true); workbook.WriteToFile('test_virtual.xls', sfExcel8, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel5, true); //workbook.WriteToFile('test_virtual.xls', sfExcel5, true);
//workbook.WriteToFile('test_virtual.xls', sfExcel2, true); //workbook.WriteToFile('test_virtual.xls', sfExcel2, true);
t := Now - t; t := Now - t;

View File

@ -21,7 +21,7 @@ var
procedure WriteFirstWorksheet(); procedure WriteFirstWorksheet();
var var
MyFormula: TsFormula; MyFormula: String;
MyRPNFormula: TsRPNFormula; MyRPNFormula: TsRPNFormula;
MyCell: PCell; MyCell: PCell;
begin begin
@ -37,40 +37,41 @@ begin
Myworksheet.WriteNumber(3, 4, 300); // E4 Myworksheet.WriteNumber(3, 4, 300); // E4
MyWorksheet.WriteNumber(4, 4, 250); // E5 MyWorksheet.WriteNumber(4, 4, 250); // E5
// =Sum(E2:e5) // =Sum(E2:E5)
MyWorksheet.WriteUTF8Text(1, 0, '=Sum(E2:e5)'); // A2 MyWorksheet.WriteUTF8Text(1, 0, '=Sum(E2:E5)'); // A2
// MyFormula := '=Sum(E2:E5)';
MyFormula.FormulaStr := '=Sum(E2:e5)';
MyFormula.DoubleValue := 0.0;
MyWorksheet.WriteFormula(1, 1, MyFormula); // B2 MyWorksheet.WriteFormula(1, 1, MyFormula); // B2
//
MyWorksheet.WriteRPNFormula(1, 2, CreateRPNFormula( // C2 MyWorksheet.WriteRPNFormula(1, 2, CreateRPNFormula( // C2
RPNCellRange('E2:E5', RPNCellRange('E2:E5',
RPNFunc(fekSum, 1, nil)))); RPNFunc('SUM', 1,
nil))));
// Write the formula =ABS(E1) // Write the formula =ABS(E1)
MyWorksheet.WriteUTF8Text(2, 0, '=ABS(E1)'); // A3 MyWorksheet.WriteUTF8Text(2, 0, '=ABS(E1)'); // A3
// MyWorksheet.WriteFormula(2, 1, 'ABS(E1)'); // B3
MyWorksheet.WriteRPNFormula(2, 2, CreateRPNFormula( // C3 MyWorksheet.WriteRPNFormula(2, 2, CreateRPNFormula( // C3
RPNCellValue('E1', RPNCellValue('E1',
RPNFunc(fekAbs, nil)))); RPNFunc('ABS',
nil))));
// Write the formula =4+5 // Write the formula =4+5
MyWorksheet.WriteUTF8Text(3, 0, '=4+5'); // A4 MyWorksheet.WriteUTF8Text(3, 0, '=4+5'); // A4
// MyWorksheet.WriteFormula(3, 1, '=4+5'); // B4
MyWorksheet.WriteRPNFormula(3, 2, CreateRPNFormula( //C4 MyWorksheet.WriteRPNFormula(3, 2, CreateRPNFormula( //C4
RPNNumber(4.0, RPNNumber(4.0,
RPNNumber(5.0, RPNNumber(5.0,
RPNFunc(fekAdd, nil))))); RPNFunc(fekAdd,
nil)))));
(*
// Write a shared formula "=E1+100" to the cell range F1:F5 // Write a shared formula "=E1+100" to the cell range F1:F5
// Please note that shared formulas are not written by sfOOXML and sfOpenDocument formats. // Please note that shared formulas are not written by sfOOXML and sfOpenDocument formats.
MyCell := MyWorksheet.WriteRPNFormula(0, 5, CreateRPNFormula( MyCell := MyWorksheet.WriteRPNFormula(0, 5, CreateRPNFormula(
RPNCellOffset(0, -1, [rfRelRow, rfRelCol], RPNCellOffset(0, -1, [rfRelRow, rfRelCol],
RPNNumber(100, RPNNumber(100,
RPNFunc(fekAdd, nil))))); RPNFunc(fekAdd,
nil)))));
MyWorksheet.UseSharedFormula('F1:F5', MyCell); MyWorksheet.UseSharedFormula('F1:F5', MyCell);
*)
end; end;
procedure WriteSecondWorksheet(); procedure WriteSecondWorksheet();
@ -98,15 +99,17 @@ begin
// Create the spreadsheet // Create the spreadsheet
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
try
WriteFirstWorksheet();
WriteSecondWorksheet();
WriteFirstWorksheet(); // Save the spreadsheet to a file
MyWorkbook.WriteToFile(MyDir + TestFile, sfExcel8, True);
WriteSecondWorksheet(); finally
MyWorkbook.Free;
end;
// Save the spreadsheet to a file
MyWorkbook.WriteToFile(MyDir + TestFile, sfExcel8, True);
// MyWorkbook.WriteToFile(MyDir + 'test_formula.odt', sfOpenDocument, False);
MyWorkbook.Free;
writeln('Finished. Please open "'+Testfile+'" in your spreadsheet program.'); writeln('Finished. Please open "'+Testfile+'" in your spreadsheet program.');
end. end.

View File

@ -727,10 +727,15 @@ end;
procedure TForm1.EdFormulaEditingDone(Sender: TObject); procedure TForm1.EdFormulaEditingDone(Sender: TObject);
var var
r, c: Cardinal; r, c: Cardinal;
s: String;
begin begin
r := WorksheetGrid.GetWorksheetRow(WorksheetGrid.Row); r := WorksheetGrid.GetWorksheetRow(WorksheetGrid.Row);
c := WorksheetGrid.GetWorksheetCol(WorksheetGrid.Col); c := WorksheetGrid.GetWorksheetCol(WorksheetGrid.Col);
WorksheetGrid.Worksheet.WriteCellValueAsString(r, c, EdFormula.Text); s := EdFormula.Text;
if (s <> '') and (s[1] = '=') then
WorksheetGrid.Worksheet.WriteFormula(r, c, Copy(s, 2, Length(s)))
else
WorksheetGrid.Worksheet.WriteCellValueAsString(r, c, EdFormula.Text);
end; end;
procedure TForm1.EdFrozenColsChange(Sender: TObject); procedure TForm1.EdFrozenColsChange(Sender: TObject);
@ -895,8 +900,10 @@ begin
cell := WorksheetGrid.Worksheet.FindCell(r, c); cell := WorksheetGrid.Worksheet.FindCell(r, c);
if cell <> nil then begin if cell <> nil then begin
s := WorksheetGrid.Worksheet.ReadFormulaAsString(cell); s := WorksheetGrid.Worksheet.ReadFormulaAsString(cell);
if s <> '' then if s <> '' then begin
EdFormula.Text := s if s[1] <> '=' then s := '=' + s;
EdFormula.Text := s;
end
else else
case cell^.ContentType of case cell^.ContentType of
cctNumber: cctNumber:
@ -993,12 +1000,9 @@ begin
then Strings.Add('ErrorValue=') then Strings.Add('ErrorValue=')
else Strings.Add(Format('ErrorValue=%s', [ else Strings.Add(Format('ErrorValue=%s', [
GetEnumName(TypeInfo(TsErrorValue), ord(ACell^.ErrorValue)) ])); GetEnumName(TypeInfo(TsErrorValue), ord(ACell^.ErrorValue)) ]));
if (ACell=nil) or (Length(ACell^.RPNFormulaValue) = 0) if (ACell=nil) or (Length(ACell^.FormulaValue)=0)
then Strings.Add('RPNFormulaValue=') then Strings.Add('FormulaValue=')
else Strings.Add(Format('RPNFormulaValue=(%d tokens)', [Length(ACell^.RPNFormulaValue)])); else Strings.Add(Format('FormulaValue="%s"', [ACell^.FormulaValue]));
if (ACell=nil) or (Length(ACell^.FormulaValue.FormulaStr)=0)
then Strings.Add('FormulaValue.FormulaStr=')
else Strings.Add(Format('FormulaValue.FormulaStr="%s"', [ACell^.FormulaValue.FormulaStr]));
if (ACell=nil) or (ACell^.SharedFormulaBase=nil) if (ACell=nil) or (ACell^.SharedFormulaBase=nil)
then Strings.Add('SharedFormulaBase=') then Strings.Add('SharedFormulaBase=')
else Strings.Add(Format('SharedFormulaBase=%s', [GetCellString( else Strings.Add(Format('SharedFormulaBase=%s', [GetCellString(

View File

@ -31,7 +31,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LazUtils"/> <PackageName Value="laz_fpspreadsheet"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">
@ -44,23 +44,13 @@
<CompilerOptions> <CompilerOptions>
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<Target>
<Filename Value="wikitableread"/>
</Target>
<SearchPaths> <SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/> <IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="..\.."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>
<UseAnsiStrings Value="False"/> <UseAnsiStrings Value="False"/>
</SyntaxOptions> </SyntaxOptions>
</Parsing> </Parsing>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions> </CompilerOptions>
</CONFIG> </CONFIG>

View File

@ -11,7 +11,7 @@ program wikitableread;
uses uses
Classes, SysUtils, fpspreadsheet, wikitable, Classes, SysUtils, fpspreadsheet, wikitable,
fpsutils; laz_fpspreadsheet, fpsutils;
var var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;

View File

@ -32,7 +32,7 @@
</RunParams> </RunParams>
<RequiredPackages Count="1"> <RequiredPackages Count="1">
<Item1> <Item1>
<PackageName Value="LazUtils"/> <PackageName Value="laz_fpspreadsheet"/>
</Item1> </Item1>
</RequiredPackages> </RequiredPackages>
<Units Count="1"> <Units Count="1">
@ -45,22 +45,14 @@
<CompilerOptions> <CompilerOptions>
<Version Value="11"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<Target>
<Filename Value="wikitablewrite"/>
</Target>
<SearchPaths> <SearchPaths>
<OtherUnitFiles Value="..\.."/> <OtherUnitFiles Value=".."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <SrcPath Value=".."/>
</SearchPaths> </SearchPaths>
<Parsing> <Parsing>
<SyntaxOptions> <SyntaxOptions>
<UseAnsiStrings Value="False"/> <UseAnsiStrings Value="False"/>
</SyntaxOptions> </SyntaxOptions>
</Parsing> </Parsing>
<Linking>
<Debugging>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>
</CompilerOptions> </CompilerOptions>
</CONFIG> </CONFIG>

View File

@ -10,7 +10,8 @@ program wikitablewrite;
{$mode delphi}{$H+} {$mode delphi}{$H+}
uses uses
Classes, SysUtils, fpspreadsheet, wikitable; Classes, SysUtils, fpspreadsheet, wikitable,
laz_fpspreadsheet;
const const
Str_First = 'First'; Str_First = 'First';

File diff suppressed because it is too large Load Diff

View File

@ -2182,7 +2182,7 @@ begin
end; end;
{ Lookup / refernence functions } { Lookup / reference functions }
function fpsCOLUMN(Args: TsArgumentStack; NumArgs: Integer): TsArgument; function fpsCOLUMN(Args: TsArgumentStack; NumArgs: Integer): TsArgument;
{ COLUMN( [reference] ) { COLUMN( [reference] )

View File

@ -1272,7 +1272,7 @@ begin
// Read formula, store in the cell's FormulaValue.FormulaStr // Read formula, store in the cell's FormulaValue.FormulaStr
formula := GetAttrValue(ACellNode, 'table:formula'); formula := GetAttrValue(ACellNode, 'table:formula');
if formula <> '' then Delete(formula, 1, 3); // delete "of:" if formula <> '' then Delete(formula, 1, 3); // delete "of:"
cell^.FormulaValue.FormulaStr := formula; cell^.FormulaValue := formula;
// Read formula results // Read formula results
// ... number value // ... number value
@ -3590,7 +3590,7 @@ begin
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:formula="%s" %s>' + '<table:table-cell table:formula="%s" %s>' +
'</table:table-cell>', [ '</table:table-cell>', [
ACell^.FormulaValue.FormulaStr, lStyle ACell^.FormulaValue, lStyle
])); ]));
end; end;

File diff suppressed because it is too large Load Diff

View File

@ -2975,10 +2975,6 @@ end;
initial column widths and row heights. initial column widths and row heights.
} }
procedure TsCustomWorksheetGrid.Setup; procedure TsCustomWorksheetGrid.Setup;
var
i: Integer;
lCol: PCol;
lRow: PRow;
begin begin
if (FWorksheet = nil) or (FWorksheet.GetCellCount = 0) then begin if (FWorksheet = nil) or (FWorksheet.GetCellCount = 0) then begin
if ShowHeaders then begin if ShowHeaders then begin

View File

@ -483,6 +483,8 @@ function ParseCellString(const AStr: String; out ACellRow, ACellCol: Cardinal;
while (i <= Length(AStr)) do begin while (i <= Length(AStr)) do begin
if (UpCase(AStr[i]) in LETTERS) then begin if (UpCase(AStr[i]) in LETTERS) then begin
ACellCol := Cardinal(ord(UpCase(AStr[i])) - ord('A')) + 1 + ACellCol * 26; ACellCol := Cardinal(ord(UpCase(AStr[i])) - ord('A')) + 1 + ACellCol * 26;
if ACellCol >= MAX_COL_COUNT then // too many columns (dropping this limitation could cause overflow if a too long string is passed
exit;
inc(i); inc(i);
end end
else else

View File

@ -155,6 +155,7 @@
<ComponentName Value="MainForm"/> <ComponentName Value="MainForm"/>
<HasResources Value="True"/> <HasResources Value="True"/>
<ResourceBaseClass Value="Form"/> <ResourceBaseClass Value="Form"/>
<UnitName Value="beMain"/>
</Unit5> </Unit5>
<Unit6> <Unit6>
<Filename Value="beutils.pas"/> <Filename Value="beutils.pas"/>

View File

@ -12,18 +12,6 @@ type
TBIFFDetailsEvent = procedure(Sender: TObject; ADetails: TStrings) of object; TBIFFDetailsEvent = procedure(Sender: TObject; ADetails: TStrings) of object;
TBIFF2RichTextRun = packed record // valid up to BIFF5
IndexToFirstChar: Byte;
FontIndex: Byte;
end;
TBIFF8RichTextRun = packed record
IndexToFirstChar: Word;
FontIndex: Word;
end;
TRichTextRuns = array of TBiff8RichTextRun;
TBIFFGrid = class(TStringGrid) TBIFFGrid = class(TStringGrid)
private private
FRecType: Word; FRecType: Word;
@ -126,11 +114,7 @@ type
procedure Click; override; procedure Click; override;
procedure DoExtractDetails; procedure DoExtractDetails;
procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean;
out AString: String; out ANumBytes: Integer; out AOffsetToAsianPhoneticBlock: Integer; out AString: String; out ANumBytes: Integer; IgnoreCompressedFlag: Boolean = false);
out AsianPhoneticBlockSize: DWord; out ARichTextRuns: TRichTextRuns;
AIgnoreCompressedFlag: Boolean = false); overload;
procedure ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean;
out AString: String; out ANumBytes: Integer; AIgnoreCompressedFlag: Boolean = false); overload;
procedure PopulateGrid; procedure PopulateGrid;
procedure ShowInRow(var ARow: Integer; var AOffs: LongWord; ASize: Word; AValue,ADescr: String); procedure ShowInRow(var ARow: Integer; var AOffs: LongWord; ASize: Word; AValue,ADescr: String);
procedure ShowRowColData(var ABufIndex: LongWord); procedure ShowRowColData(var ABufIndex: LongWord);
@ -147,7 +131,7 @@ type
implementation implementation
uses uses
StrUtils, Math, lazutf8, StrUtils, Math,
fpsutils, fpsutils,
beBIFFUtils; beBIFFUtils;
@ -198,32 +182,19 @@ end;
procedure TBIFFGrid.ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean; procedure TBIFFGrid.ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean;
out AString: String; out ANumBytes: Integer; out AOffsetToAsianPhoneticBlock: Integer; out AString: String; out ANumBytes: Integer; IgnoreCompressedFlag: Boolean = false);
out AsianPhoneticBlockSize: DWord; out ARichTextRuns: TRichTextRuns;
AIgnoreCompressedFlag: Boolean = false);
var var
i: Integer;
ls: Integer; ls: Integer;
sa: ansiString; sa: ansiString;
sw: WideString; sw: WideString;
w: Word; w: Word;
optn: Byte; optn: Byte;
bytesPerChar: Byte;
containsAsianPhonetics: Boolean;
containsRichText: Boolean;
richTextCount: Word = 0;
savedBufIndex: Integer;
begin begin
AString := ''; if Length(FBuffer) = 0 then begin
ANumBytes := 0; AString := '';
AOffsetToAsianPhoneticBlock := -1; ANumBytes := 0;
AsianPhoneticBlockSize := 0;
SetLength(ARichTextRuns, 0);
if Length(FBuffer) = 0 then
exit; exit;
end;
savedBufIndex := ABufIndex;
if ALenBytes = 1 then if ALenBytes = 1 then
ls := FBuffer[ABufIndex] ls := FBuffer[ABufIndex]
else begin else begin
@ -232,48 +203,18 @@ begin
end; end;
if AUnicode then begin if AUnicode then begin
optn := FBuffer[ABufIndex + ALenBytes]; optn := FBuffer[ABufIndex + ALenBytes];
if (optn and $01 = 0) and (not AIgnoreCompressedFlag) then if (optn and $01 = 0) and (not IgnoreCompressedFlag)
bytesPerChar := 1 then begin // compressed --> 1 byte per character
else
bytesPerChar := 2;
containsAsianPhonetics := (optn and $04 <> 0);
containsRichText := (optn and $08 <> 0);
ABufIndex := ABufIndex + ALenBytes + 1;
if containsRichText then begin
Move(FBuffer[ABufIndex], richTextCount, 2);
richTextCount := WordLEToN(richTextCount);
inc(ABufIndex, 2);
end;
if containsAsianPhonetics then begin
Move(FBuffer[ABufIndex], AsianPhoneticBlockSize, 4);
AsianPhoneticBlockSize := DWordLEToN(AsianPhoneticBlockSize);
inc(ABufIndex, 4);
end;
if bytesPerChar = 1 then begin
SetLength(sa, ls); SetLength(sa, ls);
Move(FBuffer[ABufIndex], sa[1], ls*SizeOf(AnsiChar)); ANumbytes := ls*SizeOf(AnsiChar) + ALenBytes + 1;
inc(ABufIndex, ls*SizeOf(AnsiChar)); Move(FBuffer[ABufIndex + ALenBytes + 1], sa[1], ls*SizeOf(AnsiChar));
AString := AnsiToUTF8(sa); AString := sa;
end else begin end else begin
SetLength(sw, ls); SetLength(sw, ls);
Move(FBuffer[ABufIndex], sw[1], ls*SizeOf(WideChar)); ANumBytes := ls*SizeOf(WideChar) + ALenBytes + 1;
inc(ABufIndex, ls*SizeOf(WideChar)); Move(FBuffer[ABufIndex + ALenBytes + 1], sw[1], ls*SizeOf(WideChar));
AString := UTF8Encode(WideStringLEToN(sw)); AString := UTF8Encode(WideStringLEToN(sw));
end; end;
if containsRichText then begin
SetLength(ARichTextRuns, richTextCount);
Move(FBuffer[ABufIndex], ARichTextRuns[0], richTextCount*SizeOf(TBiff8RichTextRun));
for i:=0 to richTextCount-1 do begin
ARichTextRuns[i].IndexToFirstchar := WordLEToN(ARichTextRuns[i].IndexToFirstChar);
ARichTextRuns[i].FontIndex := WordLEToN(ARichTextRuns[i].FontIndex);
end;
inc(ABufIndex, richTextCount*SizeOf(word));
end;
if containsAsianPhonetics then begin
AOffsetToAsianPhoneticBlock := ABufIndex;
inc(ABufIndex, AsianPhoneticBlockSize);
end;
ANumBytes := ABufIndex - savedBufIndex;
end else begin end else begin
SetLength(sa, ls); SetLength(sa, ls);
ANumBytes := ls*SizeOf(AnsiChar) + ALenBytes; ANumBytes := ls*SizeOf(AnsiChar) + ALenBytes;
@ -282,17 +223,6 @@ begin
end; end;
end; end;
procedure TBIFFGrid.ExtractString(ABufIndex: Integer; ALenBytes: Byte; AUnicode: Boolean;
out AString: String; out ANumBytes: Integer; AIgnoreCompressedFlag: Boolean = false);
var
asianPhoneticBlockOffset: Integer;
asianPhoneticBlockSize: DWord;
richTextRuns: TRichTextRuns;
begin
ExtractString(ABufIndex, ALenBytes, AUnicode, AString, ANumBytes,
asianPhoneticBlockOffset, asianPhoneticBlockSize, richTextRuns,
AIgnoreCompressedFlag);
end;
function TBIFFGrid.GetStringType: String; function TBIFFGrid.GetStringType: String;
begin begin
@ -1575,7 +1505,6 @@ begin
'(relict of BIFF5)'); '(relict of BIFF5)');
end else begin end else begin
ExtractString(FBufferIndex, 2, true, s, numBytes); ExtractString(FBufferIndex, 2, true, s, numBytes);
if Row = FCurrRow then begin if Row = FCurrRow then begin
FDetails.Add('Encoded URL without sheet name:'#13); FDetails.Add('Encoded URL without sheet name:'#13);
case s[1] of case s[1] of
@ -4225,12 +4154,7 @@ var
numBytes: Integer; numBytes: Integer;
s: String; s: String;
total1, total2: DWord; total1, total2: DWord;
i, j: Integer; i: Integer;
asianPhoneticBlockOffset: Integer;
asianPhoneticBlockSize: DWord;
richTextRuns: TRichTextRuns;
dw: DWord;
b: Byte;
begin begin
numBytes := 4; numBytes := 4;
Move(FBuffer[FBufferIndex], total1, numBytes); Move(FBuffer[FBufferIndex], total1, numBytes);
@ -4246,47 +4170,9 @@ begin
'Number of following strings'); 'Number of following strings');
for i:=1 to total2 do begin for i:=1 to total2 do begin
ExtractString(FBufferIndex, 2, true, s, numBytes, asianPhoneticBlockOffset, ExtractString(FBufferIndex, 2, true, s, numBytes);
asianPhoneticBlockSize, richTextRuns);
if FFormat = sfExcel8 then begin
if Row = FCurrRow then begin
FDetails.Add('Wide string info:'#13);
FDetails.Add('2 length bytes: ' + IntToStr(UTF8Length(s)));
b := FBuffer[FBufferIndex+2];
FDetails.Add('Options byte: ' + IntToStr(b));
if b and $01 = 0
then FDetails.Add(' Bit 1 = 0: compressed characters (8-bit characters)')
else FDetails.Add(' Bit 1 = 1: uncompressed characters (16-bit characters)');
if b and $04 = 0
then FDetails.Add(' Bit 4 = 0: Does not contain Asian phonetic settings')
else FDetails.Add(' Bit 4 = 1: Contains Asian phonetic settings');
if b and $08 = 0
then FDetails.Add(' Bit 8 = 0: Does not contain Rich-Text settings')
else FDetails.Add(' Bit 8 = 1: Contains Rich-Text settings');
if Length(richTextRuns) > 0 then begin
FDetails.Add('Rich-Text information (2 bytes):');
FDetails.Add(' ' +IntToStr(Length(richTextRuns)) + ' Rich-Text runs');
end;
if asianPhoneticBlockSize > 0 then begin
FDetails.Add('Asian phonetic block size information (4 bytes): ');
FDetails.Add(' Block size: ' + IntToStr(AsianPhoneticBlockSize) + ' bytes');
end;
FDetails.Add('String text: ' + s);
if Length(richTextRuns)>0 then begin
FDetails.Add('Rich text runs:');
for j:=0 to High(richTextRuns) do
FDetails.Add(Format(' Rich text run #%d: binary data $%.4x --> index of first formatted character %d, font index %d',
[j, DWord(richTextRuns[j]), richTextRuns[j].IndexToFirstChar, richTextRuns[j].FontIndex]));
end;
if asianPhoneticBlockSize>0 then begin
FDetails.Add('Asian phonetic block:');
FDetails.Add(' Size: ' + IntToStr(asianPhoneticBlockSize));
FDetails.Add(' (not decoded)');
end;
end;
ShowInRow(FCurrRow, FBufferIndex, numBytes, s, Format('Shared string #%d', [i])); ShowInRow(FCurrRow, FBufferIndex, numBytes, s, Format('Shared string #%d', [i]));
end; end;
end;
end; end;

View File

@ -61,15 +61,14 @@ var
row, col: Cardinal; row, col: Cardinal;
row1, row2: Cardinal; row1, row2: Cardinal;
col1, col2: Cardinal; col1, col2: Cardinal;
formula: TsFormula; formula: string;
s: String; s: String;
TempFile: String; TempFile: String;
ErrList: TStringList; ErrList: TStringList;
newColor: TsColor; newColor: TsColor;
expected: integer; expected: integer;
begin begin
formula.FormulaStr := '=A1'; formula := '=A1';
formula.DoubleValue := 0.0;
ErrList := TStringList.Create; ErrList := TStringList.Create;
try try

View File

@ -17,7 +17,7 @@ uses
// Not using Lazarus package as the user may be working with multiple versions // Not using Lazarus package as the user may be working with multiple versions
// Instead, add .. to unit search path // Instead, add .. to unit search path
Classes, SysUtils, fpcunit, testutils, testregistry, Classes, SysUtils, fpcunit, testutils, testregistry,
fpsallformats, fpspreadsheet, fpsfunc, fpsallformats, fpspreadsheet, fpsexprparser,
xlsbiff8 {and a project requirement for lclbase for utf8 handling}, xlsbiff8 {and a project requirement for lclbase for utf8 handling},
testsutility; testsutility;
@ -31,27 +31,42 @@ type
procedure SetUp; override; procedure SetUp; override;
procedure TearDown; override; procedure TearDown; override;
// Test formula strings // Test formula strings
procedure TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat); procedure TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat;
UseRPNFormula: Boolean);
// Test calculation of rpn formulas // Test calculation of rpn formulas
procedure TestCalcRPNFormulas(AFormat: TsSpreadsheetformat); procedure TestCalcFormulas(AFormat: TsSpreadsheetformat; UseRPNFormula: Boolean);
published published
// Writes out numbers & reads back. // Writes out numbers & reads back.
// If previous read tests are ok, this effectively tests writing. // If previous read tests are ok, this effectively tests writing.
{ BIFF2 Tests } { BIFF2 Tests }
procedure TestWriteRead_BIFF2_FormulaStrings; procedure Test_Write_Read_FormulaStrings_BIFF2;
{ BIFF5 Tests } { BIFF5 Tests }
procedure TestWriteRead_BIFF5_FormulaStrings; procedure Test_Write_Read_FormulaStrings_BIFF5;
{ BIFF8 Tests } { BIFF8 Tests }
procedure TestWriteRead_BIFF8_FormulaStrings; procedure Test_Write_Read_FormulaStrings_BIFF8;
{ OOXML Tests }
procedure Test_Write_Read_FormulaStrings_OOXML;
// Writes out and calculates formulas, read back // Writes out and calculates rpn formulas, read back
{ BIFF2 Tests } { BIFF2 Tests }
procedure TestWriteRead_BIFF2_CalcRPNFormula; procedure Test_Write_Read_CalcRPNFormula_BIFF2;
{ BIFF5 Tests } { BIFF5 Tests }
procedure TestWriteRead_BIFF5_CalcRPNFormula; procedure Test_Write_Read_CalcRPNFormula_BIFF5;
{ BIFF8 Tests } { BIFF8 Tests }
procedure TestWriteRead_BIFF8_CalcRPNFormula; procedure Test_Write_Read_CalcRPNFormula_BIFF8;
{ OOXML Tests }
procedure Test_Write_Read_CalcRPNFormula_OOXML;
// Writes out and calculates string formulas, read back
{ BIFF2 Tests }
procedure Test_Write_Read_CalcStringFormula_BIFF2;
{ BIFF5 Tests }
procedure Test_Write_Read_CalcStringFormula_BIFF5;
{ BIFF8 Tests }
procedure Test_Write_Read_CalcStringFormula_BIFF8;
{ OOXML Tests }
procedure Test_Write_Read_CalcStringFormula_OOXML;
end; end;
implementation implementation
@ -59,6 +74,18 @@ implementation
uses uses
math, typinfo, lazUTF8, fpsUtils, rpnFormulaUnit; math, typinfo, lazUTF8, fpsUtils, rpnFormulaUnit;
var
// Array containing the "true" results of the formulas, for comparison
SollValues: array of TsExpressionResult;
// Helper for statistics tests
const
STATS_NUMBERS: Array[0..4] of Double = (1.0, 1.1, 1.2, 0.9, 0.8);
var
numberArray: array[0..4] of Double;
{ TSpreadWriteReadFormatTests } { TSpreadWriteReadFormatTests }
procedure TSpreadWriteReadFormulaTests.SetUp; procedure TSpreadWriteReadFormulaTests.SetUp;
@ -71,7 +98,10 @@ begin
inherited TearDown; inherited TearDown;
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteReadFormulaStrings(AFormat: TsSpreadsheetFormat); procedure TSpreadWriteReadFormulaTests.TestWriteReadFormulaStrings(
AFormat: TsSpreadsheetFormat; UseRPNFormula: Boolean);
{ If UseRPNFormula is true the test formulas are generated from RPN formulas.
Otherwise they are generated from string formulas. }
const const
SHEET = 'Sheet1'; SHEET = 'Sheet1';
var var
@ -79,20 +109,29 @@ var
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;
Row: Integer; Row: Integer;
TempFile: string; //write xls/xml to this file and read back from it TempFile: string; //write xls/xml to this file and read back from it
formula: String;
expected: String; expected: String;
actual: String; actual: String;
cell: PCell; cell: PCell;
cellB1: Double;
cellB2: Double;
number: Double;
s: String;
hr, min, sec, msec: Word;
k: Integer;
begin begin
TempFile := GetTempFileName; TempFile := GetTempFileName;
// Create test workbook // Create test workbook
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
try try
MyWorkbook.Options := MyWorkbook.Options + [boCalcBeforeSaving];
MyWorkSheet:= MyWorkBook.AddWorksheet(SHEET); MyWorkSheet:= MyWorkBook.AddWorksheet(SHEET);
// Write out all test formulas // Write out all test formulas
// All formulas are in column B // All formulas are in column B
WriteRPNFormulaSamples(MyWorksheet, AFormat, true); {$I testcases_calcrpnformula.inc}
// WriteRPNFormulaSamples(MyWorksheet, AFormat, true, UseRPNFormula);
MyWorkBook.WriteToFile(TempFile, AFormat, true); MyWorkBook.WriteToFile(TempFile, AFormat, true);
finally finally
MyWorkbook.Free; MyWorkbook.Free;
@ -113,8 +152,8 @@ begin
for Row := 0 to MyWorksheet.GetLastRowIndex do for Row := 0 to MyWorksheet.GetLastRowIndex do
begin begin
cell := MyWorksheet.FindCell(Row, 1); cell := MyWorksheet.FindCell(Row, 1);
if (cell <> nil) and (Length(cell^.RPNFormulaValue) > 0) then begin if HasFormula(cell) then begin
actual := MyWorksheet.ReadRPNFormulaAsString(cell); actual := MyWorksheet.ReadFormulaAsString(cell);
expected := MyWorksheet.ReadAsUTF8Text(Row, 0); expected := MyWorksheet.ReadAsUTF8Text(Row, 0);
CheckEquals(expected, actual, 'Test read formula mismatch, cell '+CellNotation(MyWorkSheet,Row,1)); CheckEquals(expected, actual, 'Test read formula mismatch, cell '+CellNotation(MyWorkSheet,Row,1));
end; end;
@ -126,40 +165,46 @@ begin
end; end;
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_FormulaStrings; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF2;
begin begin
TestWriteReadFormulaStrings(sfExcel2); TestWriteReadFormulaStrings(sfExcel2, true);
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_FormulaStrings; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF5;
begin begin
TestWriteReadFormulaStrings(sfExcel5); TestWriteReadFormulaStrings(sfExcel5, true);
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_FormulaStrings; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_BIFF8;
begin begin
TestWriteReadFormulaStrings(sfExcel8); TestWriteReadFormulaStrings(sfExcel8, true);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_FormulaStrings_OOXML;
begin
TestWriteReadFormulaStrings(sfOOXML, true);
end; end;
{ Test calculation of rpn formulas } { Test calculation of formulas }
procedure TSpreadWriteReadFormulaTests.TestCalcRPNFormulas(AFormat: TsSpreadsheetFormat); procedure TSpreadWriteReadFormulaTests.TestCalcFormulas(AFormat: TsSpreadsheetFormat;
UseRPNFormula: Boolean);
{ If UseRPNFormula is TRUE, the test formulas are generated from RPN syntax,
otherwise string formulas are used. }
const const
SHEET = 'Sheet1'; SHEET = 'Sheet1';
STATS_NUMBERS: Array[0..4] of Double = (1.0, 1.1, 1.2, 0.9, 0.8);
var var
MyWorksheet: TsWorksheet; MyWorksheet: TsWorksheet;
MyWorkbook: TsWorkbook; MyWorkbook: TsWorkbook;
Row: Integer; Row: Integer;
TempFile: string; //write xls/xml to this file and read back from it TempFile: string; //write xls/xml to this file and read back from it
actual: TsArgument; actual: TsExpressionResult;
expected: TsArgument; expected: TsExpressionResult;
cell: PCell; cell: PCell;
sollValues: array of TsArgument; sollValues: array of TsExpressionResult;
formula: String; formula: String;
s: String; s: String;
t: TTime;
hr,min,sec,msec: Word; hr,min,sec,msec: Word;
ErrorMargin: double; ErrorMargin: double;
k: Integer; k: Integer;
@ -168,7 +213,8 @@ var
the formula calculation as well. The next variables, along with STATS_NUMBERS the formula calculation as well. The next variables, along with STATS_NUMBERS
above, hold the arguments for the direction function calls. } above, hold the arguments for the direction function calls. }
number: Double; number: Double;
numberArray: array[0..4] of Double; cellB1: Double;
cellB2: Double;
begin begin
ErrorMargin:=0; //1.44E-7; ErrorMargin:=0; //1.44E-7;
//1.44E-7 for SUMSQ formula //1.44E-7 for SUMSQ formula
@ -200,6 +246,7 @@ begin
// Open the workbook // Open the workbook
MyWorkbook := TsWorkbook.Create; MyWorkbook := TsWorkbook.Create;
try try
MyWorkbook.Options := Myworkbook.Options + [boReadFormulas];
MyWorkbook.ReadFromFile(TempFile, AFormat); MyWorkbook.ReadFromFile(TempFile, AFormat);
if AFormat = sfExcel2 then if AFormat = sfExcel2 then
MyWorksheet := MyWorkbook.GetFirstWorksheet MyWorksheet := MyWorkbook.GetFirstWorksheet
@ -214,15 +261,24 @@ begin
cell := MyWorksheet.FindCell(Row, 1); cell := MyWorksheet.FindCell(Row, 1);
if (cell = nil) then if (cell = nil) then
fail('Error in test code: Failed to get cell ' + CellNotation(MyWorksheet, Row, 1)); fail('Error in test code: Failed to get cell ' + CellNotation(MyWorksheet, Row, 1));
case cell^.ContentType of case cell^.ContentType of
cctBool : actual := CreateBoolArg(cell^.BoolValue); cctBool : actual := BooleanResult(cell^.BoolValue);
cctNumber : actual := CreateNumberArg(cell^.NumberValue); cctNumber : actual := FloatResult(cell^.NumberValue);
cctError : actual := CreateErrorArg(cell^.ErrorValue); cctDateTime : actual := DateTimeResult(cell^.DateTimeValue);
cctUTF8String : actual := CreateStringArg(cell^.UTF8StringValue); cctUTF8String : actual := StringResult(cell^.UTF8StringValue);
cctError : actual := ErrorResult(cell^.ErrorValue);
cctEmpty : actual := EmptyResult;
else fail('ContentType not supported'); else fail('ContentType not supported');
end; end;
expected := SollValues[row]; expected := SollValues[row];
CheckEquals(ord(expected.ArgumentType), ord(actual.ArgumentType), // Cell does not store integers!
if expected.ResultType = rtInteger then expected := FloatResult(expected.ResInteger);
CheckEquals(
GetEnumName(TypeInfo(TsExpressionResult), ord(expected.ResultType)),
GetEnumName(TypeInfo(TsExpressionResult), ord(actual.ResultType)),
'Test read calculated formula data type mismatch, formula "' + formula + 'Test read calculated formula data type mismatch, formula "' + formula +
'", cell '+CellNotation(MyWorkSheet,Row,1)); '", cell '+CellNotation(MyWorkSheet,Row,1));
@ -231,22 +287,22 @@ begin
// the file value in the same second. Therefore we neglect the milliseconds. // the file value in the same second. Therefore we neglect the milliseconds.
if formula = '=NOW()' then begin if formula = '=NOW()' then begin
// Round soll value to seconds // Round soll value to seconds
DecodeTime(expected.NumberValue, hr,min,sec,msec); DecodeTime(expected.ResDateTime, hr,min,sec,msec);
expected.NumberValue := EncodeTime(hr, min, sec, 0); expected.ResDateTime := EncodeTime(hr, min, sec, 0);
// Round formula value to seconds // Round formula value to seconds
DecodeTime(actual.NumberValue, hr,min,sec,msec); DecodeTime(actual.ResDateTime, hr,min,sec,msec);
actual.NumberValue := EncodeTime(hr,min,sec,0); actual.ResDateTime := EncodeTime(hr,min,sec,0);
end; end;
case actual.ArgumentType of case actual.ResultType of
atBool: rtBoolean:
CheckEquals(BoolToStr(expected.BoolValue), BoolToStr(actual.BoolValue), CheckEquals(BoolToStr(expected.ResBoolean), BoolToStr(actual.ResBoolean),
'Test read calculated formula result mismatch, formula "' + formula + 'Test read calculated formula result mismatch, formula "' + formula +
'", cell '+CellNotation(MyWorkSheet,Row,1)); '", cell '+CellNotation(MyWorkSheet,Row,1));
atNumber: rtFloat:
{$if (defined(mswindows)) or (FPC_FULLVERSION>=20701)} {$if (defined(mswindows)) or (FPC_FULLVERSION>=20701)}
// FPC 2.6.x and trunk on Windows need this, also FPC trunk on Linux x64 // FPC 2.6.x and trunk on Windows need this, also FPC trunk on Linux x64
CheckEquals(expected.NumberValue, actual.NumberValue, ErrorMargin, CheckEquals(expected.ResFloat, actual.ResFloat, ErrorMargin,
'Test read calculated formula result mismatch, formula "' + formula + 'Test read calculated formula result mismatch, formula "' + formula +
'", cell '+CellNotation(MyWorkSheet,Row,1)); '", cell '+CellNotation(MyWorkSheet,Row,1));
{$else} {$else}
@ -255,14 +311,14 @@ begin
'Test read calculated formula result mismatch, formula "' + formula + 'Test read calculated formula result mismatch, formula "' + formula +
'", cell '+CellNotation(MyWorkSheet,Row,1)); '", cell '+CellNotation(MyWorkSheet,Row,1));
{$endif} {$endif}
atString: rtString:
CheckEquals(expected.StringValue, actual.StringValue, CheckEquals(expected.ResString, actual.ResString,
'Test read calculated formula result mismatch, formula "' + formula + 'Test read calculated formula result mismatch, formula "' + formula +
'", cell '+CellNotation(MyWorkSheet,Row,1)); '", cell '+CellNotation(MyWorkSheet,Row,1));
atError: rtError:
CheckEquals( CheckEquals(
GetEnumName(TypeInfo(TsErrorValue), ord(expected.ErrorValue)), GetEnumName(TypeInfo(TsErrorValue), ord(expected.ResError)),
GetEnumname(TypeInfo(TsErrorValue), ord(actual.ErrorValue)), GetEnumname(TypeInfo(TsErrorValue), ord(actual.ResError)),
'Test read calculated formula error value mismatch, formula ' + formula + 'Test read calculated formula error value mismatch, formula ' + formula +
', cell '+CellNotation(MyWorkSheet,Row,1)); ', cell '+CellNotation(MyWorkSheet,Row,1));
end; end;
@ -274,19 +330,44 @@ begin
end; end;
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF2_CalcRPNFormula; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF2;
begin begin
TestCalcRPNFormulas(sfExcel2); TestCalcFormulas(sfExcel2, true);
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF5_CalcRPNFormula; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF5;
begin begin
TestCalcRPNFormulas(sfExcel5); TestCalcFormulas(sfExcel5, true);
end; end;
procedure TSpreadWriteReadFormulaTests.TestWriteRead_BIFF8_CalcRPNFormula; procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_BIFF8;
begin begin
TestCalcRPNFormulas(sfExcel8); TestCalcFormulas(sfExcel8, true);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcRPNFormula_OOXML;
begin
TestCalcFormulas(sfOOXML, true);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF2;
begin
TestCalcFormulas(sfExcel2, false);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF5;
begin
TestCalcFormulas(sfExcel5, false);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_BIFF8;
begin
TestCalcFormulas(sfExcel8, false);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_CalcStringFormula_OOXML;
begin
TestCalcFormulas(sfOOXML, false);
end; end;

View File

@ -350,7 +350,8 @@ begin
CheckEquals('$XFE$1',GetCellString(0,16384,[])); // the first column beyond xlsx CheckEquals('$XFE$1',GetCellString(0,16384,[])); // the first column beyond xlsx
// Something VERY big, beyond xlsx // Something VERY big, beyond xlsx
s := 'ZZZZ1'; // s := 'ZZZZ1'; // this is case is no longer possible because max column count has been cut down to 65536
s := 'CRAA1';
ParseCellString(s, r, c, flags); ParseCellString(s, r, c, flags);
CheckEquals(s, GetCellString(r, c, flags)); CheckEquals(s, GetCellString(r, c, flags));
end; end;

View File

@ -58,6 +58,8 @@ type
// As described in bug 25718: Feature request & patch: Implementation of writing more functions // As described in bug 25718: Feature request & patch: Implementation of writing more functions
// Writes all rpn formulas. Use Excel or Open/LibreOffice to check validity. // Writes all rpn formulas. Use Excel or Open/LibreOffice to check validity.
procedure TestRPNFormula; procedure TestRPNFormula;
// Dto, but writes string formulas.
// procedure TestStringFormula;
{$ENDIF} {$ENDIF}
// For BIFF8 format, writes all background colors in A1..A16 // For BIFF8 format, writes all background colors in A1..A16
procedure TestBiff8CellBackgroundColor; procedure TestBiff8CellBackgroundColor;
@ -69,8 +71,9 @@ uses
fpsUtils, rpnFormulaUnit; fpsUtils, rpnFormulaUnit;
const const
COLORSHEETNAME='colorsheet'; //for background color tests COLORSHEETNAME='color_sheet'; //for background color tests
RPNSHEETNAME='formula_sheet'; //for rpn formula tests RPNSHEETNAME='rpn_formula_sheet'; //for rpn formula tests
FORMULASHEETNAME='formula_sheet'; // for string formula tests
OUTPUT_FORMAT = sfExcel8; //change manually if you want to test different formats. To do: automatically output all formats OUTPUT_FORMAT = sfExcel8; //change manually if you want to test different formats. To do: automatically output all formats
var var
@ -195,7 +198,7 @@ begin
Worksheet := Workbook.AddWorksheet(COLORSHEETNAME); Worksheet := Workbook.AddWorksheet(COLORSHEETNAME);
WorkSheet.WriteUTF8Text(0,1,'TSpreadManualTests.TestBiff8CellBackgroundColor'); WorkSheet.WriteUTF8Text(0,1,'TSpreadManualTests.TestBiff8CellBackgroundColor');
RowOffset:=1; RowOffset := 1;
for i:=0 to Workbook.GetPaletteSize-1 do begin for i:=0 to Workbook.GetPaletteSize-1 do begin
WorkSheet.WriteUTF8Text(i+RowOffset,0,'BACKGROUND COLOR TEST'); WorkSheet.WriteUTF8Text(i+RowOffset,0,'BACKGROUND COLOR TEST');
Cell := Worksheet.GetCell(i+RowOffset, 0); Cell := Worksheet.GetCell(i+RowOffset, 0);
@ -218,6 +221,18 @@ begin
Worksheet := Workbook.AddWorksheet(RPNSHEETNAME); Worksheet := Workbook.AddWorksheet(RPNSHEETNAME);
WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false); WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false);
end; end;
(*
procedure TSpreadManualTests.TestStringFormula;
var
Worksheet: TsWorksheet;
begin
if Workbook = nil then
Workbook := TsWorkbook.Create;
Worksheet := Workbook.AddWorksheet(FORMULASHEETNAME);
WriteRPNFormulaSamples(Worksheet, OUTPUT_FORMAT, false, false);
end;
*)
{$ENDIF} {$ENDIF}
initialization initialization

View File

@ -150,7 +150,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr]));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellRange(cellAddr, RPNCellRange(cellAddr,
RPNFunc(fekCOUNT, 1, // 1 parameter used in COUNT RPNFunc('COUNT', 1, // 1 parameter used in COUNT
nil nil
)))); ))));
Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical! Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical!
@ -161,7 +161,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr]));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellRange(cellAddr, RPNCellRange(cellAddr,
RPNFunc(fekCOUNT, 1, RPNFunc('COUNT', 1,
nil nil
)))); ))));
Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alph-numerical! Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alph-numerical!
@ -172,7 +172,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr])); Worksheet.WriteUTF8Text(Row, 0, Format('=COUNT(%s)', [cellAddr]));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellRange(cellAddr, RPNCellRange(cellAddr,
RPNFunc(fekCOUNT, 1, RPNFunc('COUNT', 1,
nil nil
)))); ))));
Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical! Worksheet.WriteNumber(Row, 2, 6); // 7 cells, but 1 is alpha-numerical!
@ -486,7 +486,7 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=TRUE()'); Worksheet.WriteUTF8Text(Row, 0, '=TRUE()');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekTRUE, RPNFunc('TRUE',
nil))); nil)));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[true]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[true]);
@ -494,7 +494,7 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=FALSE()'); Worksheet.WriteUTF8Text(Row, 0, '=FALSE()');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekFALSE, RPNFunc('FALSE',
nil))); nil)));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[false]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[false]);
@ -505,8 +505,8 @@ begin
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('C1', RPNCellValue('C1',
RPNCellValue('C1', RPNCellValue('C1',
RPNFunc(fekEQUAL, RPNFunc(fekEqual,
RPNFunc(fekNOT, RPNFunc('NOT',
nil)))))); nil))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[not (cellC1=cellC1)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[not (cellC1=cellC1)]);
@ -520,7 +520,7 @@ begin
RPNNumber(1, RPNNumber(1,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekAND, 2, RPNFunc('AND', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (1=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (1=2)]);
@ -534,7 +534,7 @@ begin
RPNNumber(2, RPNNumber(2,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekAND, 2, RPNFunc('AND', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (2=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) and (2=2)]);
@ -548,7 +548,7 @@ begin
RPNNumber(2, RPNNumber(2,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekAND, 2, RPNFunc('AND', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) and (2=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) and (2=2)]);
@ -562,7 +562,7 @@ begin
RPNNumber(1, RPNNumber(1,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekOR, 2, RPNFunc('OR', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (1=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (1=2)]);
@ -576,7 +576,7 @@ begin
RPNNumber(2, RPNNumber(2,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekOR, 2, RPNFunc('OR', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (2=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=0) or (2=2)]);
@ -590,7 +590,7 @@ begin
RPNNumber(2, RPNNumber(2,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNFunc(fekOR, 2, RPNFunc('OR', 2,
nil))))))))); nil)))))))));
Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) or (2=2)]); Worksheet.WriteUTF8Text(Row, 2, FALSE_TRUE[(1=1) or (2=2)]);
@ -603,7 +603,7 @@ begin
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNString('correct', RPNString('correct',
RPNString('wrong', RPNString('wrong',
RPNFunc(fekIF, 3, RPNFunc('IF', 3,
nil)))))))); nil))))))));
Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'wrong')); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'wrong'));
@ -616,7 +616,7 @@ begin
RPNFunc(fekNotEQUAL, RPNFunc(fekNotEQUAL,
RPNString('correct', RPNString('correct',
RPNString('wrong', RPNString('wrong',
RPNFunc(fekIF, 3, RPNFunc('IF', 3,
nil)))))))); nil))))))));
Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'wrong')); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'wrong'));
@ -628,7 +628,7 @@ begin
RPNNumber(1, RPNNumber(1,
RPNFunc(fekEQUAL, RPNFunc(fekEQUAL,
RPNString('correct', RPNString('correct',
RPNFunc(fekIF, 2, RPNFunc('IF', 2,
nil))))))); nil)))))));
Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'FALSE')); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1=1.0, 'correct', 'FALSE'));
@ -640,7 +640,7 @@ begin
RPNNumber(1, RPNNumber(1,
RPNFunc(fekNotEQUAL, RPNFunc(fekNotEQUAL,
RPNString('correct', RPNString('correct',
RPNFunc(fekIF, 2, RPNFunc('IF', 2,
nil))))))); nil)))))));
Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'FALSE')); Worksheet.WriteUTF8Text(Row, 2, IfThen(cellB1<>1.0, 'correct', 'FALSE'));
@ -656,7 +656,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=ABS($B1)'); Worksheet.WriteUTF8Text(Row, 0, '=ABS($B1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('$B1', RPNCellValue('$B1',
RPNFunc(fekABS, RPNFunc('ABS',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, abs(cellB1)); Worksheet.WriteNumber(Row, 2, abs(cellB1));
@ -665,7 +665,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=ABS(E$1)'); Worksheet.WriteUTF8Text(Row, 0, '=ABS(E$1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('E$1', RPNCellValue('E$1',
RPNFunc(fekABS, RPNFunc('ABS',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, abs(cellE1)); Worksheet.WriteNumber(Row, 2, abs(cellE1));
@ -674,7 +674,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=SIGN(F1)'); Worksheet.WriteUTF8Text(Row, 0, '=SIGN(F1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('F1', RPNCellValue('F1',
RPNFunc(fekSIGN, RPNFunc('SIGN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, sign(cellF1)); Worksheet.WriteNumber(Row, 2, sign(cellF1));
@ -683,7 +683,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=SIGN(0)'); Worksheet.WriteUTF8Text(Row, 0, '=SIGN(0)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(0, RPNNumber(0,
RPNFunc(fekSIGN, RPNFunc('SIGN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, sign(0)); Worksheet.WriteNumber(Row, 2, sign(0));
@ -692,7 +692,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=SIGN(G1)'); Worksheet.WriteUTF8Text(Row, 0, '=SIGN(G1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('G1', RPNCellValue('G1',
RPNFunc(fekSIGN, RPNFunc('SIGN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, sign(cellG1)); Worksheet.WriteNumber(Row, 2, sign(cellG1));
@ -700,7 +700,7 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=RAND()'); Worksheet.WriteUTF8Text(Row, 0, '=RAND()');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekRAND, RPNFunc('RAND',
nil))); nil)));
Worksheet.WriteUTF8Text(Row, 2, '(random number - cannot compare)'); Worksheet.WriteUTF8Text(Row, 2, '(random number - cannot compare)');
@ -708,7 +708,7 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=PI()'); Worksheet.WriteUTF8Text(Row, 0, '=PI()');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekPI, RPNFunc('PI',
nil))); nil)));
Worksheet.WriteNumber(Row, 2, pi); Worksheet.WriteNumber(Row, 2, pi);
@ -718,10 +718,10 @@ begin
value := pi/2; value := pi/2;
Worksheet.WriteUTF8Text(Row, 0, '=DEGREES(PI()/2)'); Worksheet.WriteUTF8Text(Row, 0, '=DEGREES(PI()/2)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekPI, RPNFunc('PI',
RPNNumber(2, RPNNumber(2,
RPNFunc(fekDIV, RPNFunc(fekDIV,
RPNFunc(fekDEGREES, RPNFunc('DEGREES',
nil)))))); nil))))));
Worksheet.WriteNumber(Row, 2, value/pi*180); Worksheet.WriteNumber(Row, 2, value/pi*180);
@ -731,7 +731,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=RADIANS(90)'); Worksheet.WriteUTF8Text(Row, 0, '=RADIANS(90)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekRADIANS, RPNFunc('RADIANS',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, value/180*pi); Worksheet.WriteNumber(Row, 2, value/180*pi);
end; end;
@ -740,10 +740,10 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=SIN(PI()/2)'); Worksheet.WriteUTF8Text(Row, 0, '=SIN(PI()/2)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekPI, RPNFunc('PI',
RPNNumber(2, RPNNumber(2,
RPNFunc(fekDIV, RPNFunc(fekDIV,
RPNFunc(fekSIN, RPNFunc('SIN',
nil)))))); nil))))));
Worksheet.WriteNumber(Row, 2, sin(pi/2)); Worksheet.WriteNumber(Row, 2, sin(pi/2));
@ -753,7 +753,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=ASIN(%.1f)', [value], fs)); Worksheet.WriteUTF8Text(Row, 0, Format('=ASIN(%.1f)', [value], fs));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekASIN, RPNFunc('ASIN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arcsin(value)); Worksheet.WriteNumber(Row, 2, arcsin(value));
@ -761,8 +761,8 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=COS(PI())'); Worksheet.WriteUTF8Text(Row, 0, '=COS(PI())');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekPI, RPNFunc('PI',
RPNFunc(fekCOS, RPNFunc('COS',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, cos(pi)); Worksheet.WriteNumber(Row, 2, cos(pi));
@ -772,7 +772,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=ACOS(%.1f)', [value], fs)); Worksheet.WriteUTF8Text(Row, 0, Format('=ACOS(%.1f)', [value], fs));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekACOS, RPNFunc('ACOS',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arccos(value)); Worksheet.WriteNumber(Row, 2, arccos(value));
@ -780,10 +780,10 @@ begin
inc(Row); inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=TAN(PI()/4)'); Worksheet.WriteUTF8Text(Row, 0, '=TAN(PI()/4)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNFunc(fekPI, RPNFunc('PI',
RPNNumber(4, RPNNumber(4,
RPNFunc(fekDiv, RPNFunc(fekDiv,
RPNFunc(fekTAN, RPNFunc('TAN',
nil)))))); nil))))));
Worksheet.WriteNumber(Row, 2, tan(pi/4)); Worksheet.WriteNumber(Row, 2, tan(pi/4));
@ -793,7 +793,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=ATAN(1)'); Worksheet.WriteUTF8Text(Row, 0, '=ATAN(1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekATAN, RPNFunc('ATAN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arctan(1.0)); Worksheet.WriteNumber(Row, 2, arctan(1.0));
@ -806,7 +806,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=SINH(3)'); Worksheet.WriteUTF8Text(Row, 0, '=SINH(3)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekSINH, RPNFunc('SINH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, sinh(value)); Worksheet.WriteNumber(Row, 2, sinh(value));
@ -816,7 +816,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=ASINH(%.1f)', [value], fs)); Worksheet.WriteUTF8Text(Row, 0, Format('=ASINH(%.1f)', [value], fs));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekASINH, RPNFunc('ASINH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arcsinh(value)); Worksheet.WriteNumber(Row, 2, arcsinh(value));
@ -826,7 +826,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=COSH(3)'); Worksheet.WriteUTF8Text(Row, 0, '=COSH(3)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekCOSH, RPNFunc('COSH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, cosh(value)); Worksheet.WriteNumber(Row, 2, cosh(value));
@ -836,7 +836,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=ACOSH(10)'); Worksheet.WriteUTF8Text(Row, 0, '=ACOSH(10)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekACOSH, RPNFunc('ACOSH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arccosh(value)); Worksheet.WriteNumber(Row, 2, arccosh(value));
@ -846,7 +846,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=TANH(3)'); Worksheet.WriteUTF8Text(Row, 0, '=TANH(3)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekTANH, RPNFunc('TANH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, tanh(value)); Worksheet.WriteNumber(Row, 2, tanh(value));
@ -856,7 +856,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=ATANH(%.1f)', [value], fs)); Worksheet.WriteUTF8Text(Row, 0, Format('=ATANH(%.1f)', [value], fs));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekATANH, RPNFunc('ATANH',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, arctanh(value)); Worksheet.WriteNumber(Row, 2, arctanh(value));
end; end;
@ -867,7 +867,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=SQRT(2)'); Worksheet.WriteUTF8Text(Row, 0, '=SQRT(2)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekSQRT, RPNFunc('SQRT',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, sqrt(value)); Worksheet.WriteNumber(Row, 2, sqrt(value));
@ -877,7 +877,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=EXP(2)'); Worksheet.WriteUTF8Text(Row, 0, '=EXP(2)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekEXP, RPNFunc('EXP',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, exp(value)); Worksheet.WriteNumber(Row, 2, exp(value));
@ -887,7 +887,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=LN(2)'); Worksheet.WriteUTF8Text(Row, 0, '=LN(2)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekLN, RPNFunc('LN',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, ln(value)); Worksheet.WriteNumber(Row, 2, ln(value));
@ -898,7 +898,7 @@ begin
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNNumber(2, RPNNumber(2,
RPNFunc(fekLOG, 2, RPNFunc('LOG', 2,
nil))))); nil)))));
Worksheet.WriteNumber(Row, 2, logn(2.0, value)); Worksheet.WriteNumber(Row, 2, logn(2.0, value));
@ -908,7 +908,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=LOG10(100)'); Worksheet.WriteUTF8Text(Row, 0, '=LOG10(100)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekLOG10, RPNFunc('LOG10',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, log10(value)); Worksheet.WriteNumber(Row, 2, log10(value));
@ -918,7 +918,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, Format('=LOG10(%.2f)', [value], fs)); Worksheet.WriteUTF8Text(Row, 0, Format('=LOG10(%.2f)', [value], fs));
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(value, RPNNumber(value,
RPNFunc(fekLOG10, RPNFunc('LOG10',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, log10(value)); Worksheet.WriteNumber(Row, 2, log10(value));
@ -935,7 +935,7 @@ begin
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('$F$1', RPNCellValue('$F$1',
RPNNumber(1, RPNNumber(1,
RPNFunc(fekROUND, RPNFunc('ROUND',
nil))))); nil)))));
Worksheet.WriteNumber(Row, 2, Round(cellF1*10)/10); //RoundTo(cellF1, 1)); Worksheet.WriteNumber(Row, 2, Round(cellF1*10)/10); //RoundTo(cellF1, 1));
@ -945,7 +945,7 @@ begin
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('G1', RPNCellValue('G1',
RPNNumber(1, RPNNumber(1,
RPNFunc(fekROUND, RPNFunc('ROUND',
nil))))); nil)))));
Worksheet.WriteNumber(Row, 2, Round(cellG1*10)/10); //RoundTo(cellG1, 1)); Worksheet.WriteNumber(Row, 2, Round(cellG1*10)/10); //RoundTo(cellG1, 1));
@ -954,7 +954,7 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=INT(F1)'); Worksheet.WriteUTF8Text(Row, 0, '=INT(F1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('F1', RPNCellValue('F1',
RPNFunc(fekINT, RPNFunc('INT',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, trunc(cellF1)); Worksheet.WriteNumber(Row, 2, trunc(cellF1));
@ -963,10 +963,10 @@ begin
Worksheet.WriteUTF8Text(Row, 0, '=INT(G1)'); Worksheet.WriteUTF8Text(Row, 0, '=INT(G1)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula( Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNCellValue('G1', RPNCellValue('G1',
RPNFunc(fekINT, RPNFunc('INT',
nil)))); nil))));
Worksheet.WriteNumber(Row, 2, floor(cellG1)); // is this true? Worksheet.WriteNumber(Row, 2, floor(cellG1)); // is this true?
(*
{ ---------- } { ---------- }
inc(Row); inc(Row);
@ -2012,6 +2012,7 @@ begin
Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A!'); Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A!');
Worksheet.WriteUTF8Text(Row, 3, 'Should be "=1/2", but there are too many operands...'); Worksheet.WriteUTF8Text(Row, 3, 'Should be "=1/2", but there are too many operands...');
end; end;
*)
end; end;
end. end.

View File

@ -56,14 +56,17 @@
<Unit3> <Unit3>
<Filename Value="numberstests.pas"/> <Filename Value="numberstests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="numberstests"/>
</Unit3> </Unit3>
<Unit4> <Unit4>
<Filename Value="manualtests.pas"/> <Filename Value="manualtests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="manualtests"/>
</Unit4> </Unit4>
<Unit5> <Unit5>
<Filename Value="testsutility.pas"/> <Filename Value="testsutility.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="testsutility"/>
</Unit5> </Unit5>
<Unit6> <Unit6>
<Filename Value="internaltests.pas"/> <Filename Value="internaltests.pas"/>
@ -72,11 +75,11 @@
<Unit7> <Unit7>
<Filename Value="formattests.pas"/> <Filename Value="formattests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="formattests"/>
</Unit7> </Unit7>
<Unit8> <Unit8>
<Filename Value="colortests.pas"/> <Filename Value="colortests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="colortests"/>
</Unit8> </Unit8>
<Unit9> <Unit9>
<Filename Value="fonttests.pas"/> <Filename Value="fonttests.pas"/>

File diff suppressed because it is too large Load Diff

View File

@ -111,7 +111,10 @@ begin
if not(assigned(Worksheet)) then if not(assigned(Worksheet)) then
result:='CellNotation: error getting worksheet.' result:='CellNotation: error getting worksheet.'
else else
result:=WorkSheet.Name+'!'+ColumnToLetter(Column)+inttostr(Row+1) if Worksheet.Name <> '' then
result := WorkSheet.Name + '!' + ColumnToLetter(Column) + inttostr(Row+1)
else
Result := ColumnToLetter(Column) + IntToStr(Row + 1);
end; end;
function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String; function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String;
@ -119,7 +122,10 @@ begin
if not Assigned(Worksheet) then if not Assigned(Worksheet) then
Result := 'ColNotation: error getting worksheet.' Result := 'ColNotation: error getting worksheet.'
else else
Result := WorkSheet.Name + '!' + ColumnToLetter(Column); if Worksheet.Name <> '' then
Result := WorkSheet.Name + '!' + ColumnToLetter(Column)
else
Result := ColumnToLetter(Column);
end; end;
function RowNotation(Worksheet: TsWorksheet; Row: Integer): String; function RowNotation(Worksheet: TsWorksheet; Row: Integer): String;
@ -127,7 +133,10 @@ begin
if not Assigned(Worksheet) then if not Assigned(Worksheet) then
Result := 'RowNotation: error getting worksheet.' Result := 'RowNotation: error getting worksheet.'
else else
Result := Worksheet.Name + '!' + IntToStr(Row+1); if Worksheet.Name <> '' then
Result := Worksheet.Name + '!' + IntToStr(Row+1)
else
Result := IntToStr(Row+1);
end; end;
end. end.

View File

@ -605,7 +605,7 @@ begin
end; end;
{ Formula token array } { Formula token array }
if boReadFormulas in FWorkbook.Options then begin if (boReadFormulas in FWorkbook.Options) then begin
ok := ReadRPNTokenArray(AStream, cell); ok := ReadRPNTokenArray(AStream, cell);
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported); if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
end; end;
@ -1230,10 +1230,9 @@ end;
} }
procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream); procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream);
var var
sheet: TsWorksheet;
pane: Byte; pane: Byte;
begin begin
sheet := Workbook.GetFirstWorksheet; FWorksheet := Workbook.GetFirstWorksheet;
WriteBOF(AStream); WriteBOF(AStream);
WriteFonts(AStream); WriteFonts(AStream);
@ -1241,21 +1240,21 @@ begin
WriteFormats(AStream); WriteFormats(AStream);
WriteXFRecords(AStream); WriteXFRecords(AStream);
WriteColWidths(AStream); WriteColWidths(AStream);
WriteDimensions(AStream, sheet); WriteDimensions(AStream, FWorksheet);
WriteRows(AStream, sheet); WriteRows(AStream, FWorksheet);
if (boVirtualMode in Workbook.Options) then if (boVirtualMode in Workbook.Options) then
WriteVirtualCells(AStream) WriteVirtualCells(AStream)
else begin else begin
WriteRows(AStream, sheet); WriteRows(AStream, FWorksheet);
WriteCellsToStream(AStream, sheet.Cells); WriteCellsToStream(AStream, FWorksheet.Cells);
end; end;
WriteWindow1(AStream); WriteWindow1(AStream);
// { -- currently not working // { -- currently not working
WriteWindow2(AStream, sheet); WriteWindow2(AStream, FWorksheet);
WritePane(AStream, sheet, false, pane); // false for "is not BIFF5 or BIFF8" WritePane(AStream, FWorksheet, false, pane); // false for "is not BIFF5 or BIFF8"
WriteSelections(AStream, sheet); WriteSelections(AStream, FWorksheet);
//} //}
WriteEOF(AStream); WriteEOF(AStream);
end; end;
@ -1630,11 +1629,6 @@ begin
else else
WriteRPNTokenArray(AStream, AFormula, true, RPNLength); WriteRPNTokenArray(AStream, AFormula, true, RPNLength);
(*
{ Formula data (RPN token array) }
WriteRPNTokenArray(AStream, AFormula, true, RPNLength);
*)
{ Finally write sizes after we know them } { Finally write sizes after we know them }
FinalPos := AStream.Position; FinalPos := AStream.Position;
AStream.Position := RecordSizePos; AStream.Position := RecordSizePos;
@ -1665,13 +1659,12 @@ var
i: Integer; i: Integer;
formula: TsRPNFormula; formula: TsRPNFormula;
begin begin
SetLength(formula, Length(ACell^.SharedFormulaBase^.RPNFormulaValue)); // Create RPN formula from the shared formula base's string formula
for i:=0 to Length(formula)-1 do begin formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase);
// Copy formula
formula[i] := ACell^.SharedFormulaBase^.RPNFormulaValue[i]; // Adapt relative cell references
// Adapt relative cell references for i:=0 to Length(formula)-1 do
FixRelativeReferences(ACell, formula[i]); FixRelativeReferences(ACell, formula[i]);
end;
// Write adapted copy of shared formula to stream. // Write adapted copy of shared formula to stream.
WriteRPNTokenArray(AStream, formula, true, RPNLength); WriteRPNTokenArray(AStream, formula, true, RPNLength);

View File

@ -222,7 +222,7 @@ var
implementation implementation
uses uses
fpsStreams; fpsStreams, fpsExprParser;
const const
{ Excel record IDs } { Excel record IDs }
@ -1247,6 +1247,14 @@ begin
inherited; inherited;
end; end;
var
counter: Integer = 0;
function TsSpreadBIFF8Reader.ReadWideString(const AStream: TStream; function TsSpreadBIFF8Reader.ReadWideString(const AStream: TStream;
const ALength: WORD): WideString; const ALength: WORD): WideString;
var var
@ -1264,16 +1272,15 @@ var
begin begin
StringFlags:=AStream.ReadByte; StringFlags:=AStream.ReadByte;
Dec(PendingRecordSize); Dec(PendingRecordSize);
if StringFlags and 4 = 4 then begin
//Asian phonetics
//Read Asian phonetics Length (not used)
AsianPhoneticBytes:=DWordLEtoN(AStream.ReadDWord);
end;
if StringFlags and 8 = 8 then begin if StringFlags and 8 = 8 then begin
//Rich string //Rich string
RunsCounter:=WordLEtoN(AStream.ReadWord); RunsCounter:=WordLEtoN(AStream.ReadWord);
dec(PendingRecordSize, 2); dec(PendingRecordSize,2);
end;
if StringFlags and 4 = 4 then begin
//Asian phonetics
//Read Asian phonetics length (not used)
AsianPhoneticBytes:=DWordLEtoN(AStream.ReadDWord);
dec(PendingRecordSize, 4);
end; end;
if StringFlags and 1 = 1 Then begin if StringFlags and 1 = 1 Then begin
//String is WideStringLE //String is WideStringLE
@ -1290,6 +1297,11 @@ begin
end else begin end else begin
//String is 1 byte per char, this is UTF-16 with the high byte ommited because it is zero //String is 1 byte per char, this is UTF-16 with the high byte ommited because it is zero
//so decompress and then convert //so decompress and then convert
inc(Counter);
lLen:=ALength; lLen:=ALength;
SetLength(DecomprStrValue, lLen); SetLength(DecomprStrValue, lLen);
for i := 1 to lLen do for i := 1 to lLen do
@ -1298,7 +1310,7 @@ begin
DecomprStrValue[i] := C; DecomprStrValue[i] := C;
Dec(PendingRecordSize); Dec(PendingRecordSize);
if (PendingRecordSize<=0) and (i<lLen) then begin if (PendingRecordSize<=0) and (i<lLen) then begin
//A CONTINUE may happen here //A CONTINUE may happend here
RecordType := WordLEToN(AStream.ReadWord); RecordType := WordLEToN(AStream.ReadWord);
RecordSize := WordLEToN(AStream.ReadWord); RecordSize := WordLEToN(AStream.ReadWord);
if RecordType<>INT_EXCEL_ID_CONTINUE then begin if RecordType<>INT_EXCEL_ID_CONTINUE then begin
@ -1310,13 +1322,14 @@ begin
end; end;
end; end;
end; end;
Result := DecomprStrValue; Result := DecomprStrValue;
end; end;
if StringFlags and 8 = 8 then begin if StringFlags and 8 = 8 then begin
//Rich string (This only happens in BIFF8) //Rich string (This only happened in BIFF8)
for j := 1 to RunsCounter do begin for j := 1 to RunsCounter do begin
if (PendingRecordSize<=0) then begin if (PendingRecordSize<=0) then begin
//A CONTINUE may happen here //A CONTINUE may happend here
RecordType := WordLEToN(AStream.ReadWord); RecordType := WordLEToN(AStream.ReadWord);
RecordSize := WordLEToN(AStream.ReadWord); RecordSize := WordLEToN(AStream.ReadWord);
if RecordType<>INT_EXCEL_ID_CONTINUE then begin if RecordType<>INT_EXCEL_ID_CONTINUE then begin

View File

@ -67,176 +67,6 @@ const
INT_FONT_WEIGHT_NORMAL = $0190; INT_FONT_WEIGHT_NORMAL = $0190;
INT_FONT_WEIGHT_BOLD = $02BC; INT_FONT_WEIGHT_BOLD = $02BC;
{ Formula constants TokenID values }
{ Binary Operator Tokens 3.6}
INT_EXCEL_TOKEN_TADD = $03;
INT_EXCEL_TOKEN_TSUB = $04;
INT_EXCEL_TOKEN_TMUL = $05;
INT_EXCEL_TOKEN_TDIV = $06;
INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^
INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation &
INT_EXCEL_TOKEN_TLT = $09; // Less than <
INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <=
INT_EXCEL_TOKEN_TEQ = $0B; // Equal =
INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >=
INT_EXCEL_TOKEN_TGT = $0D; // Greater than >
INT_EXCEL_TOKEN_TNE = $0E; // Not equal <>
INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection
INT_EXCEL_TOKEN_TLIST = $10; // Cell range list
INT_EXCEL_TOKEN_TRANGE = $11; // Cell range
INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus +
INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus +
INT_EXCEL_TOKEN_TPERCENT= $14; // Percent (%, divides operand by 100)
INT_EXCEL_TOKEN_TPAREN = $15; // Operator in parenthesis
{ Constant Operand Tokens, 3.8}
INT_EXCEL_TOKEN_TMISSARG= $16; //missing operand
INT_EXCEL_TOKEN_TSTR = $17; //string
INT_EXCEL_TOKEN_TERR = $1C; //error value
INT_EXCEL_TOKEN_TBOOL = $1D; //boolean
INT_EXCEL_TOKEN_TINT = $1E; //(unsigned) integer
INT_EXCEL_TOKEN_TNUM = $1F; //floating-point
{ Operand Tokens }
// _R: reference; _V: value; _A: array
INT_EXCEL_TOKEN_TREFR = $24;
INT_EXCEL_TOKEN_TREFV = $44;
INT_EXCEL_TOKEN_TREFA = $64;
INT_EXCEL_TOKEN_TAREA_R = $25;
INT_EXCEL_TOKEN_TAREA_V = $45;
INT_EXCEL_TOKEN_TAREA_A = $65;
INT_EXCEL_TOKEN_TREFN_R = $2C;
INT_EXCEL_TOKEN_TREFN_V = $4C;
INT_EXCEL_TOKEN_TREFN_A = $6C;
{ Function Tokens }
// _R: reference; _V: value; _A: array
// Offset 0: token; offset 1: index to a built-in sheet function ( ➜ 3.111)
INT_EXCEL_TOKEN_FUNC_R = $21;
INT_EXCEL_TOKEN_FUNC_V = $41;
INT_EXCEL_TOKEN_FUNC_A = $61;
//VAR: variable number of arguments:
INT_EXCEL_TOKEN_FUNCVAR_R = $22;
INT_EXCEL_TOKEN_FUNCVAR_V = $42;
INT_EXCEL_TOKEN_FUNCVAR_A = $62;
{ Special tokens }
INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula
{ Built-in/worksheet functions }
INT_EXCEL_SHEET_FUNC_COUNT = 0;
INT_EXCEL_SHEET_FUNC_IF = 1;
INT_EXCEL_SHEET_FUNC_ISNA = 2;
INT_EXCEL_SHEET_FUNC_ISERROR = 3;
INT_EXCEL_SHEET_FUNC_SUM = 4;
INT_EXCEL_SHEET_FUNC_AVERAGE = 5;
INT_EXCEL_SHEET_FUNC_MIN = 6;
INT_EXCEL_SHEET_FUNC_MAX = 7;
INT_EXCEL_SHEET_FUNC_ROW = 8;
INT_EXCEL_SHEET_FUNC_COLUMN = 9;
INT_EXCEL_SHEET_FUNC_STDEV = 12;
INT_EXCEL_SHEET_FUNC_SIN = 15;
INT_EXCEL_SHEET_FUNC_COS = 16;
INT_EXCEL_SHEET_FUNC_TAN = 17;
INT_EXCEL_SHEET_FUNC_ATAN = 18;
INT_EXCEL_SHEET_FUNC_PI = 19;
INT_EXCEL_SHEET_FUNC_SQRT = 20;
INT_EXCEL_SHEET_FUNC_EXP = 21;
INT_EXCEL_SHEET_FUNC_LN = 22;
INT_EXCEL_SHEET_FUNC_LOG10 = 23;
INT_EXCEL_SHEET_FUNC_ABS = 24; // $18
INT_EXCEL_SHEET_FUNC_INT = 25;
INT_EXCEL_SHEET_FUNC_SIGN = 26;
INT_EXCEL_SHEET_FUNC_ROUND = 27; // $1B
INT_EXCEL_SHEET_FUNC_MID = 31;
INT_EXCEL_SHEET_FUNC_VALUE = 33;
INT_EXCEL_SHEET_FUNC_TRUE = 34;
INT_EXCEL_SHEET_FUNC_FALSE = 35;
INT_EXCEL_SHEET_FUNC_AND = 36;
INT_EXCEL_SHEET_FUNC_OR = 37;
INT_EXCEL_SHEET_FUNC_NOT = 38;
INT_EXCEL_SHEET_FUNC_VAR = 46;
INT_EXCEL_SHEET_FUNC_PV = 56;
INT_EXCEL_SHEET_FUNC_FV = 57;
INT_EXCEL_SHEET_FUNC_NPER = 58;
INT_EXCEL_SHEET_FUNC_PMT = 59;
INT_EXCEL_SHEET_FUNC_RATE = 60;
INT_EXCEL_SHEET_FUNC_RAND = 63;
INT_EXCEL_SHEET_FUNC_DATE = 65; // $41
INT_EXCEL_SHEET_FUNC_TIME = 66; // $42
INT_EXCEL_SHEET_FUNC_DAY = 67;
INT_EXCEL_SHEET_FUNC_MONTH = 68;
INT_EXCEL_SHEET_FUNC_YEAR = 69;
INT_EXCEL_SHEET_FUNC_WEEKDAY = 70;
INT_EXCEL_SHEET_FUNC_HOUR = 71;
INT_EXCEL_SHEET_FUNC_MINUTE = 72;
INT_EXCEL_SHEET_FUNC_SECOND = 73;
INT_EXCEL_SHEET_FUNC_NOW = 74;
INT_EXCEL_SHEET_FUNC_ROWS = 76;
INT_EXCEL_SHEET_FUNC_COLUMNS = 77;
INT_EXCEL_SHEET_FUNC_ASIN = 98;
INT_EXCEL_SHEET_FUNC_ACOS = 99;
INT_EXCEL_SHEET_FUNC_ISREF = 105;
INT_EXCEL_SHEET_FUNC_LOG = 109;
INT_EXCEL_SHEET_FUNC_CHAR = 111;
INT_EXCEL_SHEET_FUNC_LOWER = 112;
INT_EXCEL_SHEET_FUNC_UPPER = 113;
INT_EXCEL_SHEET_FUNC_PROPER = 114;
INT_EXCEL_SHEET_FUNC_LEFT = 115;
INT_EXCEL_SHEET_FUNC_RIGHT = 116;
INT_EXCEL_SHEET_FUNC_TRIM = 118;
INT_EXCEL_SHEET_FUNC_REPLACE = 119;
INT_EXCEL_SHEET_FUNC_SUBSTITUTE = 120;
INT_EXCEL_SHEET_FUNC_CODE = 121;
INT_EXCEL_SHEET_FUNC_CELL = 125;
INT_EXCEL_SHEET_FUNC_ISERR = 126;
INT_EXCEL_SHEET_FUNC_ISTEXT = 127;
INT_EXCEL_SHEET_FUNC_ISNUMBER = 128;
INT_EXCEL_SHEET_FUNC_ISBLANK = 129;
INT_EXCEL_SHEET_FUNC_DATEVALUE = 140;
INT_EXCEL_SHEET_FUNC_TIMEVALUE = 141;
INT_EXCEL_SHEET_FUNC_COUNTA = 169;
INT_EXCEL_SHEET_FUNC_PRODUCT = 183;
INT_EXCEL_SHEET_FUNC_ISNONTEXT = 190;
INT_EXCEL_SHEET_FUNC_STDEVP = 193;
INT_EXCEL_SHEET_FUNC_VARP = 194;
INT_EXCEL_SHEET_FUNC_ISLOGICAL = 198;
INT_EXCEL_SHEET_FUNC_TODAY = 221; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_MEDIAN = 227; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SINH = 229; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COSH = 230; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_TANH = 231; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ASINH = 232; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ACOSH = 233; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_ATANH = 234; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_INFO = 244; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_AVEDEV = 269; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BETADIST = 270; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BETAINV = 272; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_BINOMDIST = 273; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_CHIDIST = 274; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_CHIINV = 275; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_PERMUT = 299; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_POISSON = 300; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SUMSQ = 321; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_RADIANS = 342; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_DEGREES = 343; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_SUMIF = 345; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COUNTIF = 346; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_COUNTBLANK = 347; // not available in BIFF2
INT_EXCEL_SHEET_FUNC_DATEDIF = 351; // not available in BIFF2
{ Control Tokens, Special Tokens }
// 01H tExp Matrix formula or shared formula
// 02H tTbl Multiple operation table
// 15H tParen Parentheses
// 18H tNlr Natural language reference (BIFF8)
INT_EXCEL_TOKEN_TATTR = $19; // tAttr Special attribute
// 1AH tSheet Start of external sheet reference (BIFF2-BIFF4)
// 1BH tEndSheet End of external sheet reference (BIFF2-BIFF4)
{ CODEPAGE record constants } { CODEPAGE record constants }
WORD_ASCII = 367; WORD_ASCII = 367;
WORD_UTF_16 = 1200; // BIFF 8 WORD_UTF_16 = 1200; // BIFF 8
@ -475,7 +305,7 @@ type
function GetLastRowIndex(AWorksheet: TsWorksheet): Integer; function GetLastRowIndex(AWorksheet: TsWorksheet): Integer;
procedure GetLastColCallback(ACell: PCell; AStream: TStream); procedure GetLastColCallback(ACell: PCell; AStream: TStream);
function GetLastColIndex(AWorksheet: TsWorksheet): Word; function GetLastColIndex(AWorksheet: TsWorksheet): Word;
function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word; // function FormulaElementKindToExcelTokenID(AElementKind: TFEKind; out ASecondaryID: Word): Word;
// Helper function for writing a string with 8-bit length } // Helper function for writing a string with 8-bit length }
function WriteString_8BitLen(AStream: TStream; AString: String): Integer; virtual; function WriteString_8BitLen(AStream: TStream; AString: String): Integer; virtual;
@ -555,11 +385,11 @@ type
implementation implementation
uses uses
AVL_Tree, Math, Variants, fpsNumFormatParser; AVL_Tree, Math, Variants, xlsConst, fpsNumFormatParser, fpsExprParser;
{ Helper table for rpn formulas:
Assignment of FormulaElementKinds (fekXXXX) to EXCEL_TOKEN IDs. }
const const
{ Helper table for rpn formulas:
Assignment of FormulaElementKinds (fekXXXX) to EXCEL_TOKEN IDs. }
TokenIDs: array[TFEKind] of Word = ( TokenIDs: array[TFEKind] of Word = (
// Basic operands // Basic operands
INT_EXCEL_TOKEN_TREFV, {fekCell} INT_EXCEL_TOKEN_TREFV, {fekCell}
@ -590,6 +420,9 @@ const
INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=} INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=}
INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>} INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>}
INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis} INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis}
Word(-1) {fekFunc}
);
(*
// Math functions // Math functions
INT_EXCEL_SHEET_FUNC_ABS, {fekABS} INT_EXCEL_SHEET_FUNC_ABS, {fekABS}
@ -711,7 +544,7 @@ const
// Other operations // Other operations
INT_EXCEL_TOKEN_TATTR {fekOpSum} INT_EXCEL_TOKEN_TATTR {fekOpSum}
); );
*)
type type
TBIFF58BlankRecord = packed record TBIFF58BlankRecord = packed record
RecordID: Word; RecordID: Word;
@ -1188,7 +1021,6 @@ var
err: TsErrorValue; err: TsErrorValue;
ok: Boolean; ok: Boolean;
cell: PCell; cell: PCell;
begin begin
{ Index to XF Record } { Index to XF Record }
ReadRowColXF(AStream, ARow, ACol, XF); ReadRowColXF(AStream, ARow, ACol, XF);
@ -1247,7 +1079,7 @@ begin
if IsDateTime(ResultFormula, nf, nfs, dt) then if IsDateTime(ResultFormula, nf, nfs, dt) then
FWorksheet.WriteDateTime(cell, dt, nf, nfs) FWorksheet.WriteDateTime(cell, dt, nf, nfs)
else else
FWorksheet.WriteNumber(cell, ResultFormula, nf, nfs); //, nd, ncs); FWorksheet.WriteNumber(cell, ResultFormula, nf, nfs);
end; end;
{ Formula token array } { Formula token array }
@ -1274,7 +1106,7 @@ var
begin begin
ARow := WordLEtoN(AStream.ReadWord); ARow := WordLEtoN(AStream.ReadWord);
fc := WordLEtoN(AStream.ReadWord); fc := WordLEtoN(AStream.ReadWord);
pending := RecordSize - Sizeof(fc) - Sizeof(ARow); pending := RecordSize - SizeOf(fc) - SizeOf(ARow);
if FIsVirtualMode then begin if FIsVirtualMode then begin
InitCell(ARow, 0, FVirtualCell); InitCell(ARow, 0, FVirtualCell);
cell := @FVirtualCell; cell := @FVirtualCell;
@ -1626,7 +1458,7 @@ begin
end; end;
{ Reads the array of rpn tokens from the current stream position, creates an { Reads the array of rpn tokens from the current stream position, creates an
rpn formula and stores it in the cell. } rpn formula, converts it to a string formula and stores it in the cell. }
function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream; function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
ACell: PCell): Boolean; ACell: PCell): Boolean;
var var
@ -1640,9 +1472,11 @@ var
r, c, r2, c2: Cardinal; r, c, r2, c2: Cardinal;
dr, dc: Integer; dr, dc: Integer;
fek: TFEKind; fek: TFEKind;
func: Word; exprDef: TsBuiltInExprIdentifierDef;
funcCode: Word;
b: Byte; b: Byte;
found: Boolean; found: Boolean;
formula: TsRPNformula;
begin begin
rpnItem := nil; rpnItem := nil;
n := ReadRPNTokenArraySize(AStream); n := ReadRPNTokenArraySize(AStream);
@ -1698,16 +1532,11 @@ begin
INT_EXCEL_TOKEN_FUNC_A: INT_EXCEL_TOKEN_FUNC_A:
// functions with fixed argument count // functions with fixed argument count
begin begin
func := ReadRPNFunc(AStream); funcCode := ReadRPNFunc(AStream);
found := false; exprDef := BuiltInIdentifiers.IdentifierByExcelCode(funcCode);
for fek in TFuncTokens do begin if exprDef <> nil then
if (TokenIDs[fek] = func) and FixedParamCount(fek) then begin rpnItem := RPNFunc(exprDef.Name, rpnItem)
rpnItem := RPNFunc(fek, rpnItem); else
found := true;
break;
end;
end;
if not found then
supported := false; supported := false;
end; end;
@ -1717,15 +1546,11 @@ begin
// functions with variable argument count // functions with variable argument count
begin begin
b := AStream.ReadByte; b := AStream.ReadByte;
func := ReadRPNFunc(AStream); funcCode := ReadRPNFunc(AStream);
found := false; exprDef := BuiltinIdentifiers.IdentifierByExcelCode(funcCode);
for fek in TFuncTokens do if exprDef <> nil then
if (TokenIDs[fek] = func) and not FixedParamCount(fek) then begin rpnItem := RPNFunc(exprDef.Name, b, rpnItem)
rpnItem := RPNFunc(fek, b, rpnItem); else
found := true;
break;
end;
if not found then
supported := false; supported := false;
end; end;
@ -1751,11 +1576,11 @@ begin
end; end;
if not supported then begin if not supported then begin
DestroyRPNFormula(rpnItem); DestroyRPNFormula(rpnItem);
SetLength(ACell^.RPNFormulaValue, 0);
Result := false; Result := false;
end end
else begin else begin
ACell^.RPNFormulaValue := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items! formula := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items!
ACell^.FormulaValue := FWorksheet.ConvertRPNFormulaToStringFormula(formula);
Result := true; Result := true;
end; end;
end; end;
@ -1938,10 +1763,11 @@ begin
end else end else
Result := AColor; Result := AColor;
end; end;
(*
function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID( function TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID(
AElementKind: TFEKind; out ASecondaryID: Word): Word; AElementKind: TFEKind; out ASecondaryID: Word): Word;
begin begin
if AElementKind = fekFunc then
if (AElementKind >= Low(TFuncTokens)) and (AElementKind <= High(TFuncTokens)) if (AElementKind >= Low(TFuncTokens)) and (AElementKind <= High(TFuncTokens))
then begin then begin
if FixedParamCount(AElementKind) then if FixedParamCount(AElementKind) then
@ -1955,7 +1781,7 @@ begin
ASecondaryID := 0; ASecondaryID := 0;
end; end;
end; end;
*)
procedure TsSpreadBIFFWriter.GetLastRowCallback(ACell: PCell; AStream: TStream); procedure TsSpreadBIFFWriter.GetLastRowCallback(ACell: PCell; AStream: TStream);
begin begin
Unused(AStream); Unused(AStream);
@ -2174,12 +2000,17 @@ end;
{ Writes an Excel FORMULA record. { Writes an Excel FORMULA record.
Note: The formula is already stored in the cell. Note: The formula is already stored in the cell.
Since BIFF files contain RPN formulas the method calls WriteRPNFormula. Since BIFF files contain RPN formulas the string formula of the cell is
converted to an RPN formula and the method calls WriteRPNFormula.
} }
procedure TsSpreadBIFFWriter.WriteFormula(AStream: TStream; procedure TsSpreadBIFFWriter.WriteFormula(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell); const ARow, ACol: Cardinal; ACell: PCell);
var
formula: TsRPNFormula;
begin begin
WriteRPNFormula(AStream, ARow, ACol, ACell^.RPNFormulaValue, ACell); formula := FWorksheet.BuildRPNFormula(ACell);
WriteRPNFormula(AStream, ARow, ACol, formula, ACell);
SetLength(formula, 0);
end; end;
{ Writes a 64-bit floating point NUMBER record. { Writes a 64-bit floating point NUMBER record.
@ -2586,10 +2417,12 @@ procedure TsSpreadBIFFWriter.WriteRPNTokenArray(AStream: TStream;
const AFormula: TsRPNFormula; WriteTokenArraySize: Boolean; var RPNLength: Word); const AFormula: TsRPNFormula; WriteTokenArraySize: Boolean; var RPNLength: Word);
var var
i: Integer; i: Integer;
tokenID, secondaryID: Word;
n: Word; n: Word;
TokenArraySizePos: Int64; TokenArraySizePos: Int64;
FinalPos: Int64; FinalPos: Int64;
exprDef: TsExprIdentifierDef;
excelCode: Word;
primaryExcelCode, secondaryExcelCode: Word;
begin begin
RPNLength := 0; RPNLength := 0;
@ -2604,12 +2437,22 @@ begin
for i := 0 to Length(AFormula) - 1 do begin for i := 0 to Length(AFormula) - 1 do begin
{ Token identifier } { Token identifier }
tokenID := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, secondaryID); if AFormula[i].ElementKind = fekFunc then begin
AStream.WriteByte(tokenID); exprDef := BuiltinIdentifiers.IdentifierByName(Aformula[i].FuncName);
if exprDef.HasFixedArgumentCount then
primaryExcelCode := INT_EXCEL_TOKEN_FUNC_V
else
primaryExcelCode := INT_EXCEL_TOKEN_FUNCVAR_V;
secondaryExcelCode := exprDef.ExcelCode;
end else begin
primaryExcelCode := TokenIDs[AFormula[i].ElementKind];
secondaryExcelCode := 0;
end;
AStream.WriteByte(primaryExcelCode);
inc(RPNLength); inc(RPNLength);
{ Token data } { Token data }
case tokenID of case primaryExcelCode of
{ Operand Tokens } { Operand Tokens }
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell }
// INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset} // INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset}
@ -2651,6 +2494,12 @@ begin
inc(RPNLength, 8); inc(RPNLength, 8);
end; end;
INT_EXCEL_TOKEN_TINT: { fekNum, but integer }
begin
AStream.WriteBuffer(AFormula[i].IntValue, 2);
inc(RPNLength, 2);
end;
INT_EXCEL_TOKEN_TSTR: { fekString } INT_EXCEL_TOKEN_TSTR: { fekString }
{ string constant is stored as widestring in BIFF8, otherwise as ansistring { string constant is stored as widestring in BIFF8, otherwise as ansistring
Writing is done by the virtual method WriteString_8bitLen. } Writing is done by the virtual method WriteString_8bitLen. }
@ -2667,7 +2516,7 @@ begin
// Functions with fixed parameter count // Functions with fixed parameter count
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A: INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
begin begin
n := WriteRPNFunc(AStream, secondaryID); n := WriteRPNFunc(AStream, secondaryExcelCode);
inc(RPNLength, n); inc(RPNLength, n);
end; end;
@ -2675,7 +2524,7 @@ begin
INT_EXCEL_TOKEN_FUNCVAR_V: INT_EXCEL_TOKEN_FUNCVAR_V:
begin begin
AStream.WriteByte(AFormula[i].ParamsNum); AStream.WriteByte(AFormula[i].ParamsNum);
n := WriteRPNFunc(AStream, secondaryID); n := WriteRPNFunc(AStream, secondaryExcelCode);
inc(RPNLength, 1 + n); inc(RPNLength, 1 + n);
end; end;
@ -2933,14 +2782,13 @@ begin
// Number of existing formula records // Number of existing formula records
AStream.WriteByte((r2-r1+1) * (c2-c1+1)); AStream.WriteByte((r2-r1+1) * (c2-c1+1));
// Copy the formula (we don't want to overwrite the cell formulas) // Create an RPN formula from the shared formula base's string formula
// and adjust relative references // and adjust relative references
SetLength(formula, Length(ACell^.SharedFormulaBase^.RPNFormulaValue)); formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase);
for i:=0 to Length(ACell^.SharedFormulaBase^.RPNFormulaValue)-1 do begin for i:=0 to Length(formula)-1 do
formula[i] := ACell^.SharedFormulaBase^.RPNFormulaValue[i];
FixRelativeReferences(ACell, formula[i]); FixRelativeReferences(ACell, formula[i]);
end;
// Writes the (copied) rpn token array // Writes the rpn token array
WriteRPNTokenArray(AStream, formula, true, RPNLength); WriteRPNTokenArray(AStream, formula, true, RPNLength);
{ Write record size at the end after we known it } { Write record size at the end after we known it }

View File

@ -114,7 +114,7 @@ type
function GetStyleIndex(ACell: PCell): Cardinal; function GetStyleIndex(ACell: PCell): Cardinal;
procedure ListAllBorders; procedure ListAllBorders;
procedure ListAllFills; procedure ListAllFills;
function PrepareFormula(const AFormula: TsFormula): String; function PrepareFormula(const AFormula: String): String;
procedure ResetStreams; procedure ResetStreams;
procedure WriteBorderList(AStream: TStream); procedure WriteBorderList(AStream: TStream);
procedure WriteCols(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteCols(AStream: TStream; AWorksheet: TsWorksheet);
@ -648,7 +648,7 @@ begin
if datanode.NodeName = 'v' then if datanode.NodeName = 'v' then
dataStr := GetNodeValue(datanode) dataStr := GetNodeValue(datanode)
else else
if datanode.NodeName = 'f' then if (boReadFormulas in FWorkbook.Options) and (datanode.NodeName = 'f') then
begin begin
// Formula to cell // Formula to cell
formulaStr := GetNodeValue(datanode); formulaStr := GetNodeValue(datanode);
@ -660,12 +660,12 @@ begin
s := GetAttrValue(datanode, 'ref'); s := GetAttrValue(datanode, 'ref');
if (s <>'') then if (s <>'') then
begin begin
cell^.FormulaValue.FormulaStr := '=' + formulaStr; cell^.FormulaValue := formulaStr;
FWorksheet.UseSharedFormula(s, cell); FWorksheet.UseSharedFormula(s, cell);
end; end;
end end
else else
cell^.FormulaValue.FormulaStr := '=' + formulaStr; cell^.FormulaValue := formulaStr;
end; end;
end; end;
datanode := datanode.NextSibling; datanode := datanode.NextSibling;
@ -2407,10 +2407,11 @@ begin
end; end;
{ Prepares a string formula for writing } { Prepares a string formula for writing }
function TsSpreadOOXMLWriter.PrepareFormula(const AFormula: TsFormula): String; function TsSpreadOOXMLWriter.PrepareFormula(const AFormula: String): String;
begin begin
Result := AFormula.FormulaStr; Result := AFormula;
if (Result <> '') and (Result[1] = '=') then Delete(Result, 1, 1); if (Result <> '') and (Result[1] = '=') then Delete(Result, 1, 1);
Result := UTF8TextToXMLText(Result)
end; end;
{ Is called before zipping the individual file parts. Rewinds the streams. } { Is called before zipping the individual file parts. Rewinds the streams. }
@ -2426,15 +2427,6 @@ begin
ResetStream(FSSharedStrings_complete); ResetStream(FSSharedStrings_complete);
for i := 0 to High(FSSheets) do for i := 0 to High(FSSheets) do
ResetStream(FSSheets[i]); ResetStream(FSSheets[i]);
{
FSContentTypes.Position := 0;
FSRelsRels.Position := 0;
FSWorkbookRels.Position := 0;
FSWorkbook.Position := 0;
FSStyles.Position := 0;
FSSharedStrings_complete.Position := 0;
for stream in FSSheets do stream.Position := 0;
}
end; end;
{ {
@ -2544,6 +2536,7 @@ var
r, c, r2, c2: Cardinal; r, c, r2, c2: Cardinal;
cell: PCell; cell: PCell;
id: Cardinal; id: Cardinal;
t, v: String;
begin begin
cellPosText := TsWorksheet.CellPosToText(ARow, ACol); cellPosText := TsWorksheet.CellPosToText(ARow, ACol);
lStyleIndex := GetStyleIndex(ACell); lStyleIndex := GetStyleIndex(ACell);
@ -2596,15 +2589,53 @@ begin
CellPosText, lStyleIndex, CellPosText, lStyleIndex,
PtrInt(ACell^.SharedFormulaBase) // ID of the shared formula PtrInt(ACell^.SharedFormulaBase) // ID of the shared formula
])); ]));
end else end else begin
// "normal" formula // "normal" formula
case ACell^.ContentType of
cctFormula:
begin
t := '';
v := '';
end;
cctUTF8String:
begin
t := ' t="str"';
v := Format('<v>%s</v>', [UTF8TextToXMLText(ACell^.UTF8StringValue)]);
end;
cctNumber:
begin
t := '';
v := Format('<v>%g</v>', [ACell^.NumberValue], FPointSeparatorSettings);
end;
cctDateTime:
begin
t := '';
v := Format('<v>%g</v>', [ACell^.DateTimeValue], FPointSeparatorSettings);
end;
cctBool:
begin
t := ' t="b"';
if ACell^.BoolValue then
v := '<v>1</v>'
else
v := '<v>0</v>';
end;
cctError:
begin
t := ' t="e"';
v := Format('<v>%s</v>', [GetErrorValueStr(ACell^.ErrorValue)]);
end;
end;
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<c r="%s" s="%d">' + '<c r="%s" s="%d"%s>' +
'<f>%s</f>' + '<f>%s</f>' +
'%s' +
'</c>', [ '</c>', [
CellPosText, lStyleIndex, CellPosText, lStyleIndex, t,
PrepareFormula(ACell^.FormulaValue) PrepareFormula(ACell^.FormulaValue),
v
])); ]));
end;
end; end;
{******************************************************************* {*******************************************************************