fpspreadsheet: Fix written oversized worksheets being defective, adapt BIFF writer's WriteDimensions method to be compatible with oversized worksheets.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3455 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-08-08 14:30:19 +00:00
parent 59bcbe2889
commit 8f666bec13
9 changed files with 125 additions and 48 deletions

View File

@ -2939,6 +2939,8 @@ var
begin
// some abbreviations...
defFontSize := Workbook.GetFont(0).Size;
GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol);
{
firstCol := ASheet.GetFirstColIndex;
firstRow := ASheet.GetFirstRowIndex;
lastCol := ASheet.GetLastColIndex;
@ -2946,6 +2948,7 @@ begin
// avoid arithmetic overflow in case of empty worksheet
if (firstCol = $FFFFFFFF) and (lastCol = 0) then firstCol := 0;
if (FirstRow = $FFFFFFFF) and (lastRow = 0) then firstRow := 0;
}
emptyRowsAbove := firstRow > 0;
// Now loop through all rows
@ -3103,8 +3106,8 @@ begin
FPointSeparatorSettings.DecimalSeparator:='.';
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
FLimitations.MaxCols := 1024;
FLimitations.MaxRows := 1048576;
FLimitations.MaxColCount := 1024;
FLimitations.MaxRowCount := 1048576;
end;
destructor TsSpreadOpenDocWriter.Destroy;

View File

