lazarus-ccr/components/fpspreadsheet/xlsbiff5.pas
2009-01-29 13:24:37 +00:00

1077 lines
30 KiB
ObjectPascal
Executable File

{
xlsbiff5.pas
Writes an Excel 5 file
An Excel worksheet stream consists of a number of subsequent records.
To ensure a properly formed file, the following order must be respected:
1st record: BOF
2nd to Nth record: Any record
Last record: EOF
Excel 5 files are OLE compound document files, and must be written using the
fpOLE library.
Records Needed to Make a BIFF5 File Microsoft Excel Can Use:
Required Records:
BOF - Set the 6 byte offset to 0x0005 (workbook globals)
Window1
FONT - At least five of these records must be included
XF - At least 15 Style XF records and 1 Cell XF record must be included
STYLE
BOUNDSHEET - Include one BOUNDSHEET record per worksheet
EOF
BOF - Set the 6 byte offset to 0x0010 (worksheet)
INDEX
DIMENSIONS
WINDOW2
EOF
The row and column numbering in BIFF files is zero-based.
Excel file format specification obtained from:
http://sc.openoffice.org/excelfileformat.pdf
Records Needed to Make a BIFF5 File Microsoft Excel Can Use obtained from:
http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q147732&ID=KB;EN-US;Q147732&LN=EN-US&rnk=2&SD=msdn&FR=0&qry=BIFF&src=DHCS_MSPSS_msdn_SRCH&SPR=MSALL&
Microsoft BIFF 5 writer example:
http://support.microsoft.com/kb/150447/en-us
AUTHORS: Felipe Monteiro de Carvalho
}
unit xlsbiff5;
{$ifdef fpc}
{$mode delphi}
{$endif}
interface
uses
Classes, SysUtils, fpcanvas,
fpspreadsheet, fpolestorage, fpsutils;
type
{ TsSpreadBIFF5Reader }
TsSpreadBIFF5Reader = class(TsCustomSpreadReader)
private
RecordSize: Word;
FWorksheet: TsWorksheet;
FWorksheetNames: TStringList;
FCurrentWorksheet: Integer;
procedure ReadWorkbookGlobals(AStream: TStream; AData: TsWorkbook);
procedure ReadWorksheet(AStream: TStream; AData: TsWorkbook);
procedure ReadBoundsheet(AStream: TStream);
public
{ General reading methods }
procedure ReadFromFile(AFileName: string; AData: TsWorkbook); override;
procedure ReadFromStream(AStream: TStream; AData: TsWorkbook); override;
{ Record writing methods }
procedure ReadFormula(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override;
procedure ReadNumber(AStream: TStream); override;
end;
{ TsSpreadBIFF5Writer }
TsSpreadBIFF5Writer = class(TsCustomSpreadWriter)
public
// constructor Create;
// destructor Destroy; override;
{ General writing methods }
procedure WriteToFile(AFileName: string; AData: TsWorkbook); override;
procedure WriteToStream(AStream: TStream; AData: TsWorkbook); override;
{ Record writing methods }
procedure WriteBOF(AStream: TStream; ADataType: Word);
function WriteBoundsheet(AStream: TStream; ASheetName: string): Int64;
procedure WriteDimensions(AStream: TStream);
procedure WriteEOF(AStream: TStream);
procedure WriteFont(AStream: TStream; AFont: TFPCustomFont);
procedure WriteFormula(AStream: TStream; const ARow, ACol: Word; const AFormula: TRPNFormula); override;
procedure WriteIndex(AStream: TStream);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Word; const AValue: string); override;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double); override;
procedure WriteStyle(AStream: TStream);
procedure WriteWindow1(AStream: TStream);
procedure WriteWindow2(AStream: TStream; ASheetSelected: Boolean);
procedure WriteXF(AStream: TStream; AFontIndex: Word; AXF_TYPE_PROT: Byte);
end;
implementation
const
{ Excel record IDs }
INT_EXCEL_ID_BOF = $0809;
INT_EXCEL_ID_BOUNDSHEET = $0085;
INT_EXCEL_ID_EOF = $000A;
INT_EXCEL_ID_DIMENSIONS = $0200;
INT_EXCEL_ID_FONT = $0031;
INT_EXCEL_ID_FORMULA = $0006;
INT_EXCEL_ID_INDEX = $020B;
INT_EXCEL_ID_LABEL = $0204;
INT_EXCEL_ID_NUMBER = $0203;
INT_EXCEL_ID_STYLE = $0293;
INT_EXCEL_ID_WINDOW1 = $003D;
INT_EXCEL_ID_WINDOW2 = $023E;
INT_EXCEL_ID_XF = $00E0;
{ Cell Addresses constants }
MASK_EXCEL_ROW = $3FFF;
MASK_EXCEL_RELATIVE_ROW = $4000;
MASK_EXCEL_RELATIVE_COL = $8000;
{ BOF record constants }
INT_BOF_BIFF5_VER = $0500;
INT_BOF_WORKBOOK_GLOBALS= $0005;
INT_BOF_VB_MODULE = $0006;
INT_BOF_SHEET = $0010;
INT_BOF_CHART = $0020;
INT_BOF_MACRO_SHEET = $0040;
INT_BOF_WORKSPACE = $0100;
INT_BOF_BUILD_ID = $1FD2;
INT_BOF_BUILD_YEAR = $07CD;
{ FONT record constants }
INT_FONT_WEIGHT_NORMAL = $0190;
{ FORMULA record constants }
MASK_FORMULA_RECALCULATE_ALWAYS = $0001;
MASK_FORMULA_RECALCULATE_ON_OPEN = $0002;
MASK_FORMULA_SHARED_FORMULA = $0008;
{ STYLE record constants }
MASK_STYLE_BUILT_IN = $8000;
{ WINDOW1 record constants }
MASK_WINDOW1_OPTION_WINDOW_HIDDEN = $0001;
MASK_WINDOW1_OPTION_WINDOW_MINIMISED = $0002;
MASK_WINDOW1_OPTION_HORZ_SCROLL_VISIBLE = $0008;
MASK_WINDOW1_OPTION_VERT_SCROLL_VISIBLE = $0010;
MASK_WINDOW1_OPTION_WORKSHEET_TAB_VISIBLE = $0020;
{ WINDOW2 record constants }
MASK_WINDOW2_OPTION_SHOW_FORMULAS = $0001;
MASK_WINDOW2_OPTION_SHOW_GRID_LINES = $0002;
MASK_WINDOW2_OPTION_SHOW_SHEET_HEADERS = $0004;
MASK_WINDOW2_OPTION_PANES_ARE_FROZEN = $0008;
MASK_WINDOW2_OPTION_SHOW_ZERO_VALUES = $0010;
MASK_WINDOW2_OPTION_AUTO_GRIDLINE_COLOR = $0020;
MASK_WINDOW2_OPTION_COLUMNS_RIGHT_TO_LEFT = $0040;
MASK_WINDOW2_OPTION_SHOW_OUTLINE_SYMBOLS = $0080;
MASK_WINDOW2_OPTION_REMOVE_SPLITS_ON_UNFREEZE = $0100;
MASK_WINDOW2_OPTION_SHEET_SELECTED = $0200;
MASK_WINDOW2_OPTION_SHEET_ACTIVE = $0400;
{ XF substructures }
{ XF_TYPE_PROT - XF Type and Cell protection (3 Bits) - BIFF3-BIFF8 }
MASK_XF_TYPE_PROT_LOCKED = $1;
MASK_XF_TYPE_PROT_FORMULA_HIDDEN = $2;
MASK_XF_TYPE_PROT_STYLE_XF = $4; // 0 = CELL XF
{ XF_USED_ATTRIB - Attributes from parent Style XF (6 Bits) - BIFF3-BIFF8
In a CELL XF a cleared bit means that the parent attribute is used,
while a set bit indicates that the data in this XF is used
In a STYLE XF a cleared bit means that the data in this XF is used,
while a set bit indicates that the attribute should be ignored }
MASK_XF_USED_ATTRIB_NUMBER_FORMAT = $04;
MASK_XF_USED_ATTRIB_FONT = $08;
MASK_XF_USED_ATTRIB_TEXT = $10;
MASK_XF_USED_ATTRIB_BORDER_LINES = $20;
MASK_XF_USED_ATTRIB_BACKGROUND = $40;
MASK_XF_USED_ATTRIB_CELL_PROTECTION = $80;
{ XF_VERT_ALIGN }
MASK_XF_VERT_ALIGN_TOP = $00;
MASK_XF_VERT_ALIGN_CENTRED = $10;
MASK_XF_VERT_ALIGN_BOTTOM = $20;
MASK_XF_VERT_ALIGN_JUSTIFIED = $30;
{ XF record constants }
MASK_XF_TYPE_PROT = $0007;
MASK_XF_TYPE_PROT_PARENT = $FFF0;
MASK_XF_VERT_ALIGN = $70;
{
Exported functions
}
{ TsSpreadBIFF5Writer }
{*******************************************************************
* TsSpreadBIFF5Writer.WriteToFile ()
*
* DESCRIPTION: Writes an Excel BIFF5 file to the disc
*
* The BIFF 5 writer overrides this method because
* BIFF 5 is written as an OLE document, and our
* current OLE document writing method involves:
*
* 1 - Writing the BIFF data to a memory stream
*
* 2 - Write the memory stream data to disk using
* COM functions
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteToFile(AFileName: string; AData: TsWorkbook);
var
MemStream: TMemoryStream;
OutputStorage: TOLEStorage;
OLEDocument: TOLEDocument;
begin
MemStream := TMemoryStream.Create;
OutputStorage := TOLEStorage.Create;
try
WriteToStream(MemStream, AData);
// Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream;
OutputStorage.WriteOLEFile(AFileName, OLEDocument);
finally
MemStream.Free;
OutputStorage.Free;
end;
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteToStream ()
*
* DESCRIPTION: Writes an Excel BIFF5 record structure
*
* Be careful as this method doesn't write the OLE
* part of the document, just the BIFF records
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteToStream(AStream: TStream; AData: TsWorkbook);
var
FontData: TFPCustomFont;
MyData: TMemoryStream;
CurrentPos: Int64;
Boundsheets: array of Int64;
i, len: Integer;
begin
{ Write workbook globals }
WriteBOF(AStream, INT_BOF_WORKBOOK_GLOBALS);
WriteWindow1(AStream);
FontData := TFPCustomFont.Create;
try
FontData.Name := 'Arial';
// FONT0
WriteFont(AStream, FontData);
// FONT1
WriteFont(AStream, FontData);
// FONT2
WriteFont(AStream, FontData);
// FONT3
WriteFont(AStream, FontData);
// FONT5
WriteFont(AStream, FontData);
finally
FontData.Free;
end;
// XF0
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF1
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF2
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF3
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF4
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF5
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF6
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF7
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF8
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF9
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF10
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF11
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF12
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF13
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF14
WriteXF(AStream, 0, MASK_XF_TYPE_PROT_STYLE_XF);
// XF15
WriteXF(AStream, 0, 0);
WriteStyle(AStream);
// A BOUNDSHEET for each worksheet
for i := 0 to AData.GetWorksheetCount - 1 do
begin
len := Length(Boundsheets);
SetLength(Boundsheets, len + 1);
Boundsheets[len] := WriteBoundsheet(AStream, AData.GetWorksheetByIndex(i).Name);
end;
WriteEOF(AStream);
{ Write each worksheet }
for i := 0 to AData.GetWorksheetCount - 1 do
begin
{ First goes back and writes the position of the BOF of the
sheet on the respective BOUNDSHEET record }
CurrentPos := AStream.Position;
AStream.Position := Boundsheets[i];
AStream.WriteDWord(CurrentPos);
AStream.Position := CurrentPos;
WriteBOF(AStream, INT_BOF_SHEET);
WriteIndex(AStream);
WriteDimensions(AStream);
WriteWindow2(AStream, True);
WriteCellsToStream(AStream, AData.GetWorksheetByIndex(i).FCells);
WriteEOF(AStream);
end;
{ Cleanup }
SetLength(Boundsheets, 0);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteBOF ()
*
* DESCRIPTION: Writes an Excel 5 BOF record
*
* This must be the first record on an Excel 5 stream
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteBOF(AStream: TStream; ADataType: Word);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_BOF));
AStream.WriteWord(WordToLE(8));
{ BIFF version. Should only be used if this BOF is for the workbook globals }
if ADataType = INT_BOF_WORKBOOK_GLOBALS then
AStream.WriteWord(WordToLE(INT_BOF_BIFF5_VER))
else AStream.WriteWord(0);
{ Data type }
AStream.WriteWord(WordToLE(ADataType));
{ Build identifier, must not be 0 }
AStream.WriteWord(WordToLE(INT_BOF_BUILD_ID));
{ Build year, must not be 0 }
AStream.WriteWord(WordToLE(INT_BOF_BUILD_YEAR));
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteBoundsheet ()
*
* DESCRIPTION: Writes an Excel 5 BOUNDSHEET record
*
* Always located on the workbook globals substream.
*
* One BOUNDSHEET is written for each worksheet.
*
* RETURNS: The stream position where the absolute stream position
* of the BOF of this sheet should be written (4 bytes size).
*
*******************************************************************}
function TsSpreadBIFF5Writer.WriteBoundsheet(AStream: TStream; ASheetName: string): Int64;
var
Len: Byte;
begin
Len := Length(ASheetName);
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_BOUNDSHEET));
AStream.WriteWord(WordToLE(6 + 1 + Len));
{ Absolute stream position of the BOF record of the sheet represented
by this record }
Result := AStream.Position;
AStream.WriteDWord(WordToLE(0));
{ Visibility }
AStream.WriteByte(0);
{ Sheet type }
AStream.WriteByte(0);
{ Sheet name: Byte string, 8-bit length }
AStream.WriteByte(Len);
AStream.WriteBuffer(ASheetName[1], Len);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteIndex ()
*
* DESCRIPTION: Writes an Excel 5 DIMENSIONS record
*
* nm = (rl - rf - 1) / 32 + 1 (using integer division)
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteDimensions(AStream: TStream);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_DIMENSIONS));
AStream.WriteWord(WordToLE(10));
{ Index to first used row }
AStream.WriteWord(0);
{ Index to last used row, increased by 1 }
AStream.WriteWord(33);
{ Index to first used column }
AStream.WriteWord(0);
{ Index to last used column, increased by 1 }
AStream.WriteWord(10);
{ Not used }
AStream.WriteWord(0);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteEOF ()
*
* DESCRIPTION: Writes an Excel 5 EOF record
*
* This must be the last record on an Excel 5 stream
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteEOF(AStream: TStream);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_EOF));
AStream.WriteWord($0000);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteFont ()
*
* DESCRIPTION: Writes an Excel 5 FONT record
*
* The font data is passed in an instance of TFPCustomFont
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteFont(AStream: TStream; AFont: TFPCustomFont);
var
Len: Byte;
begin
Len := Length(AFont.Name);
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FONT));
AStream.WriteWord(WordToLE(14 + 1 + Len));
{ Height of the font in twips = 1/20 of a point }
AStream.WriteWord(WordToLE(200));
{ Option flags }
AStream.WriteWord(0);
{ Colour index }
AStream.WriteWord($7FFF);
{ Font weight }
AStream.WriteWord(WordToLE(INT_FONT_WEIGHT_NORMAL));
{ Escapement type }
AStream.WriteWord(0);
{ Underline type }
AStream.WriteByte(0);
{ Font family }
AStream.WriteByte(0);
{ Character set }
AStream.WriteByte(0);
{ Not used }
AStream.WriteByte(0);
{ Font name: Byte string, 8-bit length }
AStream.WriteByte(Len);
AStream.WriteBuffer(AFont.Name[1], Len);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteFormula ()
*
* DESCRIPTION: Writes an Excel 5 FORMULA record
*
* To input a formula to this method, first convert it
* to RPN, and then list all it's members in the
* AFormula array
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteFormula(AStream: TStream; const ARow,
ACol: Word; const AFormula: TRPNFormula);
var
FormulaResult: double;
i: Integer;
RPNLength: Word;
TokenArraySizePos, RecordSizePos, FinalPos: Int64;
begin
RPNLength := 0;
FormulaResult := 0.0;
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA));
RecordSizePos := AStream.Position;
AStream.WriteWord(WordToLE(22 + RPNLength));
{ BIFF Record data }
AStream.WriteWord(WordToLE(ARow));
AStream.WriteWord(WordToLE(ACol));
{ Index to XF Record }
AStream.WriteWord($0000);
{ Result of the formula in IEE 754 floating-point value }
AStream.WriteBuffer(FormulaResult, 8);
{ Options flags }
AStream.WriteWord(WordToLE(MASK_FORMULA_RECALCULATE_ALWAYS));
{ Not used }
AStream.WriteDWord(0);
{ Formula }
{ The size of the token array is written later,
because it's necessary to calculate if first,
and this is done at the same time it is written }
TokenArraySizePos := AStream.Position;
AStream.WriteWord(RPNLength);
{ Formula data (RPN token array) }
for i := 0 to Length(AFormula) - 1 do
begin
{ Token identifier }
AStream.WriteByte(AFormula[i].TokenID);
Inc(RPNLength);
{ Additional data }
case AFormula[i].TokenID of
{ binary operation tokens }
INT_EXCEL_TOKEN_TADD, INT_EXCEL_TOKEN_TSUB, INT_EXCEL_TOKEN_TMUL,
INT_EXCEL_TOKEN_TDIV, INT_EXCEL_TOKEN_TPOWER: begin end;
INT_EXCEL_TOKEN_TNUM:
begin
AStream.WriteBuffer(AFormula[i].DoubleValue, 8);
Inc(RPNLength, 8);
end;
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA:
begin
AStream.WriteWord(AFormula[i].Row and MASK_EXCEL_ROW);
AStream.WriteByte(AFormula[i].Col);
Inc(RPNLength, 3);
end;
end;
end;
{ Write sizes in the end, after we known them }
FinalPos := AStream.Position;
AStream.position := TokenArraySizePos;
AStream.WriteByte(RPNLength);
AStream.Position := RecordSizePos;
AStream.WriteWord(WordToLE(22 + RPNLength));
AStream.position := FinalPos;
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteIndex ()
*
* DESCRIPTION: Writes an Excel 5 INDEX record
*
* nm = (rl - rf - 1) / 32 + 1 (using integer division)
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteIndex(AStream: TStream);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_INDEX));
AStream.WriteWord(WordToLE(12));
{ Not used }
AStream.WriteDWord(0);
{ Index to first used row, rf, 0 based }
AStream.WriteWord(0);
{ Index to first row of unused tail of sheet, rl, last used row + 1, 0 based }
AStream.WriteWord(33);
{ Absolute stream position of the DEFCOLWIDTH record of the current sheet.
If it doesn't exist, the offset points to where it would occur. }
AStream.WriteDWord($00);
{ Array of nm absolute stream positions of the DBCELL record of each Row Block }
{ OBS: It seams to be no problem just ignoring this part of the record }
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteLabel ()
*
* DESCRIPTION: Writes an Excel 8 LABEL record
*
* Writes a string to the sheet
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteLabel(AStream: TStream; const ARow,
ACol: Word; const AValue: string);
var
L: Word;
AnsiValue: ansistring;
begin
AnsiValue := UTF8ToAnsi(AValue);
L := Length(AnsiValue);
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_LABEL));
AStream.WriteWord(WordToLE(8 + L));
{ BIFF Record data }
AStream.WriteWord(WordToLE(ARow));
AStream.WriteWord(WordToLE(ACol));
{ Index to XF record }
AStream.WriteWord(15);
{ Byte String with 16-bit size }
AStream.WriteWord(L);
AStream.WriteBuffer(AnsiValue[1], L);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteNumber ()
*
* DESCRIPTION: Writes an Excel 5 NUMBER record
*
* Writes a number (64-bit floating point) to the sheet
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteNumber(AStream: TStream; const ARow,
ACol: Cardinal; const AValue: double);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_NUMBER));
AStream.WriteWord(WordToLE(14));
{ BIFF Record data }
AStream.WriteWord(WordToLE(ARow));
AStream.WriteWord(WordToLE(ACol));
{ Index to XF record }
AStream.WriteWord($0);
{ IEE 754 floating-point value }
AStream.WriteBuffer(AValue, 8);
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteStyle ()
*
* DESCRIPTION: Writes an Excel 5 STYLE record
*
* Registers the name of a user-defined style or
* specific options for a built-in cell style.
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteStyle(AStream: TStream);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_STYLE));
AStream.WriteWord(WordToLE(4));
{ Index to style XF and defines if it's a built-in or used defined style }
AStream.WriteWord(WordToLE(MASK_STYLE_BUILT_IN));
{ Built-in cell style identifier }
AStream.WriteByte($00);
{ Level if the identifier for a built-in style is RowLevel or ColLevel, $FF otherwise }
AStream.WriteByte(WordToLE($FF));
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteWindow1 ()
*
* DESCRIPTION: Writes an Excel 5 WINDOW1 record
*
* This record contains general settings for the
* document window and global workbook settings.
*
* The values written here are reasonable defaults,
* which should work for most sheets.
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteWindow1(AStream: TStream);
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW1));
AStream.WriteWord(WordToLE(18));
{ Horizontal position of the document window, in twips = 1 / 20 of a point }
AStream.WriteWord(0);
{ Vertical position of the document window, in twips = 1 / 20 of a point }
AStream.WriteWord(WordToLE($0069));
{ Width of the document window, in twips = 1 / 20 of a point }
AStream.WriteWord(WordToLE($339F));
{ Height of the document window, in twips = 1 / 20 of a point }
AStream.WriteWord(WordToLE($1B5D));
{ Option flags }
AStream.WriteWord(WordToLE(
MASK_WINDOW1_OPTION_HORZ_SCROLL_VISIBLE or
MASK_WINDOW1_OPTION_VERT_SCROLL_VISIBLE or
MASK_WINDOW1_OPTION_WORKSHEET_TAB_VISIBLE));
{ Index to active (displayed) worksheet }
AStream.WriteWord($00);
{ Index of first visible tab in the worksheet tab bar }
AStream.WriteWord($00);
{ Number of selected worksheets }
AStream.WriteWord(WordToLE(1));
{ Width of worksheet tab bar (in 1/1000 of window width).
The remaining space is used by the horizontal scroll bar }
AStream.WriteWord(WordToLE(600));
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteWindow1 ()
*
* DESCRIPTION: Writes an Excel 5 WINDOW1 record
*
* This record contains aditional settings for the
* document window (BIFF2-BIFF4) or for a specific
* worksheet (BIFF5-BIFF8).
*
* The values written here are reasonable defaults,
* which should work for most sheets.
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteWindow2(AStream: TStream;
ASheetSelected: Boolean);
var
Options: Word;
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW2));
AStream.WriteWord(WordToLE(10));
{ Options flags }
Options := MASK_WINDOW2_OPTION_SHOW_GRID_LINES or
MASK_WINDOW2_OPTION_SHOW_SHEET_HEADERS or
MASK_WINDOW2_OPTION_SHOW_ZERO_VALUES or
MASK_WINDOW2_OPTION_AUTO_GRIDLINE_COLOR or
MASK_WINDOW2_OPTION_SHOW_OUTLINE_SYMBOLS or
MASK_WINDOW2_OPTION_SHEET_ACTIVE;
if ASheetSelected then Options := Options or MASK_WINDOW2_OPTION_SHEET_SELECTED;
AStream.WriteWord(WordToLE(Options));
{ Index to first visible row }
AStream.WriteWord(WordToLE(0));
{ Index to first visible column }
AStream.WriteWord(WordToLE(0));
{ Grid line RGB colour }
AStream.WriteDWord(WordToLE(0));
end;
{*******************************************************************
* TsSpreadBIFF5Writer.WriteXF ()
*
* DESCRIPTION: Writes an Excel 5 XF record
*
* Writes a number (64-bit floating point) to the sheet
*
*******************************************************************}
procedure TsSpreadBIFF5Writer.WriteXF(AStream: TStream; AFontIndex: Word;
AXF_TYPE_PROT: Byte);
var
XFOptions: Word;
XFAlignment, XFOrientationAttrib: Byte;
begin
{ BIFF Record header }
AStream.WriteWord(WordToLE(INT_EXCEL_ID_XF));
AStream.WriteWord(WordToLE(16));
{ Index to FONT record }
AStream.WriteWord(WordToLE(AFontIndex));
{ Index to FORMAT record }
AStream.WriteWord($00);
{ XF type, cell protection and parent style XF }
XFOptions := AXF_TYPE_PROT and MASK_XF_TYPE_PROT;
if AXF_TYPE_PROT and MASK_XF_TYPE_PROT_STYLE_XF <> 0 then
XFOptions := XFOptions or MASK_XF_TYPE_PROT_PARENT;
AStream.WriteWord(WordToLE(XFOptions));
{ Alignment and text break }
XFAlignment := MASK_XF_VERT_ALIGN_BOTTOM;
AStream.WriteByte(WordToLE(XFAlignment));
{ Text orientation and flags for used attribute groups }
XFOrientationAttrib :=
MASK_XF_USED_ATTRIB_NUMBER_FORMAT or
MASK_XF_USED_ATTRIB_FONT or
MASK_XF_USED_ATTRIB_TEXT or
MASK_XF_USED_ATTRIB_BORDER_LINES or
MASK_XF_USED_ATTRIB_BACKGROUND or
MASK_XF_USED_ATTRIB_CELL_PROTECTION;
AStream.WriteByte(WordToLE(XFOrientationAttrib));
{ Cell border lines and background area }
AStream.WriteDWord($000020C0);
AStream.WriteDWord($00000000);
end;
{*******************************************************************
* Initialization section
*
* Registers this reader / writer on fpSpreadsheet
*
*******************************************************************}
{ TsSpreadBIFF5Reader }
procedure TsSpreadBIFF5Reader.ReadWorkbookGlobals(AStream: TStream;
AData: TsWorkbook);
var
SectionEOF: Boolean = False;
RecordType: Word;
CurStreamPos: Int64;
begin
while (not SectionEOF) do
begin
{ Read the record header }
RecordType := WordLEToN(AStream.ReadWord);
RecordSize := WordLEToN(AStream.ReadWord);
CurStreamPos := AStream.Position;
case RecordType of
INT_EXCEL_ID_BOF: ;
INT_EXCEL_ID_BOUNDSHEET: ReadBoundSheet(AStream);
INT_EXCEL_ID_EOF: SectionEOF := True;
else
// nothing
end;
// Make sure we are in the right position for the next record
AStream.Seek(CurStreamPos + RecordSize, soFromBeginning);
// Check for the end of the file
if AStream.Position >= AStream.Size then SectionEOF := True;
end;
end;
procedure TsSpreadBIFF5Reader.ReadWorksheet(AStream: TStream; AData: TsWorkbook);
var
SectionEOF: Boolean = False;
RecordType: Word;
CurStreamPos: Int64;
begin
FWorksheet := AData.AddWorksheet(FWorksheetNames.Strings[FCurrentWorksheet]);
while (not SectionEOF) do
begin
{ Read the record header }
RecordType := WordLEToN(AStream.ReadWord);
RecordSize := WordLEToN(AStream.ReadWord);
CurStreamPos := AStream.Position;
case RecordType of
INT_EXCEL_ID_NUMBER: ReadNumber(AStream);
INT_EXCEL_ID_LABEL: ReadLabel(AStream);
INT_EXCEL_ID_FORMULA: ReadFormula(AStream);
INT_EXCEL_ID_BOF: ;
INT_EXCEL_ID_EOF: SectionEOF := True;
else
// nothing
end;
// Make sure we are in the right position for the next record
AStream.Seek(CurStreamPos + RecordSize, soFromBeginning);
// Check for the end of the file
if AStream.Position >= AStream.Size then SectionEOF := True;
end;
end;
procedure TsSpreadBIFF5Reader.ReadBoundsheet(AStream: TStream);
var
Len: Byte;
Str: array[0..255] of Char;
begin
{ Absolute stream position of the BOF record of the sheet represented
by this record }
// Just assume that they are in order
AStream.ReadDWord();
{ Visibility }
AStream.ReadByte();
{ Sheet type }
AStream.ReadByte();
{ Sheet name: Byte string, 8-bit length }
Len := AStream.ReadByte();
AStream.ReadBuffer(Str, Len);
Str[Len] := #0;
FWorksheetNames.Add(Str);
end;
procedure TsSpreadBIFF5Reader.ReadFromFile(AFileName: string; AData: TsWorkbook);
var
MemStream: TMemoryStream;
OLEStorage: TOLEStorage;
OLEDocument: TOLEDocument;
begin
MemStream := TMemoryStream.Create;
OLEStorage := TOLEStorage.Create;
try
// Only one stream is necessary for any number of worksheets
OLEDocument.Stream := MemStream;
OLEStorage.ReadOLEFile(AFileName, OLEDocument);
// Rewind the stream and read from it
MemStream.Position := 0;
ReadFromStream(MemStream, AData);
// Uncomment to verify if the data was correctly optained from the OLE file
// MemStream.SaveToFile(SysUtils.ChangeFileExt(AFileName, 'bin.xls'));
finally
MemStream.Free;
OLEStorage.Free;
end;
end;
procedure TsSpreadBIFF5Reader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
var
BIFF5EOF: Boolean;
begin
{ Initializations }
FWorksheetNames := TStringList.Create;
FWorksheetNames.Clear;
FCurrentWorksheet := 0;
BIFF5EOF := False;
{ Read workbook globals }
ReadWorkbookGlobals(AStream, AData);
// Check for the end of the file
if AStream.Position >= AStream.Size then BIFF5EOF := True;
{ Now read all worksheets }
while (not BIFF5EOF) do
begin
ReadWorksheet(AStream, AData);
// Check for the end of the file
if AStream.Position >= AStream.Size then BIFF5EOF := True;
// Final preparations
Inc(FCurrentWorksheet);
end;
{ Finalizations }
FWorksheetNames.Free;
end;
procedure TsSpreadBIFF5Reader.ReadFormula(AStream: TStream);
begin
end;
procedure TsSpreadBIFF5Reader.ReadLabel(AStream: TStream);
begin
end;
procedure TsSpreadBIFF5Reader.ReadNumber(AStream: TStream);
var
ARow, ACol: Word;
AValue: Double;
begin
{ BIFF Record data }
ARow := WordLEToN(AStream.ReadWord);
ACol := WordLEToN(AStream.ReadWord);
{ Index to XF record }
AStream.ReadWord();
{ IEE 754 floating-point value }
AStream.ReadBuffer(AValue, 8);
{ Save the data }
FWorksheet.WriteNumber(ARow, ACol, AValue);
end;
initialization
RegisterSpreadFormat(TsSpreadBIFF5Reader, TsSpreadBIFF5Writer, sfExcel5);
end.