fpspreadsheet/formulas with 3d references: sfExcel8 can read them now. Several bug fixes (e.g. '=Sheet1!A1' was not working). Add testcases.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6399 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2018-05-09 17:07:59 +00:00
parent e4f6f6e355
commit bfb8cff66e
11 changed files with 576 additions and 99 deletions

View File

@@ -57,7 +57,7 @@ type
{ Tokens } { Tokens }
TsTokenType = ( TsTokenType = (
ttCell, ttSheetCell, ttCellRange, ttSheetName, ttCell, ttCellRange, ttSheetName,
ttNumber, ttString, ttIdentifier, ttNumber, ttString, ttIdentifier,
ttPlus, ttMinus, ttMul, ttDiv, ttConcat, ttPercent, ttPower, ttLeft, ttRight, ttPlus, ttMinus, ttMul, ttDiv, ttConcat, ttPercent, ttPower, ttLeft, ttRight,
ttLessThan, ttLargerThan, ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual, ttLessThan, ttLargerThan, ttEqual, ttNotEqual, ttLessThanEqual, ttLargerThanEqual,
@@ -589,35 +589,33 @@ type
property SheetName: String read FSheetName; property SheetName: String read FSheetName;
end; end;
{ TsBasicCellExprNode } { TsCellExprNode }
TsBasicCellExprNode = class(TsExprNode) TsCellExprNode = class(TsExprNode)
private private
FWorksheet: TsWorksheet; FWorksheet: TsWorksheet;
FRow, FCol: Cardinal; FRow, FCol: Cardinal;
FFlags: TsRelFlags; FFlags: TsRelFlags;
FCell: PCell; FCell: PCell;
FIsRef: Boolean; FIsRef: Boolean;
FOtherSheet: Boolean;
protected protected
procedure Check; override; procedure Check; override;
function GetCol: Cardinal; function GetCol: Cardinal;
function GetRow: Cardinal; function GetRow: Cardinal;
function GetSheetIndex: Integer;
procedure GetNodeValue(out Result: TsExpressionResult); override; procedure GetNodeValue(out Result: TsExpressionResult); override;
public public
constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet; constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet;
ARow, ACol: Cardinal; AFlags: TsRelFlags); overload; ARow, ACol: Cardinal; AFlags: TsRelFlags; OtherSheet: Boolean); overload;
constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet;
ACellString: String; Othersheet: Boolean); overload;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
function NodeType: TsResultType; override; function NodeType: TsResultType; override;
property Worksheet: TsWorksheet read FWorksheet; property Worksheet: TsWorksheet read FWorksheet;
end; end;
{ TsCellExprNode } (*
TsCellExprNode = class(TsBasicCellExprNode)
public
constructor Create(AParser: TsExpressionParser; AWorksheet: TsWorksheet;
ACellString: String); overload;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override;
end;
{ TsSheetCellExprNode } { TsSheetCellExprNode }
TsSheetCellExprNode = class(TsBasicCellExprNode) TsSheetCellExprNode = class(TsBasicCellExprNode)
protected protected
@@ -627,7 +625,7 @@ type
ACellString: String); overload; ACellString: String); overload;
function AsRPNItem(ANext: PRPNItem): PRPNItem; override; function AsRPNItem(ANext: PRPNItem): PRPNItem; override;
function AsString: string; override; function AsString: string; override;
end; end; *)
{ TsCellRangeExprNode } { TsCellRangeExprNode }
@@ -1026,6 +1024,7 @@ var
flags: TsRelFlags; flags: TsRelFlags;
begin begin
C := CurrentChar; C := CurrentChar;
if C = FSheetNameTerminator then C := NextPos;
sheetName := ''; sheetName := '';
while (not IsWordDelim(C)) and (C <> cNull) and (C <> FSheetNameTerminator) do while (not IsWordDelim(C)) and (C <> cNull) and (C <> FSheetNameTerminator) do
begin begin
@@ -1662,6 +1661,7 @@ var
token: String; token: String;
prevTokenType: TsTokenType; prevTokenType: TsTokenType;
sheetname: String; sheetname: String;
sheet: TsWorksheet;
begin begin
{$ifdef debugexpr} Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr} {$ifdef debugexpr} Writeln('Primitive : ',TokenName(TokenType),': ',CurrentToken);{$endif debugexpr}
SetLength(Args, 0); SetLength(Args, 0);
@@ -1682,17 +1682,17 @@ begin
else if (TokenType = ttString) then else if (TokenType = ttString) then
Result := TsConstExprNode.CreateString(self, CurrentToken) Result := TsConstExprNode.CreateString(self, CurrentToken)
else if (TokenType = ttCell) then else if (TokenType = ttCell) then
Result := TsCellExprNode.Create(self, FWorksheet, CurrentToken) Result := TsCellExprNode.Create(self, FWorksheet, CurrentToken, false)
else if (TokenType = ttSheetName) then begin else if (TokenType = ttSheetName) then begin
sheetName := CurrentToken; sheetName := CurrentToken;
GetToken; GetToken;
if TokenType = ttCell then if TokenType = ttCell then begin
Result := TsSheetCellExprNode.Create(self, FWorksheet.Workbook.GetWorksheetByName(sheetName), CurrentToken) sheet := FWorksheet.Workbook.GetWorksheetByName(sheetName);
if sheet = nil then
sheet := FWorksheet.Workbook.AddWorksheet(sheetName, true);
Result := TsCellExprNode.Create(self, sheet, CurrentToken, true)
end;
end end
(*
else if (TokenType = ttSheetCell) then
Result := TsSheetCellExprNode.Create(self, FWorksheet.Workbook, CurrentToken)
*)
else if (TokenType = ttCellRange) then else if (TokenType = ttCellRange) then
Result := TsCellRangeExprNode.Create(self, FWorksheet, CurrentToken) Result := TsCellRangeExprNode.Create(self, FWorksheet, CurrentToken)
else if (TokenType = ttError) then else if (TokenType = ttError) then
@@ -1878,10 +1878,12 @@ procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula);
operand: TsExprNode = nil; operand: TsExprNode = nil;
fek: TFEKind; fek: TFEKind;
r,c, r2,c2: Cardinal; r,c, r2,c2: Cardinal;
idx: Integer;
flags: TsRelFlags; flags: TsRelFlags;
ID: TsExprIdentifierDef; ID: TsExprIdentifierDef;
i, n: Integer; i, n: Integer;
args: TsExprArgumentArray; args: TsExprArgumentArray;
sheet: TsWorksheet;
begin begin
if AIndex < 0 then if AIndex < 0 then
exit; exit;
@@ -1898,7 +1900,22 @@ procedure TsExpressionParser.SetRPNFormula(const AFormula: TsRPNFormula);
else else
begin begin
flags := AFormula[AIndex].RelFlags; flags := AFormula[AIndex].RelFlags;
ANode := TsCellExprNode.Create(self, FWorksheet, r, c, flags); ANode := TsCellExprNode.Create(self, FWorksheet, r, c, flags, false);
end;
dec(AIndex);
end;
fekCell3D:
begin
idx := AFormula[AIndex].Sheet;
r := AFormula[AIndex].Row;
c := AFormula[AIndex].Col;
if (LongInt(r) < 0) or (LongInt(c) < 0) then
ANode := TsConstExprNode.CreateError(self, errIllegalRef)
else
begin
flags := AFormula[AIndex].RelFlags;
sheet := FWorksheet.Workbook.GetWorksheetByIndex(idx);
ANode := TsCellExprNode.Create(Self, sheet, r, c, flags, true);
end; end;
dec(AIndex); dec(AIndex);
end; end;
@@ -3610,10 +3627,11 @@ begin
end; end;
{ TsBasicCellExprNode } { TsCellExprNode }
constructor TsBasicCellExprNode.Create(AParser: TsExpressionParser; constructor TsCellExprNode.Create(AParser: TsExpressionParser;
AWorksheet: TsWorksheet; ARow,ACol: Cardinal; AFlags: TsRelFlags); AWorksheet: TsWorksheet; ARow,ACol: Cardinal; AFlags: TsRelFlags;
OtherSheet: Boolean);
begin begin
FParser := AParser; FParser := AParser;
FWorksheet := AWorksheet; FWorksheet := AWorksheet;
@@ -3621,9 +3639,64 @@ begin
FCol := ACol; FCol := ACol;
FFlags := AFlags; FFlags := AFlags;
FCell := AWorksheet.FindCell(FRow, FCol); FCell := AWorksheet.FindCell(FRow, FCol);
FOtherSheet := OtherSheet;
end; end;
procedure TsBasicCellExprNode.Check; constructor TsCellExprNode.Create(AParser: TsExpressionParser;
AWorksheet: TsWorksheet; ACellString: String; OtherSheet: Boolean);
var
r, c: Cardinal;
flags: TsRelFlags;
begin
ParseCellString(ACellString, r, c, flags);
Create(AParser, AWorksheet, r, c, flags, OtherSheet);
end;
function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
if FIsRef then
begin
if FOtherSheet then
Result := RPNCellRef3D(GetSheetIndex, GetRow, GetCol, FFlags, ANext)
else
Result := RPNCellRef(GetRow, GetCol, FFlags, ANext)
end else
begin
if FOtherSheet then
Result := RPNCellValue3D(GetSheetIndex, GetRow, GetCol, FFlags, ANext)
else
Result := RPNCellValue(GetRow, GetCol, FFlags, ANext);
end;
end;
function TsCellExprNode.AsString: string;
var
r, c: Cardinal;
begin
r := Getrow;
c := GetCol;
if FOtherSheet then
case FParser.Dialect of
fdExcelA1:
Result := Format('%s!%s', [FWorksheet.Name, GetCellString(r, c, FFlags)]);
fdExcelR1C1:
Result := Format('%s!%s', [FWorksheet.Name,
GetCellString_R1C1(r, c, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col)]);
fdOpenDocument:
Result := Format('[%s.%s]', [FWorksheet.Name, GetCellString(r, c, FFlags)]);
end
else
case FParser.Dialect of
fdExcelA1:
Result := GetCellString(GetRow, GetCol, FFlags);
fdExcelR1C1:
Result := GetCellString_R1C1(GetRow, GetCol, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
fdOpenDocument:
Result := '[.' + GetCellString(GetRow, GetCol, FFlags) + ']';
end;
end;
procedure TsCellExprNode.Check;
begin begin
// Nothing to check; // Nothing to check;
end; end;
@@ -3638,14 +3711,14 @@ end;
address of the SourceCell. address of the SourceCell.
(2) Normal mode: (2) Normal mode:
Returns the "true" row address of the cell assigned to the formula node. } Returns the "true" row address of the cell assigned to the formula node. }
function TsBasicCellExprNode.GetCol: Cardinal; function TsCellExprNode.GetCol: Cardinal;
begin begin
Result := FCol; Result := FCol;
if FParser.CopyMode and (rfRelCol in FFlags) then if FParser.CopyMode and (rfRelCol in FFlags) then
Result := FCol - FParser.FSourceCell^.Col + FParser.FDestCell^.Col; Result := FCol - FParser.FSourceCell^.Col + FParser.FDestCell^.Col;
end; end;
procedure TsBasicCellExprNode.GetNodeValue(out Result: TsExpressionResult); procedure TsCellExprNode.GetNodeValue(out Result: TsExpressionResult);
var var
cell: PCell; cell: PCell;
begin begin
@@ -3657,7 +3730,7 @@ begin
if (cell <> nil) and HasFormula(cell) then if (cell <> nil) and HasFormula(cell) then
case FWorksheet.GetCalcState(cell) of case FWorksheet.GetCalcState(cell) of
csNotCalculated: csNotCalculated:
Worksheet.CalcFormula(cell); FWorksheet.CalcFormula(cell);
csCalculating: csCalculating:
raise ECalcEngine.CreateFmt(rsCircularReference, [GetCellString(cell^.Row, cell^.Col)]); raise ECalcEngine.CreateFmt(rsCircularReference, [GetCellString(cell^.Row, cell^.Col)]);
end; end;
@@ -3669,19 +3742,27 @@ begin
end; end;
{ See: GetCol } { See: GetCol }
function TsBasicCellExprNode.GetRow: Cardinal; function TsCellExprNode.GetRow: Cardinal;
begin begin
Result := FRow; Result := FRow;
if Parser.CopyMode and (rfRelRow in FFlags) then if Parser.CopyMode and (rfRelRow in FFlags) then
Result := FRow - FParser.FSourceCell^.Row + FParser.FDestCell^.Row; Result := FRow - FParser.FSourceCell^.Row + FParser.FDestCell^.Row;
end; end;
function TsBasicCellExprNode.NodeType: TsResultType; function TsCellExprNode.GetSheetIndex: Integer;
var
book: TsWorkbook;
begin
book := FWorksheet.Workbook;
Result := book.GetWorksheetIndex(FWorksheet);
end;
function TsCellExprNode.NodeType: TsResultType;
begin begin
Result := rtCell; Result := rtCell;
end; end;
(*
{ TsSheetCellExprNode } { TsSheetCellExprNode }
constructor TsSheetCellExprNode.Create(AParser: TsExpressionParser; constructor TsSheetCellExprNode.Create(AParser: TsExpressionParser;
@@ -3743,38 +3824,10 @@ begin
book := FWorksheet.Workbook; book := FWorksheet.Workbook;
Result := book.GetWorksheetIndex(FWorksheet); Result := book.GetWorksheetIndex(FWorksheet);
end; end;
*)
{ TsCellExprNode } { TsCellExprNode }
constructor TsCellExprNode.Create(AParser: TsExpressionParser;
AWorksheet: TsWorksheet; ACellString: String);
var
r, c: Cardinal;
flags: TsRelFlags;
begin
ParseCellString(ACellString, r, c, flags);
Create(AParser, AWorksheet, r, c, flags);
end;
function TsCellExprNode.AsRPNItem(ANext: PRPNItem): PRPNItem;
begin
if FIsRef then
Result := RPNCellRef(GetRow, GetCol, FFlags, ANext)
else
Result := RPNCellValue(GetRow, GetCol, FFlags, ANext);
end;
function TsCellExprNode.AsString: string;
begin
case FParser.Dialect of
fdExcelA1 : Result := GetCellString(GetRow, GetCol, FFlags);
fdExcelR1C1 : Result := GetCellString_R1C1(GetRow, GetCol, FFlags, FParser.FSourceCell^.Row, FParser.FSourceCell^.Col);
fdOpenDocument : Result := '[.' + GetCellString(GetRow, GetCol, FFlags) + ']';
end;
end;
{ TsCellRangeExprNode } { TsCellRangeExprNode }

View File

@@ -17,6 +17,9 @@ Specifications obtained from:
http://docs.oasis-open.org/office/v1.1/OS/OpenDocument-v1.1.pdf http://docs.oasis-open.org/office/v1.1/OS/OpenDocument-v1.1.pdf
AUTHORS: Felipe Monteiro de Carvalho / Jose Luis Jurado Rincon / Werner Pamler AUTHORS: Felipe Monteiro de Carvalho / Jose Luis Jurado Rincon / Werner Pamler
NOTICE: Active define FPSpreadDebug in the project options to get a log during
reading/writing.
} }
@@ -28,11 +31,12 @@ unit fpsopendocument;
{$I ..\fps.inc} {$I ..\fps.inc}
{.$define FPSPREADDEBUG} //used to be XLSDEBUG
interface interface
uses uses
{$IFDEF FPSpreadDebug}
LazLogger,
{$ENDIF}
Classes, SysUtils, Classes, SysUtils,
laz2_xmlread, laz2_DOM, laz2_xmlread, laz2_DOM,
avglvltree, math, dateutils, contnrs, avglvltree, math, dateutils, contnrs,
@@ -2385,6 +2389,10 @@ var
fmt: PsCellFormat; fmt: PsCellFormat;
ns: String; ns: String;
begin begin
{$IFDEF FPSpreadDebug}
DebugLn(Format('ReadFormula: ARow=%d, ACol=%d, AStyleIndex=%d', [ARow, ACol, AStyleIndex]));
{$ENDIF}
// Create cell and apply format // Create cell and apply format
if FIsVirtualMode then if FIsVirtualMode then
begin begin
@@ -2453,6 +2461,14 @@ begin
// Because fpsspreadsheet supports references to other sheets which might // Because fpsspreadsheet supports references to other sheets which might
// not have been loaded at this moment, conversion to ExcelA1 dialect // not have been loaded at this moment, conversion to ExcelA1 dialect
// (used by fps) is postponed until all sheets are read. // (used by fps) is postponed until all sheets are read.
{$IFDEF FPSpreadDebug}
DebugLn(' Formula found: ' + formula);
(*
if SameText(formula, 'IsText({.B1])') then
formula := formula + '';
*)
{$ENDIF}
end; end;
// Read formula results // Read formula results
@@ -3519,6 +3535,12 @@ var
s: String; s: String;
colsSpanned, rowsSpanned: Integer; colsSpanned, rowsSpanned: Integer;
begin begin
{$IFDEF FPSpreadDebug}
DebugLn(Format('ReadCell: ARow=%d, ACol=%d, AFormatIndex=%d',
[ARow, ACol, AFormatIndex])
);
{$ENDIF}
// Workaround for Excel files converted to ods by Calc: These files are // Workaround for Excel files converted to ods by Calc: These files are
// expanded to fill the entire max worksheet. They also have single empty // expanded to fill the entire max worksheet. They also have single empty
// cell in the outermost cells --> don't write anything here to prevent this. // cell in the outermost cells --> don't write anything here to prevent this.

