2008-02-24 13:18:34 +00:00
|
|
|
{
|
|
|
|
xlsbiff2.pas
|
|
|
|
|
|
|
|
Writes an Excel 2.x file
|
|
|
|
|
|
|
|
Excel 2.x files support only one Worksheet per Workbook, so only the first
|
|
|
|
will be written.
|
|
|
|
|
|
|
|
An Excel file 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
|
|
|
|
|
2009-01-29 13:24:37 +00:00
|
|
|
The row and column numbering in BIFF files is zero-based.
|
2009-01-29 11:30:38 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
Excel file format specification obtained from:
|
|
|
|
|
|
|
|
http://sc.openoffice.org/excelfileformat.pdf
|
|
|
|
|
2009-08-12 13:08:44 +00:00
|
|
|
Encoding information: ISO_8859_1 is used, to have support to
|
|
|
|
other characters, please use a format which support unicode
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
AUTHORS: Felipe Monteiro de Carvalho
|
|
|
|
}
|
|
|
|
unit xlsbiff2;
|
|
|
|
|
|
|
|
{$ifdef fpc}
|
|
|
|
{$mode delphi}
|
|
|
|
{$endif}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
Classes, SysUtils,
|
2011-12-24 23:42:08 +00:00
|
|
|
fpspreadsheet, xlscommon, fpsutils, lconvencoding;
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
{ TsSpreadBIFF2Reader }
|
|
|
|
|
|
|
|
TsSpreadBIFF2Reader = class(TsCustomSpreadReader)
|
|
|
|
private
|
2013-02-13 07:22:29 +00:00
|
|
|
WorkBookEncoding: TsEncoding;
|
2009-01-10 21:47:59 +00:00
|
|
|
RecordSize: Word;
|
|
|
|
FWorksheet: TsWorksheet;
|
2014-04-20 15:10:41 +00:00
|
|
|
procedure ReadRowInfo(AStream: TStream);
|
2014-04-21 11:30:22 +00:00
|
|
|
protected
|
2009-01-10 21:47:59 +00:00
|
|
|
{ Record writing methods }
|
|
|
|
procedure ReadFormula(AStream: TStream); override;
|
|
|
|
procedure ReadLabel(AStream: TStream); override;
|
|
|
|
procedure ReadNumber(AStream: TStream); override;
|
2012-12-11 16:53:54 +00:00
|
|
|
procedure ReadInteger(AStream: TStream);
|
2014-04-21 11:30:22 +00:00
|
|
|
public
|
|
|
|
{ General reading methods }
|
|
|
|
procedure ReadFromStream(AStream: TStream; AData: TsWorkbook); override;
|
2009-01-10 21:47:59 +00:00
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ TsSpreadBIFF2Writer }
|
|
|
|
|
2014-04-08 09:48:30 +00:00
|
|
|
TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter)
|
2009-06-09 11:19:10 +00:00
|
|
|
private
|
2011-05-25 15:50:18 +00:00
|
|
|
procedure WriteCellFormatting(AStream: TStream; ACell: PCell);
|
2008-02-24 13:18:34 +00:00
|
|
|
{ Record writing methods }
|
|
|
|
procedure WriteBOF(AStream: TStream);
|
|
|
|
procedure WriteEOF(AStream: TStream);
|
2013-12-07 13:42:22 +00:00
|
|
|
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); override;
|
|
|
|
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); override;
|
2010-12-08 10:24:15 +00:00
|
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); override;
|
2013-12-22 14:02:04 +00:00
|
|
|
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell); override;
|
2014-04-21 11:30:22 +00:00
|
|
|
public
|
|
|
|
{ General writing methods }
|
|
|
|
procedure WriteToStream(AStream: TStream; AData: TsWorkbook); override;
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
const
|
|
|
|
{ Excel record IDs }
|
2012-12-11 16:53:54 +00:00
|
|
|
INT_EXCEL_ID_INTEGER = $0002;
|
2008-02-24 13:18:34 +00:00
|
|
|
INT_EXCEL_ID_NUMBER = $0003;
|
|
|
|
INT_EXCEL_ID_LABEL = $0004;
|
|
|
|
INT_EXCEL_ID_FORMULA = $0006;
|
2014-04-20 15:10:41 +00:00
|
|
|
INT_EXCEL_ID_ROWINFO = $0008;
|
2008-02-24 13:18:34 +00:00
|
|
|
INT_EXCEL_ID_BOF = $0009;
|
|
|
|
INT_EXCEL_ID_EOF = $000A;
|
|
|
|
|
|
|
|
{ Cell Addresses constants }
|
|
|
|
MASK_EXCEL_ROW = $3FFF;
|
2014-04-08 09:48:30 +00:00
|
|
|
MASK_EXCEL_RELATIVE_COL = $4000; // This is according to Microsoft documentation,
|
|
|
|
MASK_EXCEL_RELATIVE_ROW = $8000; // but opposite to OpenOffice documentation!
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BOF record constants }
|
|
|
|
INT_EXCEL_SHEET = $0010;
|
|
|
|
INT_EXCEL_CHART = $0020;
|
|
|
|
INT_EXCEL_MACRO_SHEET = $0040;
|
|
|
|
|
|
|
|
{ TsSpreadBIFF2Writer }
|
|
|
|
|
2011-05-25 15:50:18 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell);
|
|
|
|
var
|
|
|
|
BorderByte: Byte = 0;
|
|
|
|
begin
|
|
|
|
if ACell^.UsedFormattingFields = [] then
|
|
|
|
begin
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
Exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
|
|
|
|
// The Border and Background
|
|
|
|
|
|
|
|
BorderByte := 0;
|
|
|
|
|
|
|
|
if uffBorder in ACell^.UsedFormattingFields then
|
|
|
|
begin
|
|
|
|
if cbNorth in ACell^.Border then BorderByte := BorderByte or $20;
|
|
|
|
if cbWest in ACell^.Border then BorderByte := BorderByte or $08;
|
|
|
|
if cbEast in ACell^.Border then BorderByte := BorderByte or $10;
|
|
|
|
if cbSouth in ACell^.Border then BorderByte := BorderByte or $40;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// BIFF2 does not support a background color, just a "shaded" option
|
|
|
|
if uffBackgroundColor in ACell^.UsedFormattingFields then
|
|
|
|
BorderByte := BorderByte or $80;
|
|
|
|
|
|
|
|
AStream.WriteByte(BorderByte);
|
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 file to a stream
|
|
|
|
|
|
|
|
Excel 2.x files support only one Worksheet per Workbook,
|
|
|
|
so only the first will be written.
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream; AData: TsWorkbook);
|
|
|
|
begin
|
|
|
|
WriteBOF(AStream);
|
|
|
|
|
2009-09-02 22:03:01 +00:00
|
|
|
WriteCellsToStream(AStream, AData.GetFirstWorksheet.Cells);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
WriteEOF(AStream);
|
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 BOF record
|
|
|
|
|
|
|
|
This must be the first record on an Excel 2 stream
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteBOF(AStream: TStream);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_BOF));
|
|
|
|
AStream.WriteWord(WordToLE($0004));
|
|
|
|
|
|
|
|
{ Unused }
|
|
|
|
AStream.WriteWord($0000);
|
|
|
|
|
|
|
|
{ Data type }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_SHEET));
|
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 EOF record
|
|
|
|
|
|
|
|
This must be the last record on an Excel 2 stream
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteEOF(AStream: TStream);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_EOF));
|
|
|
|
AStream.WriteWord($0000);
|
|
|
|
end;
|
|
|
|
|
2009-02-02 09:58:51 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 FORMULA record
|
|
|
|
|
|
|
|
The formula needs to be converted from usual user-readable string
|
|
|
|
to an RPN array
|
|
|
|
|
|
|
|
// or, in RPN: A1, B1, +
|
|
|
|
SetLength(MyFormula, 3);
|
|
|
|
MyFormula[0].TokenID := INT_EXCEL_TOKEN_TREFV; A1
|
|
|
|
MyFormula[0].Col := 0;
|
|
|
|
MyFormula[0].Row := 0;
|
|
|
|
MyFormula[1].TokenID := INT_EXCEL_TOKEN_TREFV; B1
|
|
|
|
MyFormula[1].Col := 1;
|
|
|
|
MyFormula[1].Row := 0;
|
|
|
|
MyFormula[2].TokenID := INT_EXCEL_TOKEN_TADD; +
|
|
|
|
}
|
2009-06-09 11:19:10 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream; const ARow,
|
2013-12-07 13:42:22 +00:00
|
|
|
ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
|
2009-06-09 11:19:10 +00:00
|
|
|
var
|
2008-02-24 13:18:34 +00:00
|
|
|
FormulaResult: double;
|
|
|
|
i: Integer;
|
|
|
|
RPNLength: Word;
|
2009-06-09 11:19:10 +00:00
|
|
|
TokenArraySizePos, RecordSizePos, FinalPos: Cardinal;
|
2014-04-08 09:48:30 +00:00
|
|
|
FormulaKind, ExtraInfo: Word;
|
|
|
|
r: Cardinal;
|
|
|
|
len: Integer;
|
|
|
|
s: ansistring;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2009-06-09 11:19:10 +00:00
|
|
|
RPNLength := 0;
|
2008-02-24 13:18:34 +00:00
|
|
|
FormulaResult := 0.0;
|
|
|
|
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA));
|
|
|
|
RecordSizePos := AStream.Position;
|
|
|
|
AStream.WriteWord(WordToLE(17 + RPNLength));
|
|
|
|
|
|
|
|
{ BIFF Record data }
|
2009-01-29 13:24:37 +00:00
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
|
2014-04-08 09:48:30 +00:00
|
|
|
{ Result of the formula in IEEE 754 floating-point value }
|
2008-02-24 13:18:34 +00:00
|
|
|
AStream.WriteBuffer(FormulaResult, 8);
|
|
|
|
|
|
|
|
{ 0 = Do not recalculate
|
|
|
|
1 = Always recalculate }
|
|
|
|
AStream.WriteByte($1);
|
|
|
|
|
|
|
|
{ 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.WriteByte(RPNLength);
|
|
|
|
|
|
|
|
{ Formula data (RPN token array) }
|
|
|
|
for i := 0 to Length(AFormula) - 1 do
|
|
|
|
begin
|
2014-04-08 09:48:30 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ Token identifier }
|
2014-04-08 09:48:30 +00:00
|
|
|
FormulaKind := FormulaElementKindToExcelTokenID(AFormula[i].ElementKind, ExtraInfo);
|
2009-06-09 11:19:10 +00:00
|
|
|
AStream.WriteByte(FormulaKind);
|
2008-02-24 13:18:34 +00:00
|
|
|
Inc(RPNLength);
|
|
|
|
|
|
|
|
{ Additional data }
|
2009-06-09 11:19:10 +00:00
|
|
|
case FormulaKind of
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ 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;
|
|
|
|
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TSTR:
|
|
|
|
begin
|
|
|
|
s := ansistring(AFormula[i].StringValue);
|
|
|
|
len := Length(s);
|
|
|
|
AStream.WriteByte(len);
|
|
|
|
AStream.WriteBuffer(s[1], len);
|
|
|
|
Inc(RPNLength, len + 1);
|
|
|
|
end;
|
|
|
|
|
|
|
|
INT_EXCEL_TOKEN_TBOOL:
|
|
|
|
begin
|
|
|
|
AStream.WriteByte(ord(AFormula[i].DoubleValue <> 0.0));
|
|
|
|
inc(RPNLength, 1);
|
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA:
|
|
|
|
begin
|
2014-04-08 09:48:30 +00:00
|
|
|
r := AFormula[i].Row and MASK_EXCEL_ROW;
|
|
|
|
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
|
|
|
|
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
|
|
|
|
AStream.WriteWord(r);
|
2009-01-29 13:24:37 +00:00
|
|
|
AStream.WriteByte(AFormula[i].Col);
|
2008-02-24 13:18:34 +00:00
|
|
|
Inc(RPNLength, 3);
|
|
|
|
end;
|
|
|
|
|
2014-04-08 09:48:30 +00:00
|
|
|
INT_EXCEL_TOKEN_TAREA_R: { fekCellRange }
|
|
|
|
begin
|
|
|
|
r := AFormula[i].Row and MASK_EXCEL_ROW;
|
|
|
|
if (rfRelRow in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
|
|
|
|
if (rfRelCol in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
|
|
|
|
AStream.WriteWord(WordToLE(r));
|
|
|
|
|
|
|
|
r := AFormula[i].Row2 and MASK_EXCEL_ROW;
|
|
|
|
if (rfRelRow2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_ROW;
|
|
|
|
if (rfRelCol2 in AFormula[i].RelFlags) then r := r or MASK_EXCEL_RELATIVE_COL;
|
|
|
|
AStream.WriteWord(WordToLE(r));
|
|
|
|
|
|
|
|
AStream.WriteByte(AFormula[i].Col);
|
|
|
|
AStream.WriteByte(AFormula[i].Col2);
|
|
|
|
Inc(RPNLength, 6);
|
|
|
|
end;
|
|
|
|
|
|
|
|
INT_EXCEL_TOKEN_FUNC_R, INT_EXCEL_TOKEN_FUNC_V, INT_EXCEL_TOKEN_FUNC_A:
|
|
|
|
begin
|
|
|
|
AStream.WriteByte(Lo(ExtraInfo));
|
|
|
|
Inc(RPNLength, 1);
|
|
|
|
end;
|
|
|
|
|
2009-06-09 21:34:58 +00:00
|
|
|
INT_EXCEL_TOKEN_FUNCVAR_V:
|
|
|
|
begin
|
2014-04-08 09:48:30 +00:00
|
|
|
AStream.WriteByte(AFormula[i].ParamsNum);
|
|
|
|
AStream.WriteByte(Lo(ExtraInfo));
|
|
|
|
// taking only the low-bytes, the high-bytes are needed for compatibility
|
|
|
|
// with other BIFF formats...
|
2009-06-09 21:34:58 +00:00
|
|
|
Inc(RPNLength, 2);
|
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
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(17 + RPNLength));
|
2009-06-09 11:19:10 +00:00
|
|
|
AStream.position := FinalPos;
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadBIFF2Writer.WriteLabel ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes an Excel 2 LABEL record
|
|
|
|
*
|
|
|
|
* Writes a string to the sheet
|
2013-12-07 13:42:22 +00:00
|
|
|
* If the string length exceeds 255 bytes, the string
|
|
|
|
* will be truncated and an exception will be raised as
|
|
|
|
* a warning.
|
2008-02-24 13:18:34 +00:00
|
|
|
*
|
|
|
|
*******************************************************************}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteLabel(AStream: TStream; const ARow,
|
2013-12-07 13:42:22 +00:00
|
|
|
ACol: Cardinal; const AValue: string; ACell: PCell);
|
|
|
|
const
|
|
|
|
MaxBytes=255; //limit for this format
|
2008-02-24 13:18:34 +00:00
|
|
|
var
|
|
|
|
L: Byte;
|
2009-01-28 17:18:04 +00:00
|
|
|
AnsiText: ansistring;
|
2013-12-07 13:42:22 +00:00
|
|
|
TextTooLong: boolean=false;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2009-04-18 12:31:46 +00:00
|
|
|
if AValue = '' then Exit; // Writing an empty text doesn't work
|
|
|
|
|
2009-08-12 12:46:33 +00:00
|
|
|
AnsiText := UTF8ToISO_8859_1(AValue);
|
2013-12-07 13:42:22 +00:00
|
|
|
|
|
|
|
if Length(AnsiText)>MaxBytes then
|
|
|
|
begin
|
|
|
|
// BIFF 5 does not support labels/text bigger than 255 chars,
|
|
|
|
// so BIFF2 won't either
|
|
|
|
// Rather than lose data when reading it, let the application programmer deal
|
|
|
|
// with the problem or purposefully ignore it.
|
|
|
|
TextTooLong:=true;
|
|
|
|
AnsiText := Copy(AnsiText,1,MaxBytes);
|
|
|
|
end;
|
2009-01-28 17:18:04 +00:00
|
|
|
L := Length(AnsiText);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_LABEL));
|
|
|
|
AStream.WriteWord(WordToLE(8 + L));
|
|
|
|
|
|
|
|
{ BIFF Record data }
|
2009-01-29 13:24:37 +00:00
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
2011-05-25 15:50:18 +00:00
|
|
|
WriteCellFormatting(AStream, ACell);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ String with 8-bit size }
|
|
|
|
AStream.WriteByte(L);
|
2009-01-28 17:18:04 +00:00
|
|
|
AStream.WriteBuffer(AnsiText[1], L);
|
2013-12-07 13:42:22 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
//todo: keep a log of errors and show with an exception after writing file or something.
|
|
|
|
We can't just do the following
|
|
|
|
if TextTooLong then
|
|
|
|
Raise Exception.CreateFmt('Text value exceeds %d character limit in cell [%d,%d]. Text has been truncated.',[MaxBytes,ARow,ACol]);
|
|
|
|
because the file wouldn't be written.
|
|
|
|
}
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadBIFF2Writer.WriteNumber ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes an Excel 2 NUMBER record
|
|
|
|
*
|
|
|
|
* Writes a number (64-bit IEE 754 floating point) to the sheet
|
|
|
|
*
|
|
|
|
*******************************************************************}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteNumber(AStream: TStream; const ARow,
|
2010-12-08 10:24:15 +00:00
|
|
|
ACol: Cardinal; const AValue: double; ACell: PCell);
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_NUMBER));
|
|
|
|
AStream.WriteWord(WordToLE(15));
|
|
|
|
|
|
|
|
{ BIFF Record data }
|
2009-01-29 13:24:37 +00:00
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
|
|
|
|
{ IEE 754 floating-point value }
|
|
|
|
AStream.WriteBuffer(AValue, 8);
|
|
|
|
end;
|
|
|
|
|
2013-12-22 14:02:04 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadBIFF2Writer.WriteDateTime ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes a date/time value as a text
|
2013-12-23 12:11:20 +00:00
|
|
|
* ISO 8601 format is used to preserve interoperability
|
|
|
|
* between locales.
|
2013-12-22 14:02:04 +00:00
|
|
|
*
|
2013-12-23 12:11:20 +00:00
|
|
|
* Note: this should be replaced by writing actual date/time values
|
2013-12-22 14:02:04 +00:00
|
|
|
*
|
|
|
|
*******************************************************************}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteDateTime(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell);
|
|
|
|
begin
|
2013-12-23 12:11:20 +00:00
|
|
|
WriteLabel(AStream, ARow, ACol, FormatDateTime(ISO8601Format, AValue), ACell);
|
2013-12-22 14:02:04 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
{ TsSpreadBIFF2Reader }
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
|
|
|
|
var
|
|
|
|
BIFF2EOF: Boolean;
|
|
|
|
RecordType: Word;
|
|
|
|
CurStreamPos: Int64;
|
|
|
|
begin
|
2013-02-13 07:22:29 +00:00
|
|
|
{ Store some data about the workbook that other routines need }
|
|
|
|
WorkBookEncoding := AData.Encoding;
|
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
BIFF2EOF := False;
|
|
|
|
|
|
|
|
{ In BIFF2 files there is only one worksheet, let's create it }
|
|
|
|
FWorksheet := AData.AddWorksheet('');
|
|
|
|
|
|
|
|
{ Read all records in a loop }
|
|
|
|
while not BIFF2EOF do
|
|
|
|
begin
|
|
|
|
{ Read the record header }
|
2009-01-10 22:58:00 +00:00
|
|
|
RecordType := WordLEToN(AStream.ReadWord);
|
|
|
|
RecordSize := WordLEToN(AStream.ReadWord);
|
2009-01-10 21:47:59 +00:00
|
|
|
|
|
|
|
CurStreamPos := AStream.Position;
|
|
|
|
|
|
|
|
case RecordType of
|
|
|
|
|
2012-12-11 16:53:54 +00:00
|
|
|
INT_EXCEL_ID_INTEGER: ReadInteger(AStream);
|
2009-01-10 21:47:59 +00:00
|
|
|
INT_EXCEL_ID_NUMBER: ReadNumber(AStream);
|
|
|
|
INT_EXCEL_ID_LABEL: ReadLabel(AStream);
|
|
|
|
INT_EXCEL_ID_FORMULA: ReadFormula(AStream);
|
2014-04-20 15:10:41 +00:00
|
|
|
INT_EXCEL_ID_ROWINFO: ReadRowInfo(AStream);
|
2009-01-10 21:47:59 +00:00
|
|
|
INT_EXCEL_ID_BOF: ;
|
|
|
|
INT_EXCEL_ID_EOF: BIFF2EOF := True;
|
2012-12-11 16:53:54 +00:00
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
else
|
|
|
|
// nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Make sure we are in the right position for the next record
|
|
|
|
AStream.Seek(CurStreamPos + RecordSize, soFromBeginning);
|
|
|
|
|
|
|
|
if AStream.Position >= AStream.Size then BIFF2EOF := True;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFormula(AStream: TStream);
|
|
|
|
begin
|
|
|
|
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadLabel(AStream: TStream);
|
|
|
|
var
|
|
|
|
L: Byte;
|
|
|
|
ARow, ACol: Word;
|
|
|
|
AValue: array[0..255] of Char;
|
2013-02-13 07:22:29 +00:00
|
|
|
AStrValue: UTF8String;
|
2009-01-10 21:47:59 +00:00
|
|
|
begin
|
|
|
|
{ BIFF Record data }
|
2009-01-29 13:24:37 +00:00
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
ACol := WordLEToN(AStream.ReadWord);
|
2009-01-10 21:47:59 +00:00
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
|
|
|
|
{ String with 8-bit size }
|
|
|
|
L := AStream.ReadByte();
|
|
|
|
AStream.ReadBuffer(AValue, L);
|
|
|
|
AValue[L] := #0;
|
|
|
|
|
|
|
|
{ Save the data }
|
2013-02-13 07:22:29 +00:00
|
|
|
case WorkBookEncoding of
|
|
|
|
seLatin2: AStrValue := CP1250ToUTF8(AValue);
|
|
|
|
seCyrillic: AStrValue := CP1251ToUTF8(AValue);
|
|
|
|
seGreek: AStrValue := CP1253ToUTF8(AValue);
|
|
|
|
seTurkish: AStrValue := CP1254ToUTF8(AValue);
|
|
|
|
seHebrew: AStrValue := CP1255ToUTF8(AValue);
|
|
|
|
seArabic: AStrValue := CP1256ToUTF8(AValue);
|
|
|
|
else
|
|
|
|
// Latin 1 is the default
|
|
|
|
AStrValue := CP1252ToUTF8(AValue);
|
|
|
|
end;
|
|
|
|
FWorksheet.WriteUTF8Text(ARow, ACol, AStrValue);
|
2009-01-10 21:47:59 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadNumber(AStream: TStream);
|
|
|
|
var
|
|
|
|
ARow, ACol: Word;
|
|
|
|
AValue: Double;
|
|
|
|
begin
|
|
|
|
{ BIFF Record data }
|
2009-01-29 13:24:37 +00:00
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
ACol := WordLEToN(AStream.ReadWord);
|
2009-01-10 21:47:59 +00:00
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
|
|
|
|
{ IEE 754 floating-point value }
|
|
|
|
AStream.ReadBuffer(AValue, 8);
|
|
|
|
|
|
|
|
{ Save the data }
|
|
|
|
FWorksheet.WriteNumber(ARow, ACol, AValue);
|
|
|
|
end;
|
|
|
|
|
2012-12-11 16:53:54 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadInteger(AStream: TStream);
|
|
|
|
var
|
|
|
|
ARow, ACol: Word;
|
|
|
|
AWord : Word;
|
|
|
|
begin
|
|
|
|
{ BIFF Record data }
|
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
ACol := WordLEToN(AStream.ReadWord);
|
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
AStream.ReadByte();
|
|
|
|
|
|
|
|
{ 16 bit unsigned integer }
|
|
|
|
AStream.ReadBuffer(AWord, 2);
|
|
|
|
|
|
|
|
{ Save the data }
|
|
|
|
FWorksheet.WriteNumber(ARow, ACol, AWord);
|
|
|
|
end;
|
|
|
|
|
2014-04-20 15:10:41 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadRowInfo(AStream: TStream);
|
|
|
|
type
|
|
|
|
TRowRecord = packed record
|
|
|
|
RowIndex: Word;
|
|
|
|
Col1: Word;
|
|
|
|
Col2: Word;
|
|
|
|
Height: Word;
|
|
|
|
end;
|
|
|
|
var
|
|
|
|
rowrec: TRowRecord;
|
|
|
|
lRow: PRow;
|
|
|
|
h: word;
|
|
|
|
begin
|
|
|
|
AStream.ReadBuffer(rowrec, SizeOf(TRowRecord));
|
|
|
|
h := WordLEToN(rowrec.Height);
|
|
|
|
if h and $8000 = 0 then begin // if this bit were set, rowheight would be default
|
|
|
|
lRow := FWorksheet.GetRow(WordLEToN(rowrec.RowIndex));
|
|
|
|
// Row height is encoded into the 15 remaining bits in units "twips" (1/20 pt)
|
|
|
|
lRow^.Height := TwipsToMillimeters(h and $7FFF);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* Initialization section
|
|
|
|
*
|
|
|
|
* Registers this reader / writer on fpSpreadsheet
|
|
|
|
*
|
|
|
|
*******************************************************************}
|
2009-01-10 21:47:59 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
initialization
|
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
RegisterSpreadFormat(TsSpreadBIFF2Reader, TsSpreadBIFF2Writer, sfExcel2);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
end.
|