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> <CONFIG>
<ProjectOptions> <ProjectOptions>
<Version Value="9"/> <Version Value="9"/>
@@ -43,7 +43,7 @@
</Units> </Units>
</ProjectOptions> </ProjectOptions>
<CompilerOptions> <CompilerOptions>
<Version Value="10"/> <Version Value="11"/>
<PathDelim Value="\"/> <PathDelim Value="\"/>
<SearchPaths> <SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/> <IncludeFiles Value="$(ProjOutDir)"/>

View File

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

View File

@@ -247,7 +247,7 @@ type
TCol = record TCol = record
Col: Byte; Col: Byte;
Width: Single; // in milimeters Width: Single; // in "characters". Excel uses the with of char "0" in 1st font
end; end;
PCol = ^TCol; PCol = ^TCol;
@@ -309,6 +309,7 @@ type
procedure WriteColInfo(ACol: Cardinal; AData: TCol); procedure WriteColInfo(ACol: Cardinal; AData: TCol);
{ Properties } { Properties }
property Cells: TAVLTree read FCells; property Cells: TAVLTree read FCells;
property Cols: TIndexedAVLTree read FCols;
end; end;
{ TsWorkbook } { TsWorkbook }
@@ -1325,6 +1326,7 @@ begin
inherited Destroy; inherited Destroy;
end; end;
{@@ {@@
Helper method for determining the spreadsheet type from the file type extension 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; SollDateTimeFormats: array[0..9] of TsNumberFormat;
SollDateTimeFormatStrings: array[0..9] of String; SollDateTimeFormatStrings: array[0..9] of String;
SollColWidths: array[0..1] of Single;
procedure InitSollFmtData; procedure InitSollFmtData;
type type
@@ -45,6 +47,8 @@ type
procedure TestWriteReadNumberFormats; procedure TestWriteReadNumberFormats;
// Repeat with date/times // Repeat with date/times
procedure TestWriteReadDateTimeFormats; procedure TestWriteReadDateTimeFormats;
// Test column width
procedure TestWriteReadColWidths;
end; end;
implementation implementation
@@ -125,6 +129,10 @@ begin
SollDateTimeStrings[i, 8] := FormatDateTime('nn:ss', SollDateTimes[i]); SollDateTimeStrings[i, 8] := FormatDateTime('nn:ss', SollDateTimes[i]);
SollDateTimeStrings[i, 9] := TimeIntervalToString(SollDateTimes[i]); SollDateTimeStrings[i, 9] := TimeIntervalToString(SollDateTimes[i]);
end; end;
// Column width
SollColWidths[0] := 20; // characters based on width of "0"
SollColWidths[1] := 40;
end; end;
{ TSpreadWriteReadFormatTests } { TSpreadWriteReadFormatTests }
@@ -222,6 +230,51 @@ begin
DeleteFile(TempFile); DeleteFile(TempFile);
end; 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 initialization
RegisterTest(TSpreadWriteReadFormatTests); RegisterTest(TSpreadWriteReadFormatTests);
InitSollFmtData; InitSollFmtData;

View File

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

View File

@@ -25,6 +25,9 @@ const
// Returns an A.. notation based on sheet, row, optional column (e.g. A1). // Returns an A.. notation based on sheet, row, optional column (e.g. A1).
function CellNotation(WorkSheet: TsWorksheet; Row: integer; Column: integer=0): string; 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 // Note: using this function instead of GetWorkSheetByName for compatibility with
// older fpspreadsheet versions that don't have that function // older fpspreadsheet versions that don't have that function
function GetWorksheetByName(AWorkBook: TsWorkBook; AName: String): TsWorksheet; function GetWorksheetByName(AWorkBook: TsWorkBook; AName: String): TsWorksheet;
@@ -70,6 +73,25 @@ begin
result:=WorkSheet.Name+'!'+inttostr(Column+1)+':'+inttostr(Row+1) result:=WorkSheet.Name+'!'+inttostr(Column+1)+':'+inttostr(Row+1)
end; 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. end.

View File

@@ -117,6 +117,8 @@ type
// procedure ReadCodepage in xlscommon // procedure ReadCodepage in xlscommon
// procedure ReadDateMode in xlscommon // procedure ReadDateMode in xlscommon
procedure ReadFont(const AStream: TStream); procedure ReadFont(const AStream: TStream);
// Read col info
procedure ReadColInfo(const AStream: TStream);
public public
constructor Create; override; constructor Create; override;
destructor Destroy; override; destructor Destroy; override;
@@ -140,6 +142,7 @@ type
procedure WriteXFFieldsForFormattingStyles(AStream: TStream); procedure WriteXFFieldsForFormattingStyles(AStream: TStream);
protected protected
procedure AddDefaultFormats(); override; procedure AddDefaultFormats(); override;
procedure WriteColInfo(AStream: TStream; ASheet: TsWorksheet; ACol: PCol);
public public
// constructor Create; // constructor Create;
// destructor Destroy; override; // destructor Destroy; override;
@@ -176,6 +179,7 @@ const
{ Excel record IDs } { Excel record IDs }
INT_EXCEL_ID_BOF = $0809; INT_EXCEL_ID_BOF = $0809;
INT_EXCEL_ID_BOUNDSHEET = $0085; // Renamed to SHEET in the latest OpenOffice docs 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_COUNTRY = $008C;
INT_EXCEL_ID_EOF = $000A; INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_DIMENSIONS = $0200; INT_EXCEL_ID_DIMENSIONS = $0200;
@@ -554,7 +558,9 @@ var
MyData: TMemoryStream; MyData: TMemoryStream;
CurrentPos: Int64; CurrentPos: Int64;
Boundsheets: array of Int64; Boundsheets: array of Int64;
i, len: Integer; sheet: TsWorksheet;
i, j, len: Integer;
col: PCol;
begin begin
{ Write workbook globals } { Write workbook globals }
@@ -643,6 +649,8 @@ begin
for i := 0 to AData.GetWorksheetCount - 1 do for i := 0 to AData.GetWorksheetCount - 1 do
begin begin
sheet := AData.GetWorksheetByIndex(i);
{ First goes back and writes the position of the BOF of the { First goes back and writes the position of the BOF of the
sheet on the respective BOUNDSHEET record } sheet on the respective BOUNDSHEET record }
CurrentPos := AStream.Position; CurrentPos := AStream.Position;
@@ -654,11 +662,16 @@ begin
WriteIndex(AStream); 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); WriteWindow2(AStream, True);
WriteCellsToStream(AStream, AData.GetWorksheetByIndex(i).Cells); WriteCellsToStream(AStream, sheet.Cells);
WriteEOF(AStream); WriteEOF(AStream);
end; end;
@@ -747,6 +760,32 @@ begin
AStream.WriteBuffer(WideStringToLE(WideSheetName)[1], Len * Sizeof(WideChar)); AStream.WriteBuffer(WideStringToLE(WideSheetName)[1], Len * Sizeof(WideChar));
end; 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 () * TsSpreadBIFF8Writer.WriteDateTime ()
* *
@@ -1868,6 +1907,7 @@ begin
INT_EXCEL_ID_RK: ReadRKValue(AStream); INT_EXCEL_ID_RK: ReadRKValue(AStream);
INT_EXCEL_ID_MULRK: ReadMulRKValues(AStream); INT_EXCEL_ID_MULRK: ReadMulRKValues(AStream);
INT_EXCEL_ID_LABELSST:ReadLabelSST(AStream); //BIFF8 only INT_EXCEL_ID_LABELSST:ReadLabelSST(AStream); //BIFF8 only
INT_EXCEL_ID_COLINFO: ReadColInfo(AStream);
INT_EXCEL_ID_BOF: ; INT_EXCEL_ID_BOF: ;
INT_EXCEL_ID_EOF: SectionEOF := True; INT_EXCEL_ID_EOF: SectionEOF := True;
else else
@@ -2384,6 +2424,25 @@ begin
lFontName := ReadString(AStream, Len); lFontName := ReadString(AStream, Len);
end; 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 * Initialization section
* *