View File

@@ -1322,15 +1322,17 @@ begin
end; end;
rtBoolean : WriteBoolValue(ACell, res.ResBoolean); rtBoolean : WriteBoolValue(ACell, res.ResBoolean);
rtCell : begin rtCell : begin
cell := GetCell(res.ResRow, res.ResCol); // cell := GetCell(res.ResRow, res.ResCol);
case cell^.ContentType of cell := res.Worksheet.FindCell(res.ResRow, res.ResCol);
cctNumber : WriteNumber(ACell, cell^.NumberValue); if cell <> nil then
cctDateTime : WriteDateTime(ACell, cell^.DateTimeValue); case cell^.ContentType of
cctUTF8String: WriteText(ACell, cell^.UTF8StringValue); cctNumber : WriteNumber(ACell, cell^.NumberValue);
cctBool : WriteBoolValue(ACell, cell^.Boolvalue); cctDateTime : WriteDateTime(ACell, cell^.DateTimeValue);
cctError : WriteErrorValue(ACell, cell^.ErrorValue); cctUTF8String: WriteText(ACell, cell^.UTF8StringValue);
cctEmpty : WriteBlank(ACell); cctBool : WriteBoolValue(ACell, cell^.Boolvalue);
end; cctError : WriteErrorValue(ACell, cell^.ErrorValue);
cctEmpty : WriteBlank(ACell);
end;
end; end;
end; end;
finally finally