@ -26,8 +26,8 @@ type
{@@ Record collection limitations of a particular file format }
TsSpreadsheetFormatLimitations = record
MaxRows: Cardinal;
MaxCols: Cardinal;
MaxRowCount: Cardinal;
MaxColCount: Cardinal;
end;
const
@ -6173,8 +6173,8 @@ constructor TsCustomSpreadWriter.Create(AWorkbook: TsWorkbook);
begin
inherited Create(AWorkbook);
{ A good starting point valid for many formats... }
FLimitations.MaxCols := 256;
FLimitations.MaxRows := 65536;
FLimitations.MaxColCount := 256;
FLimitations.MaxRowCount := 65536;
end;
{@@
@ -6265,7 +6265,8 @@ end;
{@@
Determines the size of the worksheet to be written. VirtualMode is respected.
Is called when the writer needs the size for output.
Is called when the writer needs the size for output. Column and row count
limitations are repsected as well.
@param AWorksheet Worksheet to be written
@param AFirsRow Index of first row to be written
@ -6276,15 +6277,26 @@ end;
procedure TsCustomSpreadWriter.GetSheetDimensions(AWorksheet: TsWorksheet;
out AFirstRow, ALastRow, AFirstCol, ALastCol: Cardinal);
begin
AFirstRow := 0;
AFirstCol := 0;
if (boVirtualMode in AWorksheet.Workbook.Options) then begin
AFirstRow := 0;
AFirstCol := 0;
ALastRow := AWorksheet.Workbook.VirtualRowCount-1;
ALastCol := AWorksheet.Workbook.VirtualColCount-1;
end else begin
Workbook.UpdateCaches;
AFirstRow := AWorksheet.GetFirstRowIndex;
AFirstCol := AWorksheet.GetFirstColIndex;
ALastRow := AWorksheet.GetLastRowIndex;
ALastCol := AWorksheet.GetLastColIndex;
end;
if AFirstCol >= Limitations.MaxColCount then
AFirstCol := Limitations.MaxColCount-1;
if AFirstRow >= Limitations.MaxRowCount then
AFirstRow := Limitations.MaxRowCount-1;
if ALastCol >= Limitations.MaxColCount then
ALastCol := Limitations.MaxColCount-1;
if ALastRow >= Limitations.MaxRowCount then
ALastRow := Limitations.MaxRowCount-1;
end;
{@@
@ -6307,10 +6319,10 @@ var
lastCol, lastRow: Cardinal;
begin
Workbook.GetLastRowColIndex(lastRow, lastCol);
if lastRow >= FLimitations.MaxRows then
Workbook.AddErrorMsg(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRows]);
if lastCol >= FLimitations.MaxCols then
Workbook.AddErrorMsg(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxCols]);
if lastRow >= FLimitations.MaxRowCount then
Workbook.AddErrorMsg(lpMaxRowsExceeded, [lastRow+1, FLimitations.MaxRowCount]);
if lastCol >= FLimitations.MaxColCount then
Workbook.AddErrorMsg(lpMaxColsExceeded, [lastCol+1, FLimitations.MaxColCount]);
end;

View File

@ -30,7 +30,6 @@ type
procedure TestWriteErrorMessages_BIFF8;
procedure TestWriteErrorMessages_ODS;
procedure TestWriteErrorMessages_OOXML;
end;
implementation
@ -62,10 +61,14 @@ var
row, col: Cardinal;
row1, row2: Cardinal;
col1, col2: Cardinal;
formula: TsFormula;
s: String;
TempFile: String;
ErrList: TStringList;
begin
formula.FormulaStr := '=A1';
formula.DoubleValue := 0.0;
ErrList := TStringList.Create;
try
// Test 1: Too many rows
@ -78,7 +81,8 @@ begin
MyWorksheet.WriteBlank(row, 0);
MyWorksheet.WriteNumber(row, 1, 1.0);
MyWorksheet.WriteUTF8Text(row, 2, 'A');
MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula(
MyWorksheet.WriteFormula(Row, 3, formula);
MyWorksheet.WriteRPNFormula(row, 4, CreateRPNFormula(
RPNCellValue('A1', nil)));
end;
TempFile:=NewTempFile;
@ -97,10 +101,11 @@ begin
col1 := MAX_COL_COUNT[TTestFormat(AFormat)] - 5;
col2 := MAX_COL_COUNT[TTestFormat(AFormat)] + 5;
for col := col1 to col2 do begin
MyWorksheet.WriteBlank(row, 0);
MyWorksheet.WriteNumber(row, 1, 1.0);
MyWorksheet.WriteUTF8Text(row, 2, 'A');
MyWorksheet.WriteRPNFormula(row, 3, CreateRPNFormula(
MyWorksheet.WriteBlank(0, col);
MyWorksheet.WriteNumber(1, col, 1.0);
MyWorksheet.WriteUTF8Text(2, col, 'A');
MyWorksheet.WriteFormula(3, col, formula);
MyWorksheet.WriteRPNFormula(4, col, CreateRPNFormula(
RPNCellValue('A1', nil)));
end;
TempFile:=NewTempFile;

View File

@ -48,7 +48,6 @@
<Unit1>
<Filename Value="datetests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="datetests"/>
</Unit1>
<Unit2>
<Filename Value="stringtests.pas"/>
@ -63,7 +62,6 @@
<Unit4>
<Filename Value="manualtests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="manualtests"/>
</Unit4>
<Unit5>
<Filename Value="testsutility.pas"/>
@ -78,7 +76,6 @@
<Unit7>
<Filename Value="formattests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="formattests"/>
</Unit7>
<Unit8>
<Filename Value="colortests.pas"/>
@ -91,7 +88,6 @@
<Unit10>
<Filename Value="optiontests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="optiontests"/>
</Unit10>
<Unit11>
<Filename Value="numformatparsertests.pas"/>
@ -100,17 +96,14 @@
<Unit12>
<Filename Value="rpnformulaunit.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="rpnFormulaUnit"/>
</Unit12>
<Unit13>
<Filename Value="formulatests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="formulatests"/>
</Unit13>
<Unit14>
<Filename Value="emptycelltests.pas"/>
<IsPartOfProject Value="True"/>
<UnitName Value="emptycelltests"/>
</Unit14>
<Unit15>
<Filename Value="errortests.pas"/>

View File

@ -1502,7 +1502,7 @@ var
s: ansistring;
xf: Word;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
RPNLength := 0;
@ -1608,7 +1608,7 @@ var
xf: Word;
rec: TBlankRecord;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
xf := FindXFIndex(ACell);
@ -1667,7 +1667,7 @@ var
var
xf: Word;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
if AValue = '' then Exit; // Writing an empty text doesn't work
@ -1730,7 +1730,7 @@ var
xf: Word;
rec: TBIFF2NumberRecord;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
xf := FindXFIndex(ACell);
@ -1763,8 +1763,8 @@ var
w: Word;
h: Single;
begin
if (ARowIndex >= FLimitations.MaxRows) or (AFirstColIndex >= FLimitations.MaxCols)
or (ALastColIndex >= FLimitations.MaxCols)
if (ARowIndex >= FLimitations.MaxRowCount) or (AFirstColIndex >= FLimitations.MaxColCount)
or (ALastColIndex >= FLimitations.MaxColCount)
then
exit;

View File

@ -307,6 +307,16 @@ const
);
type
TBIFF5DimensionsRecord = packed record
RecordID: Word;
RecordSize: Word;
FirstRow: Word;
LastRowPlus1: Word;
FirstCol: Word;
LastColPlus1: Word;
NotUsed: Word;
end;
TBIFF5LabelRecord = packed record
RecordID: Word;
RecordSize: Word;
@ -431,7 +441,7 @@ begin
WriteWindow2(AStream, sheet);
WritePane(AStream, sheet, true, pane); // true for "is BIFF5 or BIFF8"
WriteSelection(AStream, sheet, pane);
WriteRows(AStream, sheet);
//WriteRows(AStream, sheet);
if (boVirtualMode in Workbook.Options) then
WriteVirtualCells(AStream)
@ -551,8 +561,27 @@ end;
}
procedure TsSpreadBIFF5Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
var
lLastCol, lLastRow: Word;
rec: TBIFF5DimensionsRecord;
firstCol, lastCol, firstRow, lastRow: Cardinal;
lLastRow, lLastCol: Word;
begin
{ Determine sheet size }
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
{ Setup BIFF record }
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
rec.RecordSize := WordToLE(10);
rec.FirstRow := WordToLE(firstRow);
rec.LastRowPlus1 := WordToLE(lastRow+1);
rec.FirstCol := WordToLe(firstCol);
rec.LastColPlus1 := WordToLE(lastCol+1);
rec.NotUsed := 0;
{ Write BIFF record }
AStream.WriteBuffer(rec, SizeOf(rec));
(*
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DIMENSIONS));
AStream.WriteWord(WordToLE(10));
@ -573,6 +602,7 @@ begin
{ Not used }
AStream.WriteWord(0);
*)
end;
{*******************************************************************
@ -951,7 +981,7 @@ var
rec: TBIFF5LabelRecord;
buf: array of byte;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
case WorkBookEncoding of

View File

@ -34,9 +34,11 @@ EOF
The row and column numbering in BIFF files is zero-based.
Excel file format specification obtained from:
http://sc.openoffice.org/excelfileformat.pdf
see also:
http://office.microsoft.com/en-us/excel-help/excel-specifications-and-limits-HP005199291.aspx
AUTHORS: Felipe Monteiro de Carvalho
Jose Mejuto
}
@ -280,6 +282,16 @@ const
);
type
TBIFF8DimensionsRecord = packed record
RecordID: Word;
RecordSize: Word;
FirstRow: DWord;
LastRowPlus1: DWord;
FirstCol: Word;
LastColPlus1: Word;
NotUsed: Word;
end;
TBIFF8LabelRecord = packed record
RecordID: Word;
RecordSize: Word;
@ -578,7 +590,24 @@ end;
procedure TsSpreadBIFF8Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
var
firstRow, lastRow, firstCol, lastCol: Cardinal;
rec: TBIFF8DimensionsRecord;
begin
{ Determine sheet size }
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
{ Populate BIFF record }
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
rec.RecordSize := WordToLE(14);
rec.FirstRow := DWordToLE(firstRow);
rec.LastRowPlus1 := DWordToLE(lastRow+1);
rec.FirstCol := WordToLE(firstCol);
rec.LastColPlus1 := WordToLE(lastCol+1);
rec.NotUsed := 0;
{ Write BIFF record to stream }
AStream.WriteBuffer(rec, SizeOf(rec));
(*
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DIMENSIONS));
AStream.WriteWord(WordToLE(14));
@ -600,6 +629,7 @@ begin
{ Not used }
AStream.WriteWord(WordToLE(0));
*)
end;
{*******************************************************************
@ -996,7 +1026,7 @@ var
rec: TBIFF8LabelRecord;
buf: array of byte;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
WideValue := UTF8Decode(AValue); //to UTF16

View File

@ -1852,7 +1852,7 @@ procedure TsSpreadBIFFWriter.WriteBlank(AStream: TStream;
var
rec: TBIFF58BlankRecord;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
{ BIFF record header }
@ -1925,7 +1925,7 @@ var
w: Integer;
begin
if Assigned(ACol) then begin
if (ACol^.Col >= FLimitations.MaxCols) then
if (ACol^.Col >= FLimitations.MaxColCount) then
exit;
{ BIFF record header }
@ -2041,7 +2041,7 @@ procedure TsSpreadBIFFWriter.WriteNumber(AStream: TStream;
var
rec: TBIFF58NumberRecord;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
{ BIFF Record header }
@ -2269,7 +2269,7 @@ var
RPNLength: Word;
RecordSizePos, FinalPos: Int64;
begin
if (ARow >= FLimitations.MaxRows) or (ACol >= FLimitations.MaxCols) then
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
exit;
{ BIFF Record header }
@ -2494,9 +2494,9 @@ var
rowheight: Word;
h: Single;
begin
if (ARowIndex >= FLimitations.MaxRows) or
(AFirstColIndex >= FLimitations.MaxCols) or
(ALastColIndex >= FLimitations.MaxCols)
if (ARowIndex >= FLimitations.MaxRowCount) or
(AFirstColIndex >= FLimitations.MaxColCount) or
(ALastColIndex >= FLimitations.MaxColCount)
then
exit;

View File

@ -1857,9 +1857,11 @@ begin
AppendToStream(AStream,
'<sheetData>');
GetSheetDimensions(AWorksheet, r1, r2, c1, c2);
if (boVirtualMode in Workbook.Options) and Assigned(Workbook.OnWriteCellData)
then begin
for r := 0 to Workbook.VirtualRowCount-1 do begin
for r := 0 to r2 do begin
row := AWorksheet.FindRow(r);
if row <> nil then
rh := Format(' ht="%g" customHeight="1"', [
@ -1868,7 +1870,7 @@ begin
rh := '';
AppendToStream(AStream, Format(
'<row r="%d" spans="1:%d"%s>', [r+1, Workbook.VirtualColCount, rh]));
for c := 0 to Workbook.VirtualColCount-1 do begin
for c := 0 to c2 do begin
InitCell(lCell);
value := varNull;
styleCell := nil;
@ -1905,6 +1907,7 @@ begin
end else
begin
// The cells need to be written in order, row by row, cell by cell
(*
c1 := AWorksheet.GetFirstColIndex;
c2 := AWorksheet.GetLastColIndex;
if (c1 = $FFFFFFFF) and (c2 = 0) then c1 := 0; // avoid arithmetic overflow in case of empty worksheet
@ -1912,6 +1915,7 @@ begin
r2 := AWorksheet.GetlastRowIndex;
if (r1 = $FFFFFFFF) and (r2 = 0) then r1 := 0; // avoid arithmetic overflow in case of empty worksheet
// for r := 0 to AWorksheet.GetLastRowIndex do begin
*)
for r := r1 to r2 do begin
// If the row has a custom height add this value to the <row> specification
row := AWorksheet.FindRow(r);
@ -2298,8 +2302,8 @@ begin
FPointSeparatorSettings.DecimalSeparator := '.';
// http://en.wikipedia.org/wiki/List_of_spreadsheet_software#Specifications
FLimitations.MaxCols := 16384;
FLimitations.MaxRows := 1048576;
FLimitations.MaxColCount := 16384;
FLimitations.MaxRowCount := 1048576;
end;
procedure TsSpreadOOXMLWriter.CreateNumFormatList;