fpspreadsheet: Improved reading of results of functions returning strings, booleans, or errors. Optional tests in test suite producing an error when Excel reads the test file.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3050 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-05-16 08:17:49 +00:00
parent e21231de03
commit 47d624ab46
6 changed files with 167 additions and 128 deletions

View File

@ -140,8 +140,7 @@
<Unit2>
<Filename Value="..\..\fpspreadsheet.pas"/>
<UnitName Value="fpspreadsheet"/>
<IsVisibleTab Value="True"/>
<EditorIndex Value="4"/>
<EditorIndex Value="3"/>
<WindowIndex Value="0"/>
<TopLine Value="2655"/>
<CursorPos X="3" Y="2680"/>
@ -151,10 +150,10 @@
<Unit3>
<Filename Value="..\..\fpspreadsheetgrid.pas"/>
<UnitName Value="fpspreadsheetgrid"/>
<EditorIndex Value="3"/>
<EditorIndex Value="2"/>
<WindowIndex Value="0"/>
<TopLine Value="1774"/>
<CursorPos X="26" Y="1785"/>
<CursorPos X="23" Y="1783"/>
<UsageCount Value="100"/>
<Loaded Value="True"/>
</Unit3>
@ -232,7 +231,7 @@
<Unit13>
<Filename Value="..\..\fpsutils.pas"/>
<UnitName Value="fpsutils"/>
<EditorIndex Value="9"/>
<EditorIndex Value="8"/>
<WindowIndex Value="0"/>
<TopLine Value="37"/>
<CursorPos X="1" Y="21"/>
@ -264,10 +263,11 @@
<Unit17>
<Filename Value="..\..\xlsbiff8.pas"/>
<UnitName Value="xlsbiff8"/>
<EditorIndex Value="6"/>
<IsVisibleTab Value="True"/>
<EditorIndex Value="5"/>
<WindowIndex Value="0"/>
<TopLine Value="62"/>
<CursorPos X="15" Y="90"/>
<TopLine Value="1727"/>
<CursorPos X="1" Y="1744"/>
<UsageCount Value="84"/>
<Loaded Value="True"/>
</Unit17>
@ -289,30 +289,30 @@
<Unit20>
<Filename Value="..\..\xlscommon.pas"/>
<UnitName Value="xlscommon"/>
<EditorIndex Value="5"/>
<EditorIndex Value="4"/>
<WindowIndex Value="0"/>
<TopLine Value="588"/>
<CursorPos X="61" Y="592"/>
<TopLine Value="974"/>
<CursorPos X="21" Y="990"/>
<UsageCount Value="80"/>
<Loaded Value="True"/>
</Unit20>
<Unit21>
<Filename Value="..\..\xlsbiff5.pas"/>
<UnitName Value="xlsbiff5"/>
<EditorIndex Value="7"/>
<EditorIndex Value="6"/>
<WindowIndex Value="0"/>
<TopLine Value="1363"/>
<CursorPos X="1" Y="1364"/>
<TopLine Value="76"/>
<CursorPos X="49" Y="92"/>
<UsageCount Value="67"/>
<Loaded Value="True"/>
</Unit21>
<Unit22>
<Filename Value="..\..\xlsbiff2.pas"/>
<UnitName Value="xlsbiff2"/>
<EditorIndex Value="8"/>
<EditorIndex Value="7"/>
<WindowIndex Value="0"/>
<TopLine Value="664"/>
<CursorPos X="21" Y="677"/>
<TopLine Value="548"/>
<CursorPos X="1" Y="560"/>
<UsageCount Value="68"/>
<Loaded Value="True"/>
</Unit22>
@ -372,12 +372,10 @@
</Unit29>
<Unit30>
<Filename Value="d:\lazarus-svn\lcl\include\control.inc"/>
<EditorIndex Value="2"/>
<WindowIndex Value="0"/>
<TopLine Value="2696"/>
<CursorPos X="23" Y="2712"/>
<UsageCount Value="10"/>
<Loaded Value="True"/>
</Unit30>
<Unit31>
<Filename Value="..\..\fpspreadsheetchart.pas"/>
@ -593,123 +591,123 @@
<JumpHistory Count="30" HistoryIndex="29">
<Position1>
<Filename Value="mainform.pas"/>
<Caret Line="633" Column="1" TopLine="631"/>
<Caret Line="346" Column="1" TopLine="336"/>
</Position1>
<Position2>
<Filename Value="mainform.pas"/>
<Caret Line="638" Column="43" TopLine="619"/>
<Caret Line="354" Column="1" TopLine="336"/>
</Position2>
<Position3>
<Filename Value="mainform.pas"/>
<Caret Line="633" Column="43" TopLine="619"/>
<Caret Line="346" Column="8" TopLine="336"/>
</Position3>
<Position4>
<Filename Value="mainform.pas"/>
<Caret Line="195" Column="1" TopLine="168"/>
<Caret Line="343" Column="1" TopLine="336"/>
</Position4>
<Position5>
<Filename Value="mainform.pas"/>
<Caret Line="643" Column="25" TopLine="631"/>
<Caret Line="346" Column="1" TopLine="336"/>
</Position5>
<Position6>
<Filename Value="mainform.pas"/>
<Caret Line="640" Column="1" TopLine="631"/>
<Caret Line="347" Column="1" TopLine="336"/>
</Position6>
<Position7>
<Filename Value="mainform.pas"/>
<Caret Line="342" Column="3" TopLine="337"/>
<Caret Line="348" Column="1" TopLine="336"/>
</Position7>
<Position8>
<Filename Value="mainform.pas"/>
<Caret Line="343" Column="1" TopLine="336"/>
<Caret Line="349" Column="1" TopLine="336"/>
</Position8>
<Position9>
<Filename Value="mainform.pas"/>
<Caret Line="346" Column="1" TopLine="336"/>
<Caret Line="350" Column="1" TopLine="336"/>
</Position9>
<Position10>
<Filename Value="mainform.pas"/>
<Caret Line="354" Column="1" TopLine="336"/>
<Caret Line="351" Column="1" TopLine="336"/>
</Position10>
<Position11>
<Filename Value="mainform.pas"/>
<Caret Line="346" Column="8" TopLine="336"/>
<Caret Line="354" Column="1" TopLine="336"/>
</Position11>
<Position12>
<Filename Value="mainform.pas"/>
<Caret Line="343" Column="1" TopLine="336"/>
<Caret Line="640" Column="1" TopLine="621"/>
</Position12>
<Position13>
<Filename Value="mainform.pas"/>
<Caret Line="346" Column="1" TopLine="336"/>
<Caret Line="641" Column="1" TopLine="621"/>
</Position13>
<Position14>
<Filename Value="mainform.pas"/>
<Caret Line="347" Column="1" TopLine="336"/>
<Caret Line="343" Column="1" TopLine="324"/>
</Position14>
<Position15>
<Filename Value="mainform.pas"/>
<Caret Line="348" Column="1" TopLine="336"/>
<Caret Line="632" Column="30" TopLine="621"/>
</Position15>
<Position16>
<Filename Value="mainform.pas"/>
<Caret Line="349" Column="1" TopLine="336"/>
<Caret Line="633" Column="30" TopLine="622"/>
</Position16>
<Position17>
<Filename Value="mainform.pas"/>
<Caret Line="350" Column="1" TopLine="336"/>
<Caret Line="634" Column="30" TopLine="623"/>
</Position17>
<Position18>
<Filename Value="mainform.pas"/>
<Caret Line="351" Column="1" TopLine="336"/>
<Caret Line="635" Column="30" TopLine="624"/>
</Position18>
<Position19>
<Filename Value="mainform.pas"/>
<Caret Line="354" Column="1" TopLine="336"/>
<Caret Line="636" Column="30" TopLine="625"/>
</Position19>
<Position20>
<Filename Value="mainform.pas"/>
<Caret Line="640" Column="1" TopLine="621"/>
<Caret Line="637" Column="30" TopLine="626"/>
</Position20>
<Position21>
<Filename Value="mainform.pas"/>
<Caret Line="641" Column="1" TopLine="621"/>
<Caret Line="638" Column="30" TopLine="627"/>
</Position21>
<Position22>
<Filename Value="mainform.pas"/>
<Caret Line="343" Column="1" TopLine="324"/>
</Position22>
<Position23>
<Filename Value="mainform.pas"/>
<Caret Line="632" Column="30" TopLine="621"/>
</Position23>
<Position24>
<Filename Value="mainform.pas"/>
<Caret Line="633" Column="30" TopLine="622"/>
</Position24>
<Position25>
<Filename Value="mainform.pas"/>
<Caret Line="634" Column="30" TopLine="623"/>
</Position25>
<Position26>
<Filename Value="mainform.pas"/>
<Caret Line="635" Column="30" TopLine="624"/>
</Position26>
<Position27>
<Filename Value="mainform.pas"/>
<Caret Line="636" Column="30" TopLine="625"/>
</Position27>
<Position28>
<Filename Value="mainform.pas"/>
<Caret Line="637" Column="30" TopLine="626"/>
</Position28>
<Position29>
<Filename Value="mainform.pas"/>
<Caret Line="638" Column="30" TopLine="627"/>
</Position29>
<Position30>
<Filename Value="..\..\xlscommon.pas"/>
<Caret Line="592" Column="61" TopLine="588"/>
</Position22>
<Position23>
<Filename Value="..\..\xlsbiff8.pas"/>
<Caret Line="89" Column="48" TopLine="62"/>
</Position23>
<Position24>
<Filename Value="..\..\xlsbiff8.pas"/>
<Caret Line="1751" Column="16" TopLine="1743"/>
</Position24>
<Position25>
<Filename Value="..\..\xlscommon.pas"/>
<Caret Line="1237" Column="67" TopLine="1222"/>
</Position25>
<Position26>
<Filename Value="..\..\xlsbiff2.pas"/>
<Caret Line="73" Column="48" TopLine="61"/>
</Position26>
<Position27>
<Filename Value="..\..\xlsbiff8.pas"/>
<Caret Line="89" Column="29" TopLine="73"/>
</Position27>
<Position28>
<Filename Value="..\..\xlscommon.pas"/>
<Caret Line="365" Column="16" TopLine="353"/>
</Position28>
<Position29>
<Filename Value="..\..\xlsbiff5.pas"/>
<Caret Line="1379" Column="1" TopLine="1363"/>
</Position29>
<Position30>
<Filename Value="..\..\xlsbiff8.pas"/>
<Caret Line="1754" Column="1" TopLine="1742"/>
</Position30>
</JumpHistory>
</ProjectOptions>