View File

@@ -75,7 +75,7 @@ type
protected protected
procedure PopulatePalette; override; procedure PopulatePalette; override;
{ Record writing methods } { Record writing methods }
procedure ReadBoundsheet(AStream: TStream); procedure ReadBOUNDSHEET(AStream: TStream);
procedure ReadDEFINEDNAME(AStream: TStream); procedure ReadDEFINEDNAME(AStream: TStream);
procedure ReadFONT(const AStream: TStream); procedure ReadFONT(const AStream: TStream);
procedure ReadFORMAT(AStream: TStream); override; procedure ReadFORMAT(AStream: TStream); override;
@@ -364,12 +364,13 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads a BOUNDSHEET record containing a worksheet name Reads a BOUNDSHEET record containing a worksheet name
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsSpreadBIFF5Reader.ReadBoundsheet(AStream: TStream); procedure TsSpreadBIFF5Reader.ReadBOUNDSHEET(AStream: TStream);
var var
len: Byte; len: Byte;
s: AnsiString; s: AnsiString;
sheetState: Byte; sheetState: Byte;
sheetData: TsSheetData; sheet: TsWorksheet;
//sheetData: TsSheetData;
begin begin
{ Absolute stream position of the BOF record of the sheet represented { Absolute stream position of the BOF record of the sheet represented
by this record } by this record }
@@ -387,11 +388,16 @@ begin
SetLength(s, len); SetLength(s, len);
AStream.ReadBuffer(s[1], len*SizeOf(AnsiChar)); AStream.ReadBuffer(s[1], len*SizeOf(AnsiChar));
sheet := FWorkbook.AddWorksheet(ConvertEncoding(s, FCodePage, EncodingUTF8), true);
if sheetState <> 0 then
sheet.Options := sheet.Options + [soHidden];
(*
{ Temporarily store parameters for worksheet in FSheetList } { Temporarily store parameters for worksheet in FSheetList }
sheetData := TsSheetData.Create; sheetData := TsSheetData.Create;
sheetData.Name := ConvertEncoding(s, FCodePage, EncodingUTF8); sheetData.Name := ConvertEncoding(s, FCodePage, EncodingUTF8);
sheetData.Hidden := sheetState <> 0; sheetData.Hidden := sheetState <> 0;
FSheetList.Add(sheetData); FSheetList.Add(sheetData);
*)
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@@ -503,13 +509,14 @@ var
SectionEOF: Boolean = False; SectionEOF: Boolean = False;
RecordType: Word; RecordType: Word;
CurStreamPos: Int64; CurStreamPos: Int64;
sheetData: TsSheetData; // sheetData: TsSheetData;
begin begin (*
sheetData := TsSheetData(FSheetList[FCurSheetIndex]); sheetData := TsSheetData(FSheetList[FCurSheetIndex]);
FWorksheet := FWorkbook.AddWorksheet(sheetData.Name, true); FWorksheet := FWorkbook.AddWorksheet(sheetData.Name, true);
if sheetData.Hidden then if sheetData.Hidden then
FWorksheet.Options := FWorksheet.Options + [soHidden]; FWorksheet.Options := FWorksheet.Options + [soHidden];
*)
FWorksheet := FWorkbook.GetWorksheetByIndex(FCurSheetIndex);
while (not SectionEOF) do while (not SectionEOF) do
begin begin
{ Read the record header } { Read the record header }

