fpspreadsheet: Add support for reading/writing of column widths in biff8 (specified as multiples of the width of the character "0"). Add test case. No regressions

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@2945 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-04-18 13:17:22 +00:00
parent 99872da4e3
commit 36cc7d1f4e
8 changed files with 149 additions and 11 deletions

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="9"/>
@ -43,7 +43,7 @@
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="10"/>
<Version Value="11"/>
<PathDelim Value="\"/>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>

View File

@ -141,13 +141,13 @@ begin
MyWorksheet.WriteDateTime(37, 1, number, nfTimeInterval);
// Set width of columns 1 and 5
lCol.Width := 100; //mm
lCol.Width := 30;
MyWorksheet.WriteColInfo(1, lCol);
lCol.Width := 50;
lCol.Width := 5;
MyWorksheet.WriteColInfo(5, lCol);
// Set height of rows 5 and 6
lRow.Height := 10; // mm
lRow.Height := 10;
MyWorksheet.WriteRowInfo(5, lRow);
lRow.Height := 5;
MyWorksheet.WriteRowInfo(6, lRow);

View File

@ -247,7 +247,7 @@ type
TCol = record
Col: Byte;
Width: Single; // in milimeters
Width: Single; // in "characters". Excel uses the with of char "0" in 1st font
end;
PCol = ^TCol;
@ -309,6 +309,7 @@ type
procedure WriteColInfo(ACol: Cardinal; AData: TCol);
{ Properties }
property Cells: TAVLTree read FCells;
property Cols: TIndexedAVLTree read FCols;
end;
{ TsWorkbook }
@ -1325,6 +1326,7 @@ begin
inherited Destroy;
end;
{@@
Helper method for determining the spreadsheet type from the file type extension

View File

@ -28,6 +28,8 @@ var
SollDateTimeFormats: array[0..9] of TsNumberFormat;
SollDateTimeFormatStrings: array[0..9] of String;
SollColWidths: array[0..1] of Single;
procedure InitSollFmtData;
type
@ -45,6 +47,8 @@ type
procedure TestWriteReadNumberFormats;
// Repeat with date/times
procedure TestWriteReadDateTimeFormats;
// Test column width
procedure TestWriteReadColWidths;
end;
implementation
@ -125,6 +129,10 @@ begin
SollDateTimeStrings[i, 8] := FormatDateTime('nn:ss', SollDateTimes[i]);
SollDateTimeStrings[i, 9] := TimeIntervalToString(SollDateTimes[i]);
end;
// Column width
SollColWidths[0] := 20; // characters based on width of "0"
SollColWidths[1] := 40;
end;
{ TSpreadWriteReadFormatTests }
@ -222,6 +230,51 @@ begin
DeleteFile(TempFile);
end;
procedure TSpreadWriteReadFormatTests.TestWriteReadColWidths;
var
MyWorksheet: TsWorksheet;
MyWorkbook: TsWorkbook;
ActualColWidth: Single;
Col: Integer;
lpCol: PCol;
lCol: TCol;
TempFile: string; //write xls/xml to this file and read back from it
begin
TempFile:=GetTempFileName;
{// Not needed: use workbook.writetofile with overwrite=true
if fileexists(TempFile) then
DeleteFile(TempFile);
}
// Write out all test values
MyWorkbook := TsWorkbook.Create;
MyWorkSheet:= MyWorkBook.AddWorksheet(FmtNumbersSheet);
for Col := Low(SollColWidths) to High(SollColWidths) do begin
lCol.Width := SollColWidths[Col];
MyWorksheet.WriteColInfo(Col, lCol);
end;
MyWorkBook.WriteToFile(TempFile,sfExcel8,true);
MyWorkbook.Free;
// Open the spreadsheet, as biff8
MyWorkbook := TsWorkbook.Create;
MyWorkbook.ReadFromFile(TempFile, sfExcel8);
MyWorksheet:=GetWorksheetByName(MyWorkBook, FmtNumbersSheet);
if MyWorksheet=nil then
fail('Error in test code. Failed to get named worksheet');
for Col := Low(SollColWidths) to High(SollColWidths) do begin
lpCol := MyWorksheet.GetCol(Col);
if lpCol = nil then
fail('Error in test code. Failed to return saved column width');
ActualColWidth := lpCol^.Width;
CheckEquals(SollColWidths[Col], ActualColWidth, 'Test saved colwidth mismatch column '+ColNotation(MyWorkSheet,Col));
end;
// Finalization
MyWorkbook.Free;
DeleteFile(TempFile);
end;
initialization
RegisterTest(TSpreadWriteReadFormatTests);
InitSollFmtData;

View File

@ -142,7 +142,7 @@
</Other>
</CompilerOptions>
<Debugging>
<Exceptions Count="4">
<Exceptions Count="5">
<Item1>
<Name Value="EAbort"/>
<Enabled Value="False"/>
@ -157,8 +157,10 @@
</Item3>
<Item4>
<Name Value="EAssertionFailedError"/>
<Enabled Value="False"/>
</Item4>
<Item5>
<Name Value="EIgnoredTest"/>
</Item5>
</Exceptions>
</Debugging>
</CONFIG>

View File

@ -25,6 +25,9 @@ const
// Returns an A.. notation based on sheet, row, optional column (e.g. A1).
function CellNotation(WorkSheet: TsWorksheet; Row: integer; Column: integer=0): string;
// Returns an A notation of column based on sheed and column
function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String;
// Note: using this function instead of GetWorkSheetByName for compatibility with
// older fpspreadsheet versions that don't have that function
function GetWorksheetByName(AWorkBook: TsWorkBook; AName: String): TsWorksheet;
@ -70,6 +73,25 @@ begin
result:=WorkSheet.Name+'!'+inttostr(Column+1)+':'+inttostr(Row+1)
end;
function ColNotation(WorkSheet: TsWorksheet; Column:Integer): String;
begin
if not Assigned(Worksheet) then
Result := 'ColNotation: error getting worksheet.'
else begin
if Column < 26 then
Result := Worksheet.Name + '!' + char(Column+65)
else
if Column < 26*26 then
Result := Worksheet.Name + '!' + char(Column div 26 + 65) + char(Column mod 26 + 65)
else
if Column < 26*26*26 then
Result := Worksheet.Name + '!' + char(Column div (26*26) + 65) +
char(Column mod (26*26) div 26 + 65) +
char(Column mod (26*26*26) + 65)
else
Result := 'ColNotation: At most three digits supported.';
end;
end;
end.

View File

@ -117,6 +117,8 @@ type
// procedure ReadCodepage in xlscommon
// procedure ReadDateMode in xlscommon
procedure ReadFont(const AStream: TStream);
// Read col info
procedure ReadColInfo(const AStream: TStream);
public
constructor Create; override;
destructor Destroy; override;
@ -140,6 +142,7 @@ type
procedure WriteXFFieldsForFormattingStyles(AStream: TStream);
protected
procedure AddDefaultFormats(); override;
procedure WriteColInfo(AStream: TStream; ASheet: TsWorksheet; ACol: PCol);
public
// constructor Create;
// destructor Destroy; override;
@ -176,6 +179,7 @@ const
{ Excel record IDs }
INT_EXCEL_ID_BOF = $0809;
INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs
INT_EXCEL_ID_COLINFO = $007D;
INT_EXCEL_ID_COUNTRY = $008C;
INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_DIMENSIONS = $0200;
@ -554,7 +558,9 @@ var
MyData: TMemoryStream;
CurrentPos: Int64;
Boundsheets: array of Int64;
i, len: Integer;
sheet: TsWorksheet;
i, j, len: Integer;
col: PCol;
begin
{ Write workbook globals }
@ -643,6 +649,8 @@ begin
for i := 0 to AData.GetWorksheetCount - 1 do
begin
sheet := AData.GetWorksheetByIndex(i);
{ First goes back and writes the position of the BOF of the
sheet on the respective BOUNDSHEET record }
CurrentPos := AStream.Position;
@ -654,11 +662,16 @@ begin
WriteIndex(AStream);
WriteDimensions(AStream, AData.GetWorksheetByIndex(i));
for j := 0 to sheet.Cols.Count-1 do begin
col := PCol(sheet.Cols[j]);
WriteColInfo(AStream, sheet, col);
end;
WriteDimensions(AStream, sheet);
WriteWindow2(AStream, True);
WriteCellsToStream(AStream, AData.GetWorksheetByIndex(i).Cells);
WriteCellsToStream(AStream, sheet.Cells);
WriteEOF(AStream);
end;
@ -747,6 +760,32 @@ begin
AStream.WriteBuffer(WideStringToLE(WideSheetName)[1], Len * Sizeof(WideChar));
end;
{*******************************************************************
* TsSpreadBIFF8Writer.WriteColumn ()
*
* DESCRIPTION: Writes a COLINFO record
*******************************************************************}
procedure TsSpreadBIFF8Writer.WriteColInfo(AStream: TStream;
ASheet: TsWorkSheet; ACol: PCol);
var
w: Integer;
begin
if Assigned(ACol) and Assigned(ASheet) then begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_COLINFO)); // BIFF record header
AStream.WriteWord(WordToLE(12)); // Record size
AStream.WriteWord(WordToLE(ACol^.Col)); // start column
AStream.WriteWord(WordToLE(ACol^.Col)); // end column
{ calculate width to be in units of 1/256 of pixel width of character "0" }
w := round(ACol^.Width * 256);
AStream.WriteWord(WordToLE(w)); // write width
AStream.WriteWord(15); // XF record, ignored
AStream.WriteWord(0); // option flags, ignored
AStream.WriteWord(0); // "not used"
end;
end;
{*******************************************************************
* TsSpreadBIFF8Writer.WriteDateTime ()
*
@ -1868,6 +1907,7 @@ begin
INT_EXCEL_ID_RK: ReadRKValue(AStream);
INT_EXCEL_ID_MULRK: ReadMulRKValues(AStream);
INT_EXCEL_ID_LABELSST:ReadLabelSST(AStream); //BIFF8 only
INT_EXCEL_ID_COLINFO: ReadColInfo(AStream);
INT_EXCEL_ID_BOF: ;
INT_EXCEL_ID_EOF: SectionEOF := True;
else
@ -2384,6 +2424,25 @@ begin
lFontName := ReadString(AStream, Len);
end;
procedure TsSpreadBiff8Reader.ReadColInfo(const AStream: TStream);
var
c, c1, c2: Cardinal;
w: Word;
col: TCol;
begin
// read column start and end index of column range
c1 := WordLEToN(AStream.ReadWord);
c2 := WordLEToN(AStream.ReadWord);
// read col width in 1/256 of the width of "0" character
w := WordLEToN(AStream.ReadWord);
// calculate width in units of "characters"
col.Width := w / 256;
// assign width to columns
for c := c1 to c2 do
FWorksheet.WriteColInfo(c, col);
end;
{*******************************************************************
* Initialization section
*