View File

@ -1941,4 +1941,56 @@ begin
RPNFunc(fekSUBSTITUTE, 3,
nil))))));
Worksheet.WriteUTF8Text(Row, 2, 'He..o Wor.d!');
inc(Row, 2);
Worksheet.WriteUTF8Text(Row, 0, 'Errors');
Worksheet.WriteUsedFormatting(Row, 0, [uffBold]);
// Division by 0
// These tests partly produce an error messsage when the file is read by Excel.
// In order to avoid confusion they are deactivated by default.
// Remove the comment below to see these tests.
(*
inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=1/0');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(1,
RPNNumber(0,
RPNFunc(fekDiv,
nil)))));
Worksheet.WriteUTF8Text(Row, 2, 'Error #DIV/0!');
// Not enough operands for operation
inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=1/2 ("2" forgotten from formula)" ');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(1,
// here we'd normally put "RPNFormula(2," - but we "forget" it...
RPNFunc(fekDiv,
nil))));
Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A"');
// Too many operands given
inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=1/2 (too many operands)');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(1,
RPNNumber(2,
RPNNumber(3, // This line is too much
RPNFunc(fekDiv,
nil))))));
Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A!');
// Parameter count not specified
inc(Row);
Worksheet.WriteUTF8Text(Row, 0, '=SUM(1, 2) (parameter count not specified');
Worksheet.WriteRPNFormula(Row, 1, CreateRPNFormula(
RPNNumber(1,
RPNNumber(2,
RPNFunc(fekSum, // We "forget" to specify the number of arguments
nil)))));
Worksheet.WriteUTF8Text(Row, 2, 'Error #N/A');
*)
end;