View File

@@ -65,6 +65,15 @@ uses
fpsutils; fpsutils;
type type
TExternBookKind = (ebkExternal, ebkInternal, ebkAddInFunc, ebkDDE_OLE);
TBIFF8ExternBook = class
Kind: TExternBookKind;
DocumentURL: String;
SheetNames: String;
function GetSheetName(AIndex: Integer): String;
end;
TBIFF8ExternSheet = packed record TBIFF8ExternSheet = packed record
ExternBookIndex: Word; ExternBookIndex: Word;
FirstSheetIndex: Word; FirstSheetIndex: Word;
@@ -80,6 +89,7 @@ type
FCommentPending: Boolean; FCommentPending: Boolean;
FCommentID: Integer; FCommentID: Integer;
FCommentLen: Integer; FCommentLen: Integer;
FBiff8ExternBooks: TFPObjectList;
FBiff8ExternSheets: array of TBiff8ExternSheet; FBiff8ExternSheets: array of TBiff8ExternSheet;
function ReadString(const AStream: TStream; const ALength: Word; function ReadString(const AStream: TStream; const ALength: Word;
out ARichTextParams: TsRichTextParams): String; out ARichTextParams: TsRichTextParams): String;
@@ -94,6 +104,7 @@ type
procedure ReadBOUNDSHEET(AStream: TStream); procedure ReadBOUNDSHEET(AStream: TStream);
procedure ReadCONTINUE(const AStream: TStream); procedure ReadCONTINUE(const AStream: TStream);
procedure ReadDEFINEDNAME(const AStream: TStream); procedure ReadDEFINEDNAME(const AStream: TStream);
procedure ReadEXTERNBOOK(const AStream: TStream);
procedure ReadEXTERNSHEET(const AStream: TStream); procedure ReadEXTERNSHEET(const AStream: TStream);
procedure ReadFONT(const AStream: TStream); procedure ReadFONT(const AStream: TStream);
procedure ReadFORMAT(AStream: TStream); override; procedure ReadFORMAT(AStream: TStream); override;
@@ -116,14 +127,18 @@ type
procedure ReadRPNCellRangeOffset(AStream: TStream; procedure ReadRPNCellRangeOffset(AStream: TStream;
out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer;
out AFlags: TsRelFlags); override; out AFlags: TsRelFlags); override;
procedure ReadRPNSheetIndex(AStream: TStream; out ASheet1, ASheet2: Integer); override;
procedure ReadRSTRING(AStream: TStream); procedure ReadRSTRING(AStream: TStream);
procedure ReadSST(const AStream: TStream); procedure ReadSST(const AStream: TStream);
function ReadString_8bitLen(AStream: TStream): String; override; function ReadString_8bitLen(AStream: TStream): String; override;
procedure ReadStringRecord(AStream: TStream); override; procedure ReadStringRecord(AStream: TStream); override;
procedure ReadTXO(const AStream: TStream); procedure ReadTXO(const AStream: TStream);
procedure ReadXF(const AStream: TStream);
protected
procedure ReadWorkbookGlobals(AStream: TStream); override; procedure ReadWorkbookGlobals(AStream: TStream); override;
procedure ReadWorksheet(AStream: TStream); override; procedure ReadWorksheet(AStream: TStream); override;
procedure ReadXF(const AStream: TStream);
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
destructor Destroy; override; destructor Destroy; override;
@@ -489,6 +504,27 @@ begin
end; end;
{ TBiff8ExternBook }
function TBiff8ExternBook.GetSheetName(AIndex: Integer): String;
var
L: TStrings;
begin
L := TStringList.Create;
try
L.Delimiter := #1;
L.StrictDelimiter := true;
L.DelimitedText := SheetNames;
if (AIndex >= 0) and (AIndex < L.Count) then
Result := L[AIndex]
else
Result := '';
finally
L.Free;
end;
end;
{ TsSpreadBIFF8Reader } { TsSpreadBIFF8Reader }
constructor TsSpreadBIFF8Reader.Create(AWorkbook: TsWorkbook); constructor TsSpreadBIFF8Reader.Create(AWorkbook: TsWorkbook);
@@ -502,6 +538,7 @@ var
j: Integer; j: Integer;
begin begin
SetLength(FBiff8ExternSheets, 0); SetLength(FBiff8ExternSheets, 0);
FBiff8ExternBooks.Free;
if Assigned(FSharedStringTable) then if Assigned(FSharedStringTable) then
begin begin
@@ -845,6 +882,7 @@ begin
INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream); INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream);
INT_EXCEL_ID_DEFINEDNAME : ReadDEFINEDNAME(AStream); INT_EXCEL_ID_DEFINEDNAME : ReadDEFINEDNAME(AStream);
INT_EXCEL_ID_EOF : SectionEOF := True; INT_EXCEL_ID_EOF : SectionEOF := True;
INT_EXCEL_ID_EXTERNBOOK : ReadEXTERNBOOK(AStream);
INT_EXCEL_ID_EXTERNSHEET : ReadEXTERNSHEET(AStream); INT_EXCEL_ID_EXTERNSHEET : ReadEXTERNSHEET(AStream);
INT_EXCEL_ID_FONT : ReadFont(AStream); INT_EXCEL_ID_FONT : ReadFont(AStream);
INT_EXCEL_ID_FORMAT : ReadFormat(AStream); INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
@@ -875,13 +913,15 @@ var
SectionEOF: Boolean = False; SectionEOF: Boolean = False;
RecordType: Word; RecordType: Word;
CurStreamPos: Int64; CurStreamPos: Int64;
sheetData: TsSheetData; // sheetData: TsSheetData;
begin begin
(*
sheetData := TsSheetData(FSheetList[FCurSheetIndex]); sheetData := TsSheetData(FSheetList[FCurSheetIndex]);
FWorksheet := FWorkbook.AddWorksheet(sheetData.Name, true); FWorksheet := FWorkbook.AddWorksheet(sheetData.Name, true);
if sheetData.Hidden then if sheetData.Hidden then
FWorksheet.Options := FWorksheet.Options + [soHidden]; FWorksheet.Options := FWorksheet.Options + [soHidden];
*)
FWorksheet := FWorkbook.GetWorksheetByIndex(FCurSheetIndex);
while (not SectionEOF) do while (not SectionEOF) do
begin begin
{ Read the record header } { Read the record header }
@@ -970,6 +1010,7 @@ var
len: Byte; len: Byte;
wideName: WideString; wideName: WideString;
rtParams: TsRichTextParams; rtParams: TsRichTextParams;
sheet: TsWorksheet;
sheetstate: Byte; sheetstate: Byte;
sheetdata: TsSheetData; sheetdata: TsSheetData;
begin begin
@@ -990,10 +1031,15 @@ begin
{ Read string with flags } { Read string with flags }
wideName := ReadWideString(AStream, len, rtParams); wideName := ReadWideString(AStream, len, rtParams);
sheet := FWorkbook.AddWorksheet(UTF8Encode(widename), true);
if sheetState <> 0 then
sheet.Options := sheet.Options + [soHidden];
(*
sheetData := TsSheetData.Create; sheetData := TsSheetData.Create;
sheetData.Name := UTF8Encode(wideName); sheetData.Name := UTF8Encode(wideName);
sheetData.Hidden := sheetState <> 0; sheetData.Hidden := sheetState <> 0;
FSheetList.Add(sheetdata); FSheetList.Add(sheetdata);
*)
end; end;
function TsSpreadBIFF8Reader.ReadString(const AStream: TStream; function TsSpreadBIFF8Reader.ReadString(const AStream: TStream;
@@ -1263,21 +1309,24 @@ end;
function TsSpreadBIFF8Reader.ReadRPNCellRange3D(AStream: TStream; function TsSpreadBIFF8Reader.ReadRPNCellRange3D(AStream: TStream;
var ARPNItem: PRPNItem): Boolean; var ARPNItem: PRPNItem): Boolean;
var var
sheetIndex: Integer; sheetIndex1, sheetIndex2: Integer;
r1, c1, r2, c2: Cardinal; r1, c1, r2, c2: Cardinal;
flags: TsRelFlags; flags: TsRelFlags;
begin begin
Result := true; Result := true;
sheetIndex := WordLEToN(AStream.ReadWord); ReadRPNSheetIndex(AStream, sheetIndex1, sheetIndex2);
if FBiff8ExternSheets[sheetIndex].ExternBookIndex <> 0 then if (sheetIndex1 = -1) or (sheetIndex2 = -1) then
exit(false); exit(False); // unsupported case
ReadRPNCellRangeAddress(AStream, r1, c1, r2, c2, flags); ReadRPNCellRangeAddress(AStream, r1, c1, r2, c2, flags);
if r2 = $FFFF then r2 := Cardinal(-1); if r2 = $FFFF then r2 := Cardinal(-1);
if c2 = $FF then c2 := Cardinal(-1); if c2 = $FF then c2 := Cardinal(-1);
ARPNItem := RPNCellRange3D( ARPNItem := RPNCellRange3D(
FBiff8ExternSheets[sheetIndex].FirstSheetIndex, r1, c1, sheetIndex1, r1, c1,
FBiff8ExternSheets[sheetIndex].LastSheetIndex, r2, c2, sheetIndex2, r2, c2,
flags, ARPNItem); flags, ARPNItem
);
end; end;
{ Reads the difference between row and column corner indexes of a cell range { Reads the difference between row and column corner indexes of a cell range
@@ -1311,6 +1360,31 @@ begin
if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2);
end; end;
procedure TsSpreadBIFF8Reader.ReadRPNSheetIndex(AStream: TStream;
out ASheet1, ASheet2: Integer);
var
refIndex: Word;
ref: TBiff8ExternSheet;
extbook: TBiff8ExternBook;
begin
// Index to REF entry in EXTERNSHEET record
refIndex := WordLEToN(AStream.ReadWord);
ref := FBiff8ExternSheets[refIndex];
extBook := FBiff8ExternBooks[ref.ExternBookIndex] as TBiff8ExternBook;
// Only links to internal sheets supported so far.
if extBook.Kind <> ebkInternal then
begin
ASheet1 := -1;
ASheet2 := -1;
exit;
end;
ASheet1 := ref.FirstSheetIndex;
ASheet2 := ref.LastSheetIndex;
end;
procedure TsSpreadBIFF8Reader.ReadRSTRING(AStream: TStream); procedure TsSpreadBIFF8Reader.ReadRSTRING(AStream: TStream);
var var
j, L: Word; j, L: Word;
@@ -1801,7 +1875,61 @@ begin
// Skip rest... // Skip rest...
end; end;
{ Reads an EXTERNSHEET record. Needed for named cells and print ranges. } procedure TsSpreadBIFF8Reader.ReadEXTERNBOOK(const AStream: TStream);
var
i, n: Integer;
url: widestring;
sheetnames: widestring;
externbook: TBiff8Externbook;
p: Int64;
t: array[0..1] of byte;
begin
if FBiff8ExternBooks = nil then
FBiff8ExternBooks := TFPObjectList.Create(true);
externBook := TBiff8ExternBook.Create;
// Count of sheets in book
n := WordLEToN(AStream.ReadWord);
// Determine type of book
p := AStream.Position;
AStream.ReadBuffer(t[0], 2);
if (t[0] = 1) and (t[1] = 4) then
externbook.Kind := ebkInternal
else
if (t[0] = 1) and (t[1] = $3A) then
externbook.Kind := ebkAddInFunc
else
if n = 0 then
externbook.Kind := ebkDDE_OLE
else
externbook.Kind := ebkExternal;
if (externbook.Kind = ebkExternal) then
begin
AStream.Position := p;
// Encoded URL without sheet name (Unicode string, 16bit string length)
url := ReadWideString(AStream, false);
externbook.DocumentURL := UTF8Encode(url);
if n = 0 then
sheetnames := ''
else begin
// Sheet names (Unicode strings with 16bit string length)
sheetnames := UTF8Encode(ReadWideString(AStream, false));
for i := 2 to n do
sheetnames := sheetnames + #1 + UTF8Encode(ReadWideString(AStream, false));
end;
externbook.SheetNames := sheetNames;
end;
FBiff8ExternBooks.Add(externbook);
end;
{ Reads an EXTERNSHEET record. Needed for 3d-references, named cells and
print ranges. }
procedure TsSpreadBIFF8Reader.ReadEXTERNSHEET(const AStream: TStream); procedure TsSpreadBIFF8Reader.ReadEXTERNSHEET(const AStream: TStream);
var var
numItems: Word; numItems: Word;

View File

@@ -395,7 +395,7 @@ type
FCurSheetIndex: Integer; FCurSheetIndex: Integer;
FActivePane: Integer; FActivePane: Integer;
FExternSheets: TStrings; FExternSheets: TStrings;
FSheetList: TFPList; // FSheetList: TFPList;
procedure AddBuiltinNumFormats; override; procedure AddBuiltinNumFormats; override;
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual;
@@ -485,6 +485,7 @@ type
out AFlags: TsRelFlags); virtual; out AFlags: TsRelFlags); virtual;
function ReadRPNFunc(AStream: TStream): Word; virtual; function ReadRPNFunc(AStream: TStream): Word; virtual;
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual; procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual;
procedure ReadRPNSheetIndex(AStream: TStream; out ASheet1, ASheet2: Integer); virtual;
function ReadRPNTokenArray(AStream: TStream; ACell: PCell; function ReadRPNTokenArray(AStream: TStream; ACell: PCell;
ASharedFormulaBase: PCell = nil): Boolean; overload; ASharedFormulaBase: PCell = nil): Boolean; overload;
function ReadRPNTokenArray(AStream: TStream; ARpnTokenArraySize: Word; function ReadRPNTokenArray(AStream: TStream; ARpnTokenArraySize: Word;
@@ -505,11 +506,12 @@ type
procedure ReadWindow2(AStream: TStream); virtual; procedure ReadWindow2(AStream: TStream); virtual;
// Read WINDOWPROTECT record // Read WINDOWPROTECT record
procedure ReadWindowProtect(AStream: TStream); procedure ReadWindowProtect(AStream: TStream);
protected
procedure InternalReadFromStream(AStream: TStream);
procedure ReadWorkbookGlobals(AStream: TStream); virtual; procedure ReadWorkbookGlobals(AStream: TStream); virtual;
procedure ReadWorksheet(AStream: TStream); virtual; procedure ReadWorksheet(AStream: TStream); virtual;
procedure InternalReadFromStream(AStream: TStream);
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
destructor Destroy; override; destructor Destroy; override;
@@ -976,7 +978,7 @@ constructor TsSpreadBIFFReader.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create(AWorkbook); inherited Create(AWorkbook);
FSheetList := TFPList.Create; // FSheetList := TFPList.Create;
FPalette := TsPalette.Create; FPalette := TsPalette.Create;
PopulatePalette; PopulatePalette;
@@ -1006,9 +1008,9 @@ var
begin begin
for j:=0 to FDefinedNames.Count-1 do TObject(FDefinedNames[j]).Free; for j:=0 to FDefinedNames.Count-1 do TObject(FDefinedNames[j]).Free;
FDefinedNames.Free; FDefinedNames.Free;
{
for j:= 0 to FSheetList.Count-1 do TObject(FSheetList[j]).Free; for j:= 0 to FSheetList.Count-1 do TObject(FSheetList[j]).Free;
FSheetList.Free; FSheetList.Free; }
FExternSheets.Free; FExternSheets.Free;
FPalette.Free; FPalette.Free;
@@ -2471,6 +2473,17 @@ begin
ACol := WordLEToN(AStream.ReadWord); ACol := WordLEToN(AStream.ReadWord);
end; end;
{@@ ----------------------------------------------------------------------------
Reads the indexes of the first and last worksheet of a cell reference used
in a formula from the current stream position.
Place holder - must be overridden! }
procedure TsSpreadBIFFReader.ReadRPNSheetIndex(AStream: TStream;
out ASheet1, ASheet2: Integer);
begin
ASheet1 := -1;
ASheet2 := -1;
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, converts it to a string formula and stores it in the cell. rpn formula, converts it to a string formula and stores it in the cell.
@@ -2666,6 +2679,7 @@ var
funcCode: Word; funcCode: Word;
b: Byte; b: Byte;
found: Boolean; found: Boolean;
sheet1, sheet2: Integer;
begin begin
rpnItem := nil; rpnItem := nil;
p0 := AStream.Position; p0 := AStream.Position;
@@ -2716,6 +2730,21 @@ begin
INT_EXCEL_TOKEN_TREFN_R: rpnItem := RPNCellRef(r, c, flags, rpnItem); INT_EXCEL_TOKEN_TREFN_R: rpnItem := RPNCellRef(r, c, flags, rpnItem);
end; end;
end; end;
INT_EXCEL_TOKEN_TREF3D_R, INT_EXCEL_TOKEN_TREF3d_V:
begin
ReadRPNSheetIndex(AStream, sheet1, sheet2);
ReadRPNCellAddressOffset(AStream, dr, dc, flags);
if (rfRelRow in flags)
then r := LongInt(ACell^.Row) + dr
else r := dr;
if (rfRelCol in flags)
then c := LongInt(ACell^.Col) + dc
else c := dc;
case token of
INT_EXCEL_TOKEN_TREF3D_V: rpnItem := RpnCellValue3D(sheet1, r, c, flags, rpnItem);
INT_EXCEL_TOKEN_TREF3D_R: rpnItem := RpnCellRef3D(sheet1, r, c, flags, rpnItem);
end;
end;
INT_EXCEL_TOKEN_TAREA3D_R: INT_EXCEL_TOKEN_TAREA3D_R:
begin begin
if not ReadRPNCellRange3D(AStream, rpnItem) then supported := false; if not ReadRPNCellRange3D(AStream, rpnItem) then supported := false;
@@ -3046,6 +3075,7 @@ var
BIFFEOF: Boolean; BIFFEOF: Boolean;
i: Integer; i: Integer;
sheet: TsWorksheet; sheet: TsWorksheet;
numsheets: Integer;
begin begin
// Check if the operation succeeded // Check if the operation succeeded
if AStream.Size = 0 then if AStream.Size = 0 then
@@ -3060,6 +3090,7 @@ begin
{ Read workbook globals } { Read workbook globals }
ReadWorkbookGlobals(AStream); ReadWorkbookGlobals(AStream);
numSheets := FWorkbook.GetWorksheetCount;
{ Check for the end of the file } { Check for the end of the file }
if AStream.Position >= AStream.Size then if AStream.Position >= AStream.Size then
@@ -3078,12 +3109,12 @@ begin
inc(FCurSheetIndex); inc(FCurSheetIndex);
// It can happen in files written by Office97 that the OLE directory is // It can happen in files written by Office97 that the OLE directory is
// at the end of the file. // at the end of the file.
if FCurSheetIndex = FSheetList.Count then if FCurSheetIndex = numSheets then
BIFFEOF := true; BIFFEOF := true;
end; end;
{ Extract print ranges, repeated rows/cols } { Extract print ranges, repeated rows/cols }
for i:=0 to FWorkbook.GetWorksheetCount-1 do begin for i := 0 to numSheets - 1 do begin
sheet := FWorkbook.GetWorksheetByIndex(i); sheet := FWorkbook.GetWorksheetByIndex(i);
FixDefinedNames(sheet); FixDefinedNames(sheet);
ExtractPrintRanges(sheet); ExtractPrintRanges(sheet);

View File

@@ -23,6 +23,10 @@
</CompilerMessages> </CompilerMessages>
<CustomOptions Value="$(IDEBuildOptions) <CustomOptions Value="$(IDEBuildOptions)
-dDisableWrapperFunctions"/> -dDisableWrapperFunctions"/>
<OtherDefines Count="2">
<Define0 Value="DisableWrapperFunctions"/>
<Define1 Value="FPSDebug"/>
</OtherDefines>
</Other> </Other>
</CompilerOptions> </CompilerOptions>
<Description Value="laz_fpspreadsheet is a non-visual component that allows you to use the fpspreadsheet package to read/write spreadsheet files in .xls (BIFF/Excel), .ods OpenDocument (LibreOffice/OpenOffice) and .xlsx Open XML (Excel) formats. <Description Value="laz_fpspreadsheet is a non-visual component that allows you to use the fpspreadsheet package to read/write spreadsheet files in .xls (BIFF/Excel), .ods OpenDocument (LibreOffice/OpenOffice) and .xlsx Open XML (Excel) formats.

View File

@@ -35,6 +35,7 @@ type
UseRPNFormula: Boolean); UseRPNFormula: Boolean);
procedure Test_Write_Read_CalcFormulas(AFormat: TsSpreadsheetformat; procedure Test_Write_Read_CalcFormulas(AFormat: TsSpreadsheetformat;
UseRPNFormula: Boolean); UseRPNFormula: Boolean);
procedure Test_Write_Read_Calc3DFormulas(AFormat: TsSpreadsheetFormat);
published published
// Writes out formulas & reads them back. // Writes out formulas & reads them back.
@@ -74,6 +75,12 @@ type
{ ODS Tests } { ODS Tests }
procedure Test_Write_Read_CalcStringFormula_ODS; procedure Test_Write_Read_CalcStringFormula_ODS;
{ Formulas with 3D references to other sheets }
procedure Test_Write_Read_Calc3DFormula_BIFF8;
procedure Test_Write_Read_Calc3DFormula_OOXML;
procedure Test_Write_Read_Calc3DFormula_ODS;
end; end;
implementation implementation
@@ -681,6 +688,143 @@ begin
end; end;
*) *)
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_Calc3DFormulas(
AFormat: TsSpreadsheetFormat);
{ If UseRPNFormula is TRUE, the test formulas are generated from RPN syntax,
otherwise string formulas are used. }
var
sheet1, sheet2, sheet3: TsWorksheet;
workbook: TsWorkbook;
row: Integer;
tempFile: string; //write xls/xml to this file and read back from it
actual: TsExpressionResult;
expected: TsExpressionResult;
cell: PCell;
sollValues: array of TsExpressionResult;
formula: String;
ErrorMargin: Double;
begin
ErrorMargin:=0; //1.44E-7;
//1.44E-7 for SUMSQ formula
//6.0E-8 for SUM formula
//4.8E-8 for MAX formula
//2.4E-8 for now formula
//about 1E-15 is needed for some trig functions
// Create test workbook
workbook := TsWorkbook.Create;
try
workbook.Options := workbook.Options + [boCalcBeforeSaving];
sheet1 := workBook.AddWorksheet('Sheet1');
sheet2 := workbook.AddWorksheet('Sheet2');
sheet3 := workbook.AddWorksheet('Sheet3');
{ Write out test formulas.
This include file creates various formulas in column A and stores
the expected results in the array SollValues. }
Row := 0;
TempFile := GetTempFileName;
{$I testcases_calc3dformula.inc}
workbook.WriteToFile(TempFile, AFormat, true);
finally
workbook.Free;
end;
// Open the workbook
workbook := TsWorkbook.Create;
try
workbook.Options := workbook.Options + [boReadFormulas];
workbook.ReadFromFile(TempFile, AFormat);
if AFormat = sfExcel2 then
Fail('This test should not be executed')
else
sheet1 := workbook.GetWorksheetByName('Sheet1');
if sheet1=nil then
Fail('Error in test code. Failed to get named worksheet');
for row := 0 to sheet1.GetLastRowIndex do
begin
cell := sheet1.FindCell(Row, 1);
if (cell = nil) then
fail('Error in test code: Failed to get cell ' + CellNotation(sheet1, Row, 1));
case cell^.ContentType of
cctBool : actual := BooleanResult(cell^.BoolValue);
cctNumber : actual := FloatResult(cell^.NumberValue);
cctDateTime : actual := DateTimeResult(cell^.DateTimeValue);
cctUTF8String : actual := StringResult(cell^.UTF8StringValue);
cctError : actual := ErrorResult(cell^.ErrorValue);
cctEmpty : actual := EmptyResult;
else fail('ContentType not supported');
end;
expected := SollValues[row];
// Cell does not store integers!
if expected.ResultType = rtInteger then expected := FloatResult(expected.ResInteger);
(*
// The now function result is volatile, i.e. changes continuously. The
// time for the soll value was created such that we can expect to have
// the file value in the same second. Therefore we neglect the milliseconds.
if formula = '=NOW()' then begin
// Round soll value to seconds
DecodeTime(expected.ResDateTime, hr,min,sec,msec);
expected.ResDateTime := EncodeTime(hr, min, sec, 0);
// Round formula value to seconds
DecodeTime(actual.ResDateTime, hr,min,sec,msec);
actual.ResDateTime := EncodeTime(hr,min,sec,0);
end; *)
case actual.ResultType of
rtBoolean:
CheckEquals(BoolToStr(expected.ResBoolean), BoolToStr(actual.ResBoolean),
'Test read calculated formula result mismatch, cell '+CellNotation(sheet1, Row, 1));
rtFloat:
{$if (defined(mswindows)) or (FPC_FULLVERSION>=20701)}
// FPC 2.6.x and trunk on Windows need this, also FPC trunk on Linux x64
CheckEquals(expected.ResFloat, actual.ResFloat, ErrorMargin,
'Test read calculated formula result mismatch, cell '+CellNotation(sheet1, Row, 1));
{$else}
// Non-Windows: test without error margin
CheckEquals(expected.ResFloat, actual.ResFloat,
'Test read calculated formula result mismatch, cell '+CellNotation(sheet1, Row, 1));
{$endif}
rtString:
CheckEquals(expected.ResString, actual.ResString,
'Test read calculated formula result mismatch, cell '+CellNotation(sheet1, Row, 1));
rtError:
CheckEquals(
GetEnumName(TypeInfo(TsErrorValue), ord(expected.ResError)),
GetEnumname(TypeInfo(TsErrorValue), ord(actual.ResError)),
'Test read calculated formula error value mismatch, cell '+CellNotation(sheet1, Row, 1));
end;
end;
finally
workbook.Free;
DeleteFile(TempFile);
end;
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_Calc3DFormula_BIFF8;
begin
Test_Write_Read_Calc3DFormulas(sfExcel8);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_Calc3DFormula_OOXML;
begin
Test_Write_Read_Calc3DFormulas(sfOOXML);
end;
procedure TSpreadWriteReadFormulaTests.Test_Write_Read_Calc3DFormula_ODS;
begin
Test_Write_Read_Calc3DFormulas(sfOpenDocument);
end;
initialization initialization
// Register so these tests are included in a full run // Register so these tests are included in a full run
RegisterTest(TSpreadWriteReadFormulaTests); RegisterTest(TSpreadWriteReadFormulaTests);

