
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@688 8e941d3f-bd1b-0410-a28a-d453659cc2b4
1077 lines
30 KiB
ObjectPascal
Executable File
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.
|
|
|