View File

@ -70,7 +70,7 @@ type
procedure ReadNumber(AStream: TStream); override;
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
procedure ReadRowInfo(AStream: TStream); override;
procedure ReadStringRecord(AStream: TStream; var AStringResult: String); override;
procedure ReadStringRecord(AStream: TStream); override;
procedure ReadWindow2(AStream: TStream); override;
procedure ReadXF(AStream: TStream);
public
@ -405,6 +405,7 @@ begin
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_COLWIDTH : ReadColWidth(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
@ -547,27 +548,22 @@ begin
end;
{ Reads a STRING record which contains the result of string formula. }
procedure TsSpreadBIFF2Reader.ReadStringRecord(AStream: TStream;
var AStringResult: String);
procedure TsSpreadBIFF2Reader.ReadStringRecord(AStream: TStream);
var
record_id: Word;
record_size: word;
len: Byte;
s: ansistring;
begin
record_id := WordLEToN(AStream.ReadWord);
if record_id <> INT_EXCEL_ID_STRING then
raise Exception.Create('ReadStringRecord: wrong record found.');
record_size := WordLEToN(AStream.ReadWord);
// The string is a byte-string with 16 bit length
len := AStream.ReadByte;
if len > 0 then begin
SetLength(s, Len);
AStream.ReadBuffer(s[1], len);
end else
s := '';
AStringResult := s;
if (FIncompleteCell <> nil) and (s <> '') then begin
FIncompleteCell^.UTF8StringValue := s;
FIncompleteCell^.ContentType := cctUTF8String;
end;
end;
FIncompleteCell := nil;
end;
{ Reads the WINDOW2 record containing information like "show grid lines",

View File

@ -89,7 +89,7 @@ type
procedure ReadWorksheet(AStream: TStream; AData: TsWorkbook);
procedure ReadBoundsheet(AStream: TStream);
procedure ReadRichString(AStream: TStream);
procedure ReadStringRecord(AStream: TStream; var AStringResult: String); override;
procedure ReadStringRecord(AStream: TStream); override;
procedure ReadXF(AStream: TStream);
public
{ General reading methods }
@ -1256,6 +1256,7 @@ begin
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
INT_EXCEL_ID_PANE : ReadPane(AStream);
INT_EXCEL_ID_BOF : ;
@ -1362,27 +1363,22 @@ begin
end;
{ Reads a STRING record which contains the result of string formula. }
procedure TsSpreadBIFF5Reader.ReadStringRecord(AStream: TStream;
var AStringResult: String);
procedure TsSpreadBIFF5Reader.ReadStringRecord(AStream: TStream);
var
record_id: Word;
record_size: word;
len: Word;
s: ansistring;
begin
record_id := WordLEToN(AStream.ReadWord);
if record_id <> INT_EXCEL_ID_STRING then
raise Exception.Create('ReadStringRecord: wrong record found.');
record_size := WordLEToN(AStream.ReadWord);
// The string is a byte-string with 16 bit length
len := WordLEToN(AStream.ReadWord);
if len > 0 then begin
SetLength(s, Len);
AStream.ReadBuffer(s[1], len);
end else
s := '';
AStringResult := s;
if (FIncompleteCell <> nil) and (s <> '') then begin
FIncompleteCell^.UTF8StringValue := s;
FIncompleteCell^.ContentType := cctUTF8String;
end;
end;
FIncompleteCell := nil;
end;
procedure TsSpreadBIFF5Reader.ReadFromFile(AFileName: string; AData: TsWorkbook);

View File

@ -86,7 +86,7 @@ type
procedure ReadLabelSST(const AStream: TStream);
procedure ReadRichString(const AStream: TStream);
procedure ReadSST(const AStream: TStream);
procedure ReadStringRecord(AStream: TStream; var AStringResult: String); override;
procedure ReadStringRecord(AStream: TStream); override;
procedure ReadXF(const AStream: TStream);
public
destructor Destroy; override;
@ -1462,6 +1462,7 @@ begin
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
//(RSTRING) This record stores a formatted text cell (Rich-Text).
// In BIFF8 it is usually replaced by the LABELSST record. Excel still
// uses this record, if it copies formatted text cells to the clipboard.
@ -1740,18 +1741,16 @@ begin
ApplyCellFormatting(ARow, ACol, XF);
end;
procedure TsSpreadBIFF8Reader.ReadStringRecord(AStream: TStream;
var AStringResult: String);
procedure TsSpreadBIFF8Reader.ReadStringRecord(AStream: TStream);
var
record_id: Word;
record_size: word;
s: String;
begin
record_id := WordLEToN(AStream.ReadWord);
if record_id <> INT_EXCEL_ID_STRING then
raise Exception.Create('ReadStringRecord: wrong record found.');
record_size := WordLEToN(AStream.ReadWord);
AStringResult := ReadWideString(AStream, false);
s := ReadWideString(AStream, false);
if (FIncompleteCell <> nil) and (s <> '') then begin
FIncompleteCell^.UTF8StringValue := s;
FIncompleteCell^.ContentType := cctUTF8String;
end;
FIncompleteCell := nil;
end;
procedure TsSpreadBIFF8Reader.ReadXF(const AStream: TStream);

View File

@ -362,6 +362,7 @@ type
FDateMode: TDateMode;
FPaletteFound: Boolean;
FXFList: TFPList; // of TXFListData
FIncompleteCell: PCell;
procedure ApplyCellFormatting(ARow, ACol: Cardinal; XFIndex: Word); virtual;
procedure CreateNumFormatList; override;
// Extracts a number out of an RK value
@ -402,7 +403,7 @@ type
// Read row info
procedure ReadRowInfo(AStream: TStream); virtual;
// Read STRING record (result of string formula)
procedure ReadStringRecord(AStream: TStream; var AResultString: String); virtual;
procedure ReadStringRecord(AStream: TStream); virtual;
// Read WINDOW2 record (gridlines, sheet headers)
procedure ReadWindow2(AStream: TStream); virtual;
public
@ -981,19 +982,17 @@ begin
//RPN data not used by now
AStream.Position := AStream.Position + FormulaSize;
(*
// Now determine the type of the formula result
if (Data[6] = $FF) and (Data[7] = $FF) then
case Data[0] of
0: begin
ReadStringRecord(AStream, resultStr);
if resultStr = '' then
FWorksheet.WriteBlank(ARow, ACol)
else
FWorksheet.WriteUTF8Text(ARow, ACol, resultStr);
end;
1: FWorksheet.WriteBoolValue(ARow, ACol, Data[2] = 1);
2: begin
0: // String -> Value is found in next record (STRING)
FIncompleteCell := FWorksheet.GetCell(ARow, ACol);
1: // Boolean value
FWorksheet.WriteBoolValue(ARow, ACol, Data[2] = 1);
2: begin // Error value
case Data[2] of
ERR_INTERSECTION_EMPTY : err := errEmptyIntersection;
ERR_DIVIDE_BY_ZERO : err := errDivideByZero;
@ -1007,20 +1006,20 @@ begin
end;
3: FWorksheet.WriteBlank(ARow, ACol);
end
else begin *)
else begin
if SizeOf(Double) <> 8 then
raise Exception.Create('Double is not 8 bytes');
// Result is a number or a date/time
Move(Data[0], ResultFormula, SizeOf(Data));
{Find out what cell type, set content type and value}
{Find out what cell type, set content type and value}
ExtractNumberFormat(XF, nf, nd, nfs);
if IsDateTime(ResultFormula, nf, dt) then
FWorksheet.WriteDateTime(ARow, ACol, dt, nf, nfs)
else
FWorksheet.WriteNumber(ARow, ACol, ResultFormula, nf, nd);
// end;
end;
{Add attributes}
ApplyCellFormatting(ARow, ACol, XF);
@ -1233,12 +1232,11 @@ begin
end;
{ Reads a STRING record. It immediately precedes a FORMULA record which has a
string result.
Returns here just a dummy string. Has to be overridden to read the real text. }
procedure TsSpreadBIFFReader.ReadStringRecord(AStream: TStream;
var AResultString: String);
string result. The read value is applied to the FIncompleteCell.
Must be overridden because the implementation depends on BIFF version. }
procedure TsSpreadBIFFReader.ReadStringRecord(AStream: TStream);
begin
AResultString := '(STRING)';
//
end;
{ Reads the WINDOW2 record containing information like "show grid lines",