View File

@@ -38,7 +38,7 @@
<PackageName Value="FCL"/> <PackageName Value="FCL"/>
</Item4> </Item4>
</RequiredPackages> </RequiredPackages>
<Units Count="28"> <Units Count="29">
<Unit0> <Unit0>
<Filename Value="spreadtestgui.lpr"/> <Filename Value="spreadtestgui.lpr"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
@@ -152,6 +152,10 @@
<Filename Value="ssttests.pas"/> <Filename Value="ssttests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
</Unit27> </Unit27>
<Unit28>
<Filename Value="testcases_calc3dformula.inc"/>
<IsPartOfProject Value="True"/>
</Unit28>
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>

View File

@@ -6,7 +6,8 @@ program spreadtestgui;
uses uses
{$IFDEF HEAPTRC} {$IFDEF HEAPTRC}
HeapTrc, SysUtils, //HeapTrc,
SysUtils,
{$ENDIF} {$ENDIF}
Interfaces, Forms, GuiTestRunner, datetests, stringtests, numberstests, Interfaces, Forms, GuiTestRunner, datetests, stringtests, numberstests,
manualtests, testsutility, internaltests, formattests, colortests, fonttests, manualtests, testsutility, internaltests, formattests, colortests, fonttests,

View File

@@ -0,0 +1,81 @@
{ include file for "formulatests.pas", containing the test cases for the
calc3dformula test. }
// Setting up some test numbers
sheet1.WriteText(0, 4, 'abc'); // E1 = 'abc'
sheet1.WriteNumber(1, 5, 12.0); // F2 = 12.0
sheet2.WriteText(2, 1, 'A'); // B3 = 'A'
sheet2.WriteNumber(1, 4, 1.0); // E2 = 1.0
sheet3.WriteText(1, 2, 'B'); // C2 = 'B'
sheet3.WriteNumber(1, 1, 2.0); // B2 = 2.0
//------------------------------------------------------------------------------
Row := 0;
formula := 'Sheet2!B3'; { A1 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('A');
inc(Row);
formula := 'Sheet2!B3&Sheet3!C2'; { A2 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('AB');
inc(Row);
formula := 'Sheet2!E2'; { A3 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := FloatResult(1.0);
inc(Row);
formula := 'Sheet2!E2+Sheet3!B2'; { A4 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := FloatResult(3.0);
inc(Row);
formula := 'E1&Sheet2!B3'; { A5 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, 'E1&Sheet2!B3');
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('abcA');
inc(Row); { A6 }
formula := 'F2-Sheet2!E2-11';
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := FloatResult(0.0);
inc(Row);
formula := 'Sheet2!$B$3'; { A7 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('A');
inc(Row);
formula := 'Sheet2!B$3&Sheet3!$C2'; { A8 }
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, formula);
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('AB');
{
inc(Row);
formula := 'D1&Sheet2!B3%"BC"';
sheet1.WriteText(Row, 0, formula);
sheet1.WriteFormula(Row, 1, 'D1&Sheet2!B3%"BC"');
SetLength(SollValues, Row+1);
SollValues[Row] := StringResult('abcABC');
}