You've already forked lazarus-ccr
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:
@@ -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)"/>
|
||||||
|
@@ -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);
|
||||||
|
Binary file not shown.
@@ -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
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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>
|
||||||
|
@@ -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.
|
||||||
|
|
||||||
|
@@ -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
|
||||||
*
|
*
|
||||||
|
Reference in New Issue
Block a user