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;
|
2014-08-11 22:39:47 +00:00
|
|
|
|
|
|
|
const
|
|
|
|
BIFF2_MAX_PALETTE_SIZE = 8;
|
|
|
|
// There are more colors but they do not seem to be controlled by a palette.
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
type
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
{ TsBIFF2NumFormatList }
|
|
|
|
TsBIFF2NumFormatList = class(TsCustomNumFormatList)
|
|
|
|
protected
|
|
|
|
procedure AddBuiltinFormats; override;
|
2014-06-19 19:25:40 +00:00
|
|
|
public
|
|
|
|
constructor Create(AWorkbook: TsWorkbook);
|
2014-05-21 23:01:07 +00:00
|
|
|
procedure ConvertBeforeWriting(var AFormatString: String;
|
2014-06-13 13:33:02 +00:00
|
|
|
var ANumFormat: TsNumberFormat); override;
|
2014-05-21 23:01:07 +00:00
|
|
|
function FindFormatOf(AFormatCell: PCell): Integer; override;
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
|
2009-01-10 21:47:59 +00:00
|
|
|
{ TsSpreadBIFF2Reader }
|
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
TsSpreadBIFF2Reader = class(TsSpreadBIFFReader)
|
2009-01-10 21:47:59 +00:00
|
|
|
private
|
2013-02-13 07:22:29 +00:00
|
|
|
WorkBookEncoding: TsEncoding;
|
2014-04-23 22:29:32 +00:00
|
|
|
FFont: TsFont;
|
2014-04-21 11:30:22 +00:00
|
|
|
protected
|
2014-07-24 15:56:03 +00:00
|
|
|
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); override;
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure CreateNumFormatList; override;
|
2014-05-02 18:49:05 +00:00
|
|
|
procedure ExtractNumberFormat(AXFIndex: WORD;
|
2014-06-12 22:20:45 +00:00
|
|
|
out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); override;
|
2014-04-21 21:43:43 +00:00
|
|
|
procedure ReadBlank(AStream: TStream); override;
|
2014-04-24 23:05:00 +00:00
|
|
|
procedure ReadColWidth(AStream: TStream);
|
2014-09-09 11:42:20 +00:00
|
|
|
procedure ReadDefRowHeight(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure ReadFont(AStream: TStream);
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure ReadFontColor(AStream: TStream);
|
2014-06-13 13:33:02 +00:00
|
|
|
procedure ReadFormat(AStream: TStream); override;
|
2009-01-10 21:47:59 +00:00
|
|
|
procedure ReadFormula(AStream: TStream); override;
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure ReadInteger(AStream: TStream);
|
2009-01-10 21:47:59 +00:00
|
|
|
procedure ReadLabel(AStream: TStream); override;
|
|
|
|
procedure ReadNumber(AStream: TStream); override;
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
|
2014-05-03 20:12:44 +00:00
|
|
|
procedure ReadRowInfo(AStream: TStream); override;
|
2014-05-23 13:16:01 +00:00
|
|
|
function ReadRPNFunc(AStream: TStream): Word; override;
|
2014-10-03 22:02:09 +00:00
|
|
|
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); override;
|
2014-05-23 13:16:01 +00:00
|
|
|
function ReadRPNTokenArraySize(AStream: TStream): Word; override;
|
2014-05-16 08:17:49 +00:00
|
|
|
procedure ReadStringRecord(AStream: TStream); override;
|
2014-05-04 18:07:54 +00:00
|
|
|
procedure ReadWindow2(AStream: TStream); override;
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure ReadXF(AStream: TStream);
|
2014-04-21 11:30:22 +00:00
|
|
|
public
|
2014-08-11 15:53:06 +00:00
|
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
2014-04-21 11:30:22 +00:00
|
|
|
{ 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
|
2014-05-21 23:01:07 +00:00
|
|
|
function FindXFIndex(ACell: PCell): Word;
|
2014-07-15 21:00:49 +00:00
|
|
|
procedure GetCellAttributes(ACell: PCell; XFIndex: Word;
|
|
|
|
out Attrib1, Attrib2, Attrib3: Byte);
|
2008-02-24 13:18:34 +00:00
|
|
|
{ Record writing methods }
|
|
|
|
procedure WriteBOF(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure WriteCellFormatting(AStream: TStream; ACell: PCell; XFIndex: Word);
|
2014-04-24 23:05:00 +00:00
|
|
|
procedure WriteColWidth(AStream: TStream; ACol: PCol);
|
|
|
|
procedure WriteColWidths(AStream: TStream);
|
2014-08-08 19:00:27 +00:00
|
|
|
procedure WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
|
2008-02-24 13:18:34 +00:00
|
|
|
procedure WriteEOF(AStream: TStream);
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure WriteFont(AStream: TStream; AFontIndex: Integer);
|
|
|
|
procedure WriteFonts(AStream: TStream);
|
2014-05-02 18:49:05 +00:00
|
|
|
procedure WriteFormatCount(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure WriteIXFE(AStream: TStream; XFIndex: Word);
|
|
|
|
procedure WriteXF(AStream: TStream; AFontIndex, AFormatIndex: byte;
|
|
|
|
ABorders: TsCellBorders = []; AHorAlign: TsHorAlignment = haLeft;
|
|
|
|
AddBackground: Boolean = false);
|
|
|
|
procedure WriteXFFieldsForFormattingStyles(AStream: TStream);
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure WriteXFRecords(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
protected
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure CreateNumFormatList; override;
|
|
|
|
procedure ListAllNumFormats; override;
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); override;
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure WriteFormat(AStream: TStream; AFormatData: TsNumFormatData;
|
|
|
|
AListIndex: Integer); override;
|
2014-07-24 15:56:03 +00:00
|
|
|
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: string; ACell: PCell); override;
|
|
|
|
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AValue: double; ACell: PCell); override;
|
2014-05-07 18:31:27 +00:00
|
|
|
procedure WriteRow(AStream: TStream; ASheet: TsWorksheet;
|
|
|
|
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); override;
|
2014-07-01 22:47:10 +00:00
|
|
|
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
|
|
|
const AFormula: TsRPNFormula; ACell: PCell); override;
|
|
|
|
function WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word; override;
|
2014-08-17 22:25:46 +00:00
|
|
|
procedure WriteRPNSharedFormulaLink(AStream: TStream; ACell: PCell;
|
|
|
|
var RPNLength: Word); override;
|
2014-07-01 22:47:10 +00:00
|
|
|
procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); override;
|
2014-08-17 22:25:46 +00:00
|
|
|
procedure WriteSharedFormula(AStream: TStream; ACell: PCell); override;
|
2014-07-01 22:47:10 +00:00
|
|
|
procedure WriteStringRecord(AStream: TStream; AString: String); override;
|
2014-05-07 18:31:27 +00:00
|
|
|
procedure WriteWindow1(AStream: TStream); override;
|
|
|
|
procedure WriteWindow2(AStream: TStream; ASheet: TsWorksheet);
|
2014-04-21 11:30:22 +00:00
|
|
|
public
|
2014-08-11 15:53:06 +00:00
|
|
|
constructor Create(AWorkbook: TsWorkbook); override;
|
2014-04-21 11:30:22 +00:00
|
|
|
{ General writing methods }
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure WriteToStream(AStream: TStream); override;
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-24 21:27:57 +00:00
|
|
|
var
|
2014-05-02 18:49:05 +00:00
|
|
|
{ the palette of the default BIFF2 colors as "big-endian color" values }
|
2014-04-24 21:27:57 +00:00
|
|
|
PALETTE_BIFF2: array[$0..$07] of TsColorValue = (
|
2014-04-23 22:29:32 +00:00
|
|
|
$000000, // $00: black
|
|
|
|
$FFFFFF, // $01: white
|
|
|
|
$FF0000, // $02: red
|
|
|
|
$00FF00, // $03: green
|
|
|
|
$0000FF, // $04: blue
|
|
|
|
$FFFF00, // $05: yellow
|
|
|
|
$FF00FF, // $06: magenta
|
|
|
|
$00FFFF // $07: cyan
|
|
|
|
);
|
|
|
|
|
2014-08-11 22:39:47 +00:00
|
|
|
// However, it looks as if BIFF2 can handle more colors, at least 16 are
|
|
|
|
// compatible with the other formats.
|
2014-05-02 18:49:05 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
implementation
|
|
|
|
|
2014-05-04 18:07:54 +00:00
|
|
|
uses
|
2014-06-12 22:20:45 +00:00
|
|
|
Math, fpsNumFormatParser;
|
2014-05-04 18:07:54 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
const
|
|
|
|
{ Excel record IDs }
|
2014-09-09 11:42:20 +00:00
|
|
|
INT_EXCEL_ID_DIMENSIONS = $0000;
|
|
|
|
INT_EXCEL_ID_BLANK = $0001;
|
|
|
|
INT_EXCEL_ID_INTEGER = $0002;
|
|
|
|
INT_EXCEL_ID_NUMBER = $0003;
|
|
|
|
INT_EXCEL_ID_LABEL = $0004;
|
|
|
|
INT_EXCEL_ID_ROW = $0008;
|
|
|
|
INT_EXCEL_ID_BOF = $0009;
|
|
|
|
{%H-}INT_EXCEL_ID_INDEX = $000B;
|
|
|
|
INT_EXCEL_ID_FORMAT = $001E;
|
|
|
|
INT_EXCEL_ID_FORMATCOUNT = $001F;
|
|
|
|
INT_EXCEL_ID_COLWIDTH = $0024;
|
|
|
|
INT_EXCEL_ID_DEFROWHEIGHT = 00025;
|
|
|
|
INT_EXCEL_ID_WINDOW2 = $003E;
|
|
|
|
INT_EXCEL_ID_XF = $0043;
|
|
|
|
INT_EXCEL_ID_IXFE = $0044;
|
|
|
|
INT_EXCEL_ID_FONTCOLOR = $0045;
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ BOF record constants }
|
2014-09-09 11:42:20 +00:00
|
|
|
INT_EXCEL_SHEET = $0010;
|
|
|
|
{%H-}INT_EXCEL_CHART = $0020;
|
2014-08-12 14:52:57 +00:00
|
|
|
{%H-}INT_EXCEL_MACRO_SHEET = $0040;
|
2014-05-14 23:17:46 +00:00
|
|
|
|
2014-07-21 20:33:25 +00:00
|
|
|
type
|
2014-08-08 19:00:27 +00:00
|
|
|
TBIFF2DimensionsRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
FirstRow: Word;
|
|
|
|
LastRowPlus1: Word;
|
|
|
|
FirstCol: Word;
|
|
|
|
LastColPlus1: Word;
|
|
|
|
end;
|
|
|
|
|
2014-07-21 20:33:25 +00:00
|
|
|
TBIFF2LabelRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
Row: Word;
|
|
|
|
Col: Word;
|
|
|
|
Attrib1: Byte;
|
|
|
|
Attrib2: Byte;
|
|
|
|
Attrib3: Byte;
|
|
|
|
TextLen: Byte;
|
|
|
|
end;
|
|
|
|
|
|
|
|
TBIFF2NumberRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
Row: Word;
|
|
|
|
Col: Word;
|
|
|
|
Attrib1: Byte;
|
|
|
|
Attrib2: Byte;
|
|
|
|
Attrib3: Byte;
|
|
|
|
Value: Double;
|
|
|
|
end;
|
|
|
|
|
2014-07-24 15:56:03 +00:00
|
|
|
TBIFF2IntegerRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
Row: Word;
|
|
|
|
Col: Word;
|
|
|
|
Attrib1: Byte;
|
|
|
|
Attrib2: Byte;
|
|
|
|
Attrib3: Byte;
|
|
|
|
Value: Word;
|
|
|
|
end;
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
|
|
|
|
{ TsBIFF2NumFormatList }
|
|
|
|
|
2014-05-20 16:13:48 +00:00
|
|
|
constructor TsBIFF2NumFormatList.Create(AWorkbook: TsWorkbook);
|
2014-05-14 15:24:02 +00:00
|
|
|
begin
|
2014-05-20 16:13:48 +00:00
|
|
|
inherited Create(AWorkbook);
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
|
2014-06-13 13:33:02 +00:00
|
|
|
{ Prepares the list of built-in number formats. They are created in the default
|
|
|
|
dialect for FPC, they have to be converted to Excel syntax before writing.
|
|
|
|
Note that Excel2 expects them to be localized. This is something which has to
|
|
|
|
be taken account of in ConverBeforeWriting.}
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure TsBIFF2NumFormatList.AddBuiltinFormats;
|
2014-05-21 23:01:07 +00:00
|
|
|
var
|
2014-05-22 12:34:10 +00:00
|
|
|
fs: TFormatSettings;
|
2014-06-19 19:25:40 +00:00
|
|
|
cs: string;
|
2014-05-14 15:24:02 +00:00
|
|
|
begin
|
2014-06-13 13:33:02 +00:00
|
|
|
fs := FWorkbook.FormatSettings;
|
2014-06-19 19:25:40 +00:00
|
|
|
cs := fs.CurrencyString;
|
2014-06-13 13:33:02 +00:00
|
|
|
AddFormat( 0, '', nfGeneral);
|
|
|
|
AddFormat( 1, '0', nfFixed);
|
|
|
|
AddFormat( 2, '0.00', nfFixed);
|
|
|
|
AddFormat( 3, '#,##0', nfFixedTh);
|
|
|
|
AddFormat( 4, '#,##0.00', nfFixedTh);
|
|
|
|
AddFormat( 5, '"'+cs+'"#,##0_);("'+cs+'"#,##0)', nfCurrency);
|
|
|
|
AddFormat( 6, '"'+cs+'"#,##0_);[Red]("'+cs+'"#,##0)', nfCurrencyRed);
|
|
|
|
AddFormat( 7, '"'+cs+'"#,##0.00_);("'+cs+'"#,##0.00)', nfCurrency);
|
|
|
|
AddFormat( 8, '"'+cs+'"#,##0.00_);[Red]("'+cs+'"#,##0.00)', nfCurrency);
|
|
|
|
AddFormat( 9, '0%', nfPercentage);
|
|
|
|
AddFormat(10, '0.00%', nfPercentage);
|
|
|
|
AddFormat(11, '0.00E+00', nfExp);
|
|
|
|
AddFormat(12, fs.ShortDateFormat, nfShortDate);
|
|
|
|
AddFormat(13, fs.LongDateFormat, nfLongDate);
|
|
|
|
AddFormat(14, 'd/mmm', nfCustom);
|
|
|
|
AddFormat(15, 'mmm/yy', nfCustom);
|
|
|
|
AddFormat(16, AddAMPM(fs.ShortTimeFormat, fs), nfShortTimeAM);
|
|
|
|
AddFormat(17, AddAMPM(fs.LongTimeFormat, fs), nfLongTimeAM);
|
|
|
|
AddFormat(18, fs.ShortTimeFormat, nfShortTime);
|
|
|
|
AddFormat(19, fs.LongTimeFormat, nfLongTime);
|
|
|
|
AddFormat(20, fs.ShortDateFormat + ' ' + fs.ShortTimeFormat, nfShortDateTime);
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
FFirstFormatIndexInFile := 0; // BIFF2 stores built-in formats to file.
|
|
|
|
FNextFormatIndex := 21; // not needed - there are not user-defined formats
|
|
|
|
end;
|
|
|
|
|
2014-05-21 23:01:07 +00:00
|
|
|
|
|
|
|
procedure TsBIFF2NumFormatList.ConvertBeforeWriting(var AFormatString: String;
|
2014-06-13 13:33:02 +00:00
|
|
|
var ANumFormat: TsNumberFormat);
|
2014-05-14 15:24:02 +00:00
|
|
|
var
|
2014-06-13 13:33:02 +00:00
|
|
|
parser: TsNumFormatParser;
|
2014-05-14 15:24:02 +00:00
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(ANumFormat);
|
|
|
|
|
2014-06-13 13:33:02 +00:00
|
|
|
if AFormatString = '' then
|
|
|
|
AFormatString := 'General'
|
|
|
|
else begin
|
|
|
|
parser := TsNumFormatParser.Create(FWorkbook, AFormatString);
|
|
|
|
try
|
|
|
|
parser.Localize;
|
|
|
|
parser.LimitDecimals;
|
|
|
|
AFormatString := parser.FormatString[nfdExcel];
|
|
|
|
finally
|
|
|
|
parser.Free;
|
|
|
|
end;
|
2014-05-21 23:01:07 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
function TsBIFF2NumFormatList.FindFormatOf(AFormatCell: PCell): Integer;
|
|
|
|
var
|
2014-06-12 22:20:45 +00:00
|
|
|
parser: TsNumFormatParser;
|
|
|
|
decs: Integer;
|
|
|
|
dt: string;
|
2014-05-21 23:01:07 +00:00
|
|
|
begin
|
2014-06-23 09:15:56 +00:00
|
|
|
Result := 0;
|
|
|
|
|
2014-06-12 22:20:45 +00:00
|
|
|
parser := TsNumFormatParser.Create(Workbook, AFormatCell^.NumberFormatStr);
|
|
|
|
try
|
|
|
|
decs := parser.Decimals;
|
|
|
|
dt := parser.GetDateTimeCode(0);
|
|
|
|
finally
|
|
|
|
parser.Free;
|
|
|
|
end;
|
|
|
|
|
2014-05-21 23:01:07 +00:00
|
|
|
case AFormatCell^.NumberFormat of
|
2014-06-23 09:15:56 +00:00
|
|
|
nfGeneral : exit;
|
|
|
|
nfFixed : Result := IfThen(decs = 0, 1, 2);
|
|
|
|
nfFixedTh : Result := IfThen(decs = 0, 3, 4);
|
|
|
|
nfCurrency : Result := IfThen(decs = 0, 5, 7);
|
|
|
|
nfCurrencyRed : Result := IfThen(decs = 0, 6, 8);
|
|
|
|
nfPercentage : Result := IfThen(decs = 0, 9, 10);
|
|
|
|
nfExp : Result := 11;
|
|
|
|
nfShortDate : Result := 12;
|
|
|
|
nfLongDate : Result := 13;
|
|
|
|
nfShortTimeAM : Result := 16;
|
|
|
|
nfLongTimeAM : Result := 17;
|
|
|
|
nfShortTime : Result := 18;
|
|
|
|
nfLongTime : Result := 19;
|
|
|
|
nfShortDateTime: Result := 20;
|
|
|
|
nfCustom : if dt = 'dm' then Result := 14 else
|
|
|
|
if dt = 'my' then Result := 15;
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
end;
|
2014-05-21 23:01:07 +00:00
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ TsSpreadBIFF2Reader }
|
|
|
|
|
2014-08-11 15:53:06 +00:00
|
|
|
constructor TsSpreadBIFF2Reader.Create(AWorkbook: TsWorkbook);
|
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
2014-08-11 22:39:47 +00:00
|
|
|
FLimitations.MaxPaletteSize := BIFF2_MAX_PALETTE_SIZE;
|
2014-08-11 15:53:06 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-24 15:56:03 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ApplyCellFormatting(ACell: PCell; XFIndex: Word);
|
2014-05-01 21:47:41 +00:00
|
|
|
var
|
|
|
|
xfData: TXFListData;
|
|
|
|
begin
|
2014-07-24 15:56:03 +00:00
|
|
|
if Assigned(ACell) then begin
|
2014-05-01 21:47:41 +00:00
|
|
|
xfData := TXFListData(FXFList.items[XFIndex]);
|
|
|
|
|
|
|
|
// Font index, "bold" attribute
|
|
|
|
if xfData.FontIndex = 1 then
|
2014-07-24 15:56:03 +00:00
|
|
|
Include(ACell^.UsedFormattingFields, uffBold)
|
2014-05-01 21:47:41 +00:00
|
|
|
else
|
2014-07-24 15:56:03 +00:00
|
|
|
Include(ACell^.UsedFormattingFields, uffFont);
|
|
|
|
ACell^.FontIndex := xfData.FontIndex;
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Alignment
|
2014-07-24 15:56:03 +00:00
|
|
|
ACell^.HorAlignment := xfData.HorAlignment;
|
|
|
|
ACell^.VertAlignment := xfData.VertAlignment;
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Wordwrap not supported by BIFF2
|
2014-07-24 15:56:03 +00:00
|
|
|
Exclude(ACell^.UsedFormattingFields, uffWordwrap);
|
2014-05-01 21:47:41 +00:00
|
|
|
// Text rotation not supported by BIFF2
|
2014-07-24 15:56:03 +00:00
|
|
|
Exclude(ACell^.UsedFormattingFields, uffTextRotation);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Border
|
|
|
|
if xfData.Borders <> [] then begin
|
2014-07-24 15:56:03 +00:00
|
|
|
Include(ACell^.UsedFormattingFields, uffBorder);
|
|
|
|
ACell^.Border := xfData.Borders;
|
2014-05-01 21:47:41 +00:00
|
|
|
end else
|
2014-07-24 15:56:03 +00:00
|
|
|
Exclude(ACell^.UsedFormattingFields, uffBorder);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Background, only shaded, color is ignored
|
|
|
|
if xfData.BackgroundColor <> 0 then
|
2014-07-24 15:56:03 +00:00
|
|
|
Include(ACell^.UsedFormattingFields, uffBackgroundColor)
|
2014-05-01 21:47:41 +00:00
|
|
|
else
|
2014-07-24 15:56:03 +00:00
|
|
|
Exclude(ACell^.UsedFormattingFields, uffBackgroundColor);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
{ Creates the correct version of the number format list.
|
|
|
|
It is for BIFF2 and BIFF3 file formats. }
|
|
|
|
procedure TsSpreadBIFF2Reader.CreateNumFormatList;
|
|
|
|
begin
|
|
|
|
FreeAndNil(FNumFormatList);
|
2014-05-20 16:13:48 +00:00
|
|
|
FNumFormatList := TsBIFF2NumFormatList.Create(Workbook);
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Extracts the number format data from an XF record indexed by AXFIndex.
|
|
|
|
Note that BIFF2 supports only 21 formats. }
|
|
|
|
procedure TsSpreadBIFF2Reader.ExtractNumberFormat(AXFIndex: WORD;
|
2014-06-12 22:20:45 +00:00
|
|
|
out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String);
|
2014-05-02 18:49:05 +00:00
|
|
|
var
|
2014-05-14 15:24:02 +00:00
|
|
|
lNumFormatData: TsNumFormatData;
|
2014-05-02 18:49:05 +00:00
|
|
|
begin
|
2014-05-14 15:24:02 +00:00
|
|
|
lNumFormatData := FindNumFormatDataForCell(AXFIndex);
|
2014-05-14 23:17:46 +00:00
|
|
|
if lNumFormatData <> nil then begin
|
|
|
|
ANumberFormat := lNumFormatData.NumFormat;
|
|
|
|
ANumberFormatStr := lNumFormatData.FormatString;
|
|
|
|
end else begin
|
|
|
|
ANumberFormat := nfGeneral;
|
|
|
|
ANumberFormatStr := '';
|
|
|
|
end;
|
2014-05-02 18:49:05 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadBlank(AStream: TStream);
|
|
|
|
var
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: Word;
|
2014-07-24 15:56:03 +00:00
|
|
|
cell: PCell;
|
2014-05-01 21:47:41 +00:00
|
|
|
begin
|
|
|
|
ReadRowColXF(AStream, ARow, ACol, XF);
|
2014-07-24 15:56:03 +00:00
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := FWorksheet.GetCell(ARow, ACol);
|
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
if FIsVirtualMode then
|
|
|
|
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadColWidth(AStream: TStream);
|
|
|
|
var
|
|
|
|
c, c1, c2: Cardinal;
|
|
|
|
w: Word;
|
|
|
|
col: TCol;
|
|
|
|
begin
|
|
|
|
// read column start and end index of column range
|
|
|
|
c1 := AStream.ReadByte;
|
|
|
|
c2 := AStream.ReadByte;
|
|
|
|
// 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;
|
2014-09-09 11:42:20 +00:00
|
|
|
// assign width to columns, but only if different from default column width.
|
|
|
|
if not SameValue(col.Width, FWorksheet.DefaultColWidth) then
|
|
|
|
for c := c1 to c2 do
|
|
|
|
FWorksheet.WriteColInfo(c, col);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadDefRowHeight(AStream: TStream);
|
|
|
|
var
|
|
|
|
hw: word;
|
|
|
|
h : Single;
|
|
|
|
begin
|
|
|
|
hw := WordLEToN(AStream.ReadWord);
|
|
|
|
h := TwipsToPts(hw and $8000) / FWorkbook.GetDefaultFontSize;
|
|
|
|
if h > ROW_HEIGHT_CORRECTION then
|
|
|
|
FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION;
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFont(AStream: TStream);
|
|
|
|
var
|
|
|
|
lHeight: Word;
|
|
|
|
lOptions: Word;
|
|
|
|
Len: Byte;
|
|
|
|
lFontName: UTF8String;
|
|
|
|
begin
|
|
|
|
FFont := TsFont.Create;
|
|
|
|
|
|
|
|
{ Height of the font in twips = 1/20 of a point }
|
|
|
|
lHeight := WordLEToN(AStream.ReadWord);
|
|
|
|
FFont.Size := lHeight/20;
|
|
|
|
|
|
|
|
{ Option flags }
|
|
|
|
lOptions := WordLEToN(AStream.ReadWord);
|
|
|
|
FFont.Style := [];
|
|
|
|
if lOptions and $0001 <> 0 then Include(FFont.Style, fssBold);
|
|
|
|
if lOptions and $0002 <> 0 then Include(FFont.Style, fssItalic);
|
|
|
|
if lOptions and $0004 <> 0 then Include(FFont.Style, fssUnderline);
|
|
|
|
if lOptions and $0008 <> 0 then Include(FFont.Style, fssStrikeout);
|
|
|
|
|
|
|
|
{ Font name: Unicodestring, char count in 1 byte }
|
|
|
|
Len := AStream.ReadByte();
|
|
|
|
SetLength(lFontName, Len);
|
|
|
|
AStream.ReadBuffer(lFontName[1], Len);
|
|
|
|
FFont.FontName := lFontName;
|
|
|
|
|
|
|
|
{ Add font to workbook's font list }
|
|
|
|
FWorkbook.AddFont(FFont);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFontColor(AStream: TStream);
|
|
|
|
begin
|
|
|
|
FFont.Color := WordLEToN(AStream.ReadWord);
|
|
|
|
end;
|
|
|
|
|
2014-06-13 13:33:02 +00:00
|
|
|
// Read the FORMAT record for formatting numerical data
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFormat(AStream: TStream);
|
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(AStream);
|
2014-06-13 13:33:02 +00:00
|
|
|
// We ignore the formats in the file, everything is known
|
|
|
|
// (Using the formats in the file would require de-localizing them).
|
|
|
|
end;
|
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
|
|
|
|
var
|
|
|
|
BIFF2EOF: Boolean;
|
|
|
|
RecordType: Word;
|
|
|
|
CurStreamPos: Int64;
|
|
|
|
begin
|
|
|
|
// Clear existing fonts. They will be replaced by those from the file.
|
|
|
|
FWorkbook.RemoveAllFonts;
|
|
|
|
|
|
|
|
{ Store some data about the workbook that other routines need }
|
|
|
|
WorkBookEncoding := AData.Encoding;
|
|
|
|
|
|
|
|
BIFF2EOF := False;
|
|
|
|
|
|
|
|
{ In BIFF2 files there is only one worksheet, let's create it }
|
2014-09-12 09:38:12 +00:00
|
|
|
FWorksheet := AData.AddWorksheet('', true); // true --> accept empty sheet name
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Read all records in a loop }
|
|
|
|
while not BIFF2EOF do
|
|
|
|
begin
|
|
|
|
{ Read the record header }
|
|
|
|
RecordType := WordLEToN(AStream.ReadWord);
|
|
|
|
RecordSize := WordLEToN(AStream.ReadWord);
|
|
|
|
|
|
|
|
CurStreamPos := AStream.Position;
|
|
|
|
|
|
|
|
case RecordType of
|
2014-09-09 11:42:20 +00:00
|
|
|
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
|
|
|
|
INT_EXCEL_ID_FONT : ReadFont(AStream);
|
|
|
|
INT_EXCEL_ID_FONTCOLOR : ReadFontColor(AStream);
|
|
|
|
INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
|
|
|
|
INT_EXCEL_ID_INTEGER : ReadInteger(AStream);
|
|
|
|
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
|
|
|
|
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
|
|
|
|
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
|
|
|
|
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
|
|
|
|
INT_EXCEL_ID_COLWIDTH : ReadColWidth(AStream);
|
|
|
|
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
|
|
|
|
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
|
|
|
|
INT_EXCEL_ID_DEFROWHEIGHT: ReadDefRowHeight(AStream);
|
|
|
|
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
|
|
|
|
INT_EXCEL_ID_PANE : ReadPane(AStream);
|
|
|
|
INT_EXCEL_ID_XF : ReadXF(AStream);
|
|
|
|
INT_EXCEL_ID_BOF : ;
|
|
|
|
INT_EXCEL_ID_EOF : BIFF2EOF := True;
|
2014-05-01 21:47:41 +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;
|
2014-09-07 22:25:11 +00:00
|
|
|
|
|
|
|
FixCols(FWorksheet);
|
|
|
|
FixRows(FWorksheet);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadFormula(AStream: TStream);
|
2014-05-25 16:49:45 +00:00
|
|
|
var
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: Word;
|
|
|
|
ok: Boolean;
|
2014-06-19 19:25:40 +00:00
|
|
|
formulaResult: Double = 0.0;
|
2014-05-25 16:49:45 +00:00
|
|
|
// rpnFormula: TsRPNFormula;
|
|
|
|
Data: array [0..7] of byte;
|
|
|
|
dt: TDateTime;
|
|
|
|
nf: TsNumberFormat;
|
|
|
|
nfs: String;
|
|
|
|
err: TsErrorValue;
|
|
|
|
cell: PCell;
|
2014-05-01 21:47:41 +00:00
|
|
|
begin
|
2014-05-25 16:49:45 +00:00
|
|
|
{ BIFF Record row/column/style }
|
|
|
|
ReadRowColXF(AStream, ARow, ACol, XF);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
2014-05-25 16:49:45 +00:00
|
|
|
{ Result of the formula result in IEEE 754 floating-point value }
|
2014-10-06 10:43:10 +00:00
|
|
|
Data[0] := 0; // to silence the compiler...
|
2014-05-25 16:49:45 +00:00
|
|
|
AStream.ReadBuffer(Data, Sizeof(Data));
|
|
|
|
|
|
|
|
{ Recalculation byte - currently not used }
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
2014-07-24 15:56:03 +00:00
|
|
|
{ Create cell }
|
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := FWorksheet.GetCell(ARow, ACol);
|
|
|
|
|
2014-05-25 16:49:45 +00:00
|
|
|
// Now determine the type of the formula result
|
|
|
|
if (Data[6] = $FF) and (Data[7] = $FF) then
|
|
|
|
case Data[0] of
|
|
|
|
0: // String -> Value is found in next record (STRING)
|
2014-07-24 15:56:03 +00:00
|
|
|
FIncompleteCell := cell;
|
2014-05-25 16:49:45 +00:00
|
|
|
1: // Boolean value
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteBoolValue(cell, Data[2] = 1);
|
2014-05-25 16:49:45 +00:00
|
|
|
2: begin // Error value
|
|
|
|
case Data[2] of
|
|
|
|
ERR_INTERSECTION_EMPTY : err := errEmptyIntersection;
|
|
|
|
ERR_DIVIDE_BY_ZERO : err := errDivideByZero;
|
|
|
|
ERR_WRONG_TYPE_OF_OPERAND: err := errWrongType;
|
|
|
|
ERR_ILLEGAL_REFERENCE : err := errIllegalRef;
|
|
|
|
ERR_WRONG_NAME : err := errWrongName;
|
|
|
|
ERR_OVERFLOW : err := errOverflow;
|
|
|
|
ERR_ARG_ERROR : err := errArgError;
|
|
|
|
end;
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteErrorValue(cell, err);
|
2014-05-25 16:49:45 +00:00
|
|
|
end;
|
|
|
|
3: // Empty cell
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteBlank(cell);
|
2014-05-25 16:49:45 +00:00
|
|
|
end
|
|
|
|
else begin
|
2014-08-12 14:52:57 +00:00
|
|
|
{if SizeOf(Double) <> 8 then
|
|
|
|
raise Exception.Create('Double is not 8 bytes');}
|
2014-05-25 16:49:45 +00:00
|
|
|
|
|
|
|
// Result is a number or a date/time
|
|
|
|
Move(Data[0], formulaResult, SizeOf(Data));
|
|
|
|
|
|
|
|
{Find out what cell type, set content type and value}
|
2014-06-12 22:20:45 +00:00
|
|
|
ExtractNumberFormat(XF, nf, nfs);
|
|
|
|
if IsDateTime(formulaResult, nf, nfs, dt) then
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteDateTime(cell, dt, nf, nfs)
|
2014-05-25 16:49:45 +00:00
|
|
|
else
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteNumber(cell, formulaResult, nf, nfs);
|
2014-05-25 16:49:45 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ Formula token array }
|
2014-08-30 18:03:22 +00:00
|
|
|
if (boReadFormulas in FWorkbook.Options) then begin
|
2014-08-15 19:29:58 +00:00
|
|
|
ok := ReadRPNTokenArray(AStream, cell);
|
2014-05-25 16:49:45 +00:00
|
|
|
if not ok then FWorksheet.WriteErrorValue(cell, errFormulaNotSupported);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Apply formatting to cell }
|
2014-07-24 15:56:03 +00:00
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
|
|
|
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadLabel(AStream: TStream);
|
|
|
|
var
|
2014-07-21 20:33:25 +00:00
|
|
|
rec: TBIFF2LabelRecord;
|
2014-05-01 21:47:41 +00:00
|
|
|
L: Byte;
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: Word;
|
2014-07-21 20:33:25 +00:00
|
|
|
AValue: ansistring;
|
2014-05-01 21:47:41 +00:00
|
|
|
AStrValue: UTF8String;
|
2014-07-24 15:56:03 +00:00
|
|
|
cell: PCell;
|
2014-05-01 21:47:41 +00:00
|
|
|
begin
|
2014-07-21 20:33:25 +00:00
|
|
|
{ Read entire record, starting at Row, except for string data }
|
2014-10-06 10:43:10 +00:00
|
|
|
rec.Row := 0; // to silence the compiler...
|
2014-07-21 20:33:25 +00:00
|
|
|
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2LabelRecord) - 2*SizeOf(Word));
|
|
|
|
ARow := WordLEToN(rec.Row);
|
|
|
|
ACol := WordLEToN(rec.Col);
|
|
|
|
XF := rec.Attrib1 and $3F;
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ String with 8-bit size }
|
2014-08-07 22:24:22 +00:00
|
|
|
L := rec.TextLen;
|
2014-07-21 20:33:25 +00:00
|
|
|
SetLength(AValue, L);
|
|
|
|
AStream.ReadBuffer(AValue[1], L);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Save the data }
|
|
|
|
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;
|
2014-07-24 15:56:03 +00:00
|
|
|
|
|
|
|
{ Create cell }
|
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := FWorksheet.GetCell(ARow, ACol);
|
|
|
|
FWorksheet.WriteUTF8Text(cell, AStrValue);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Apply formatting to cell }
|
2014-07-24 15:56:03 +00:00
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
|
|
|
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadNumber(AStream: TStream);
|
|
|
|
var
|
2014-07-21 20:33:25 +00:00
|
|
|
rec: TBIFF2NumberRecord;
|
2014-05-01 21:47:41 +00:00
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: Word;
|
2014-06-19 19:25:40 +00:00
|
|
|
value: Double = 0.0;
|
2014-05-02 18:49:05 +00:00
|
|
|
dt: TDateTime;
|
|
|
|
nf: TsNumberFormat;
|
|
|
|
nfs: String;
|
2014-07-24 15:56:03 +00:00
|
|
|
cell: PCell;
|
2014-05-01 21:47:41 +00:00
|
|
|
begin
|
2014-07-21 20:33:25 +00:00
|
|
|
{ Read entire record, starting at Row }
|
2014-10-06 10:43:10 +00:00
|
|
|
rec.Row := 0; // to silence the compiler...
|
2014-07-21 20:33:25 +00:00
|
|
|
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2NumberRecord) - 2*SizeOf(Word));
|
|
|
|
ARow := WordLEToN(rec.Row);
|
|
|
|
ACol := WordLEToN(rec.Col);
|
|
|
|
XF := rec.Attrib1 and $3F;
|
|
|
|
value := rec.Value;
|
2014-05-01 21:47:41 +00:00
|
|
|
|
2014-07-24 15:56:03 +00:00
|
|
|
{Create cell}
|
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := FWorksheet.GetCell(ARow, ACol);
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{Find out what cell type, set content type and value}
|
2014-06-12 22:20:45 +00:00
|
|
|
ExtractNumberFormat(XF, nf, nfs);
|
|
|
|
if IsDateTime(value, nf, nfs, dt) then
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteDateTime(cell, dt, nf, nfs)
|
2014-05-02 18:49:05 +00:00
|
|
|
else
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteNumber(cell, value, nf, nfs);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Apply formatting to cell }
|
2014-07-24 15:56:03 +00:00
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
|
|
|
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadInteger(AStream: TStream);
|
|
|
|
var
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: Word;
|
2014-06-19 19:25:40 +00:00
|
|
|
AWord : Word = 0;
|
2014-07-24 15:56:03 +00:00
|
|
|
cell: PCell;
|
|
|
|
rec: TBIFF2IntegerRecord;
|
2014-05-01 21:47:41 +00:00
|
|
|
begin
|
2014-10-06 10:43:10 +00:00
|
|
|
{ Read record into buffer }
|
|
|
|
rec.Row := 0; // to silence the comiler...
|
2014-07-24 15:56:03 +00:00
|
|
|
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2NumberRecord) - 2*SizeOf(Word));
|
|
|
|
ARow := WordLEToN(rec.Row);
|
|
|
|
ACol := WordLEToN(rec.Col);
|
|
|
|
XF := rec.Attrib1 and $3F;
|
|
|
|
AWord := WordLEToN(rec.Value);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
2014-07-24 15:56:03 +00:00
|
|
|
{ Create cell }
|
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := FWorksheet.GetCell(ARow, ACol);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Save the data }
|
2014-07-24 15:56:03 +00:00
|
|
|
FWorksheet.WriteNumber(cell, AWord);
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
{ Apply formatting to cell }
|
2014-07-24 15:56:03 +00:00
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
|
|
|
Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// Read the row, column and xf index
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadRowColXF(AStream: TStream;
|
|
|
|
out ARow, ACol: Cardinal; out AXF: WORD);
|
|
|
|
begin
|
|
|
|
{ BIFF Record data for row and column}
|
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
ACol := WordLEToN(AStream.ReadWord);
|
|
|
|
|
|
|
|
{ Index to XF record }
|
|
|
|
AXF := AStream.ReadByte;
|
|
|
|
|
|
|
|
{ Index to format and font record, Cell style - ignored because contained in XF
|
|
|
|
Must read to keep the record in sync. }
|
|
|
|
AStream.ReadWord;
|
|
|
|
end;
|
|
|
|
|
|
|
|
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
|
2014-10-06 10:43:10 +00:00
|
|
|
rowRec.RowIndex := 0; // to silence the compiler...
|
2014-05-01 21:47:41 +00:00
|
|
|
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)
|
2014-05-31 21:04:53 +00:00
|
|
|
// We need it in "lines" units.
|
|
|
|
lRow^.Height := TwipsToPts(h and $7FFF) / Workbook.GetFont(0).Size;
|
|
|
|
if lRow^.Height > ROW_HEIGHT_CORRECTION then
|
|
|
|
lRow^.Height := lRow^.Height - ROW_HEIGHT_CORRECTION
|
|
|
|
else
|
|
|
|
lRow^.Height := 0;
|
2014-05-07 18:31:27 +00:00
|
|
|
end;
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-23 13:16:01 +00:00
|
|
|
{ Reads the identifier for an RPN function with fixed argument count.
|
|
|
|
Valid for BIFF2-BIFF3. }
|
|
|
|
function TsSpreadBIFF2Reader.ReadRPNFunc(AStream: TStream): Word;
|
|
|
|
var
|
|
|
|
b: Byte;
|
|
|
|
begin
|
|
|
|
b := AStream.ReadByte;
|
|
|
|
Result := b;
|
|
|
|
end;
|
|
|
|
|
2014-08-15 19:29:58 +00:00
|
|
|
{ Reads the cell coordiantes of the top/left cell of a range using a shared formula.
|
|
|
|
This cell contains the rpn token sequence of the formula.
|
2014-08-18 13:48:46 +00:00
|
|
|
Is overridden because BIFF2 has 1 byte for column.
|
|
|
|
Code is not called for shared formulas (which are not supported by BIFF2), but
|
|
|
|
maybe for array formulas. }
|
2014-08-15 19:29:58 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadRPNSharedFormulaBase(AStream: TStream;
|
|
|
|
out ARow, ACol: Cardinal);
|
|
|
|
begin
|
|
|
|
// 2 bytes for row of first cell in shared formula
|
|
|
|
ARow := WordLEToN(AStream.ReadWord);
|
|
|
|
// 1 byte for column of first cell in shared formula
|
|
|
|
ACol := AStream.ReadByte;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2014-05-23 13:16:01 +00:00
|
|
|
{ Helper funtion for reading of the size of the token array of an RPN formula.
|
|
|
|
Is overridden because BIFF2 uses 1 byte only. }
|
|
|
|
function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word;
|
|
|
|
begin
|
|
|
|
Result := AStream.ReadByte;
|
|
|
|
end;
|
|
|
|
|
2014-05-14 16:30:33 +00:00
|
|
|
{ Reads a STRING record which contains the result of string formula. }
|
2014-05-16 08:17:49 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadStringRecord(AStream: TStream);
|
2014-05-14 16:30:33 +00:00
|
|
|
var
|
|
|
|
len: Byte;
|
|
|
|
s: ansistring;
|
|
|
|
begin
|
2014-07-02 11:51:59 +00:00
|
|
|
// The string is a byte-string with 8 bit length
|
2014-05-14 16:30:33 +00:00
|
|
|
len := AStream.ReadByte;
|
|
|
|
if len > 0 then begin
|
|
|
|
SetLength(s, Len);
|
|
|
|
AStream.ReadBuffer(s[1], len);
|
2014-05-16 08:17:49 +00:00
|
|
|
if (FIncompleteCell <> nil) and (s <> '') then begin
|
2014-07-02 11:51:59 +00:00
|
|
|
// The "IncompleteCell" has been identified in the sheet when reading
|
|
|
|
// the FORMULA record which precedes the String record.
|
|
|
|
FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s);
|
2014-05-16 08:17:49 +00:00
|
|
|
FIncompleteCell^.ContentType := cctUTF8String;
|
2014-07-24 15:56:03 +00:00
|
|
|
if FIsVirtualMode then
|
|
|
|
Workbook.OnReadCellData(Workbook, FIncompleteCell^.Row, FIncompleteCell^.Col, FIncompleteCell);
|
2014-05-16 08:17:49 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
FIncompleteCell := nil;
|
2014-05-14 16:30:33 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-04 18:07:54 +00:00
|
|
|
{ Reads the WINDOW2 record containing information like "show grid lines",
|
|
|
|
"show sheet headers", "panes are frozen", etc. }
|
|
|
|
procedure TsSpreadBIFF2Reader.ReadWindow2(AStream: TStream);
|
|
|
|
begin
|
|
|
|
// Show formulas, not results
|
2014-06-19 19:25:40 +00:00
|
|
|
AStream.ReadByte;
|
2014-05-04 18:07:54 +00:00
|
|
|
|
|
|
|
// Show grid lines
|
|
|
|
if AStream.ReadByte <> 0 then
|
|
|
|
FWorksheet.Options := FWorksheet.Options + [soShowGridLines]
|
|
|
|
else
|
|
|
|
FWorksheet.Options := FWorksheet.Options - [soShowGridLines];
|
|
|
|
|
|
|
|
// Show sheet headers
|
|
|
|
if AStream.ReadByte <> 0 then
|
|
|
|
FWorksheet.Options := FWorksheet.Options + [soShowHeaders]
|
|
|
|
else
|
|
|
|
FWorksheet.Options := FWorksheet.Options - [soShowHeaders];
|
|
|
|
|
|
|
|
// Panes are frozen
|
|
|
|
if AStream.ReadByte <> 0 then
|
|
|
|
FWorksheet.Options := FWorksheet.Options + [soHasFrozenPanes]
|
|
|
|
else
|
|
|
|
FWorksheet.Options := FWorksheet.Options - [soHasFrozenPanes];
|
|
|
|
|
|
|
|
// Show zero values
|
2014-06-19 19:25:40 +00:00
|
|
|
AStream.ReadByte;
|
2014-05-04 18:07:54 +00:00
|
|
|
|
|
|
|
// Index to first visible row
|
2014-06-19 19:25:40 +00:00
|
|
|
WordLEToN(AStream.ReadWord);
|
2014-05-04 18:07:54 +00:00
|
|
|
|
|
|
|
// Indoex to first visible column
|
2014-06-19 19:25:40 +00:00
|
|
|
WordLEToN(AStream.ReadWord);
|
2014-05-04 18:07:54 +00:00
|
|
|
|
|
|
|
// Use automatic grid line color (0= manual)
|
2014-06-19 19:25:40 +00:00
|
|
|
AStream.ReadByte;
|
2014-05-04 18:07:54 +00:00
|
|
|
|
|
|
|
// Manual grid line line color (rgb)
|
2014-10-03 22:02:09 +00:00
|
|
|
DWordToLE(AStream.ReadDWord);
|
2014-05-04 18:07:54 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-01 21:47:41 +00:00
|
|
|
procedure TsSpreadBIFF2Reader.ReadXF(AStream: TStream);
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Offset Size Contents
|
|
|
|
0 1 Index to FONT record (➜5.45)
|
|
|
|
1 1 Not used
|
|
|
|
2 1 Number format and cell flags:
|
|
|
|
Bit Mask Contents
|
|
|
|
5-0 3FH Index to FORMAT record (➜5.49)
|
|
|
|
6 40H 1 = Cell is locked
|
|
|
|
7 80H 1 = Formula is hidden
|
|
|
|
3 1 Horizontal alignment, border style, and background:
|
|
|
|
Bit Mask Contents
|
|
|
|
2-0 07H XF_HOR_ALIGN – Horizontal alignment
|
|
|
|
0 General, 1 Left, 2 Center, 3 Right, 4 Filled
|
|
|
|
3 08H 1 = Cell has left black border
|
|
|
|
4 10H 1 = Cell has right black border
|
|
|
|
5 20H 1 = Cell has top black border
|
|
|
|
6 40H 1 = Cell has bottom black border
|
|
|
|
7 80H 1 = Cell has shaded background }
|
2014-04-23 22:29:32 +00:00
|
|
|
type
|
2014-05-02 18:49:05 +00:00
|
|
|
TXFRecord = packed record
|
|
|
|
FontIndex: byte;
|
|
|
|
NotUsed: byte;
|
|
|
|
NumFormat_Flags: byte;
|
|
|
|
HorAlign_Border_BackGround: Byte;
|
2014-04-23 22:29:32 +00:00
|
|
|
end;
|
2014-05-01 21:47:41 +00:00
|
|
|
var
|
|
|
|
lData: TXFListData;
|
|
|
|
xf: TXFRecord;
|
|
|
|
b: Byte;
|
|
|
|
begin
|
2014-10-06 10:43:10 +00:00
|
|
|
// Read entire xf record into buffer
|
|
|
|
xf.FontIndex := 0; // to silence the compiler...
|
2014-05-01 21:47:41 +00:00
|
|
|
AStream.ReadBuffer(xf, SizeOf(xf));
|
|
|
|
|
|
|
|
lData := TXFListData.Create;
|
|
|
|
|
|
|
|
// Font index
|
|
|
|
lData.FontIndex := xf.FontIndex;
|
|
|
|
|
|
|
|
// Format index
|
2014-05-02 18:49:05 +00:00
|
|
|
lData.FormatIndex := xf.NumFormat_Flags and $3F;
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Horizontal alignment
|
|
|
|
b := xf.HorAlign_Border_Background and MASK_XF_HOR_ALIGN;
|
|
|
|
if (b <= ord(High(TsHorAlignment))) then
|
|
|
|
lData.HorAlignment := TsHorAlignment(b)
|
|
|
|
else
|
|
|
|
lData.HorAlignment := haDefault;
|
|
|
|
|
|
|
|
// Vertical alignment - not used in BIFF2
|
|
|
|
lData.VertAlignment := vaBottom;
|
|
|
|
|
|
|
|
// Word wrap - not used in BIFF2
|
|
|
|
lData.WordWrap := false;
|
|
|
|
|
|
|
|
// Text rotation - not used in BIFF2
|
|
|
|
lData.TextRotation := trHorizontal;
|
|
|
|
|
|
|
|
// Borders
|
|
|
|
lData.Borders := [];
|
|
|
|
if xf.HorAlign_Border_Background and $08 <> 0 then
|
|
|
|
Include(lData.Borders, cbWest);
|
|
|
|
if xf.HorAlign_Border_Background and $10 <> 0 then
|
|
|
|
Include(lData.Borders, cbEast);
|
|
|
|
if xf.HorAlign_Border_Background and $20 <> 0 then
|
|
|
|
Include(lData.Borders, cbNorth);
|
|
|
|
if xf.HorAlign_Border_Background and $40 <> 0 then
|
|
|
|
Include(lData.Borders, cbSouth);
|
|
|
|
|
|
|
|
// Background color not supported, only shaded background
|
|
|
|
if xf.HorAlign_Border_Background and $80 <> 0 then
|
2014-06-23 13:49:12 +00:00
|
|
|
lData.BackgroundColor := 1 // encodes "shaded background = true"
|
2014-05-01 21:47:41 +00:00
|
|
|
else
|
2014-06-23 13:49:12 +00:00
|
|
|
ldata.BackgroundColor := 0; // encodes "shaded background = false"
|
2014-05-01 21:47:41 +00:00
|
|
|
|
|
|
|
// Add the decoded data to the list
|
|
|
|
FXFList.Add(lData);
|
2014-05-02 18:49:05 +00:00
|
|
|
|
|
|
|
ldata := TXFListData(FXFList.Items[FXFList.Count-1]);
|
2014-05-01 21:47:41 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ TsSpreadBIFF2Writer }
|
|
|
|
|
2014-08-11 15:53:06 +00:00
|
|
|
constructor TsSpreadBIFF2Writer.Create(AWorkbook: TsWorkbook);
|
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
2014-08-11 22:39:47 +00:00
|
|
|
FLimitations.MaxPaletteSize := BIFF2_MAX_PALETTE_SIZE;
|
2014-08-11 15:53:06 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
{ Creates the correct version of the number format list.
|
|
|
|
It is for BIFF2 and BIFF3 file formats. }
|
|
|
|
procedure TsSpreadBIFF2Writer.CreateNumFormatList;
|
|
|
|
begin
|
|
|
|
FreeAndNil(FNumFormatList);
|
2014-05-20 16:13:48 +00:00
|
|
|
FNumFormatList := TsBIFF2NumFormatList.Create(Workbook);
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
function TsSpreadBIFF2Writer.FindXFIndex(ACell: PCell): Word;
|
|
|
|
var
|
2014-05-21 23:01:07 +00:00
|
|
|
lIndex: Integer;
|
|
|
|
lCell: TCell;
|
2014-04-22 23:10:32 +00:00
|
|
|
begin
|
2014-05-21 23:01:07 +00:00
|
|
|
// First try the fast methods for default formats
|
2014-04-22 23:10:32 +00:00
|
|
|
if ACell^.UsedFormattingFields = [] then
|
|
|
|
Result := 15
|
|
|
|
else begin
|
|
|
|
// If not, then we need to search in the list of dynamic formats
|
2014-05-21 23:01:07 +00:00
|
|
|
lCell := ACell^;
|
|
|
|
lIndex := FindFormattingInList(@lCell);
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
// Carefully check the index
|
2014-05-21 23:01:07 +00:00
|
|
|
if (lIndex < 0) or (lIndex > Length(FFormattingStyles)) then
|
2014-04-22 23:10:32 +00:00
|
|
|
raise Exception.Create('[TsSpreadBIFF2Writer.WriteXFIndex] Invalid Index, this should not happen!');
|
2014-05-21 23:01:07 +00:00
|
|
|
Result := FFormattingStyles[lIndex].Row;
|
2014-04-22 23:10:32 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-07-15 21:00:49 +00:00
|
|
|
{ Determines the cell attributes needed for writing a cell content record, such
|
|
|
|
as WriteLabel, WriteNumber, etc.
|
|
|
|
The cell attributes contain, in bit masks, xf record index, font index, borders, etc.}
|
|
|
|
procedure TsSpreadBIFF2Writer.GetCellAttributes(ACell: PCell; XFIndex: Word;
|
|
|
|
out Attrib1, Attrib2, Attrib3: Byte);
|
|
|
|
begin
|
|
|
|
if ACell^.UsedFormattingFields = [] then begin
|
|
|
|
Attrib1 := 15;
|
|
|
|
Attrib2 := 0;
|
|
|
|
Attrib3 := 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// 1st byte:
|
|
|
|
// Mask $3F: Index to XF record
|
|
|
|
// Mask $40: 1 = Cell is locked
|
|
|
|
// Mask $80: 1 = Formula is hidden
|
|
|
|
Attrib1 := Min(XFIndex, $3F) and $3F;
|
|
|
|
|
|
|
|
// 2nd byte:
|
|
|
|
// Mask $3F: Index to FORMAT record
|
|
|
|
// Mask $C0: Index to FONT record
|
|
|
|
Attrib2 := ACell^.FontIndex shr 6;
|
|
|
|
|
|
|
|
// 3rd byte
|
|
|
|
// Mask $07: horizontal alignment
|
|
|
|
// Mask $08: Cell has left border
|
|
|
|
// Mask $10: Cell has right border
|
|
|
|
// Mask $20: Cell has top border
|
|
|
|
// Mask $40: Cell has bottom border
|
|
|
|
// Mask $80: Cell has shaded background
|
|
|
|
Attrib3 := 0;
|
|
|
|
if uffHorAlign in ACell^.UsedFormattingFields then
|
|
|
|
Attrib3 := ord (ACell^.HorAlignment);
|
|
|
|
if uffBorder in ACell^.UsedFormattingFields then begin
|
|
|
|
if cbNorth in ACell^.Border then Attrib3 := Attrib3 or $20;
|
|
|
|
if cbWest in ACell^.Border then Attrib3 := Attrib3 or $08;
|
|
|
|
if cbEast in ACell^.Border then Attrib3 := Attrib3 or $10;
|
|
|
|
if cbSouth in ACell^.Border then Attrib3 := Attrib3 or $40;
|
|
|
|
end;
|
|
|
|
if uffBackgroundColor in ACell^.UsedFormattingFields then
|
|
|
|
Attrib3 := Attrib3 or $80;
|
|
|
|
end;
|
|
|
|
|
2014-05-21 23:01:07 +00:00
|
|
|
{ Builds up the list of number formats to be written to the biff2 file.
|
|
|
|
Unlike biff5+ no formats are added here because biff2 supports only 21
|
|
|
|
standard formats; these formats have been added by the NumFormatList's
|
2014-06-13 13:33:02 +00:00
|
|
|
AddBuiltInFormats.
|
|
|
|
|
|
|
|
NOT CLEAR IF THIS IS TRUE ????
|
|
|
|
}
|
|
|
|
// ToDo: check if the BIFF2 format is really restricted to 21 formats.
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.ListAllNumFormats;
|
|
|
|
begin
|
2014-05-21 23:01:07 +00:00
|
|
|
// Nothing to do here.
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-21 23:01:07 +00:00
|
|
|
{ Attaches cell formatting data for the given cell to the current record.
|
|
|
|
Is called from all writing methods of cell contents. }
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteCellFormatting(AStream: TStream; ACell: PCell;
|
|
|
|
XFIndex: Word);
|
2011-05-25 15:50:18 +00:00
|
|
|
var
|
2014-04-21 21:43:43 +00:00
|
|
|
b: Byte;
|
2014-07-02 19:25:37 +00:00
|
|
|
w: Word;
|
2011-05-25 15:50:18 +00:00
|
|
|
begin
|
|
|
|
if ACell^.UsedFormattingFields = [] then
|
|
|
|
begin
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
AStream.WriteByte($0);
|
|
|
|
Exit;
|
|
|
|
end;
|
|
|
|
|
2014-04-21 21:43:43 +00:00
|
|
|
// 1st byte:
|
|
|
|
// Mask $3F: Index to XF record
|
|
|
|
// Mask $40: 1 = Cell is locked
|
|
|
|
// Mask $80: 1 = Formula is hidden
|
2014-05-22 09:38:28 +00:00
|
|
|
AStream.WriteByte(Min(XFIndex, $3F) and $3F);
|
2011-05-25 15:50:18 +00:00
|
|
|
|
2014-04-21 21:43:43 +00:00
|
|
|
// 2nd byte:
|
|
|
|
// Mask $3F: Index to FORMAT record
|
|
|
|
// Mask $C0: Index to FONT record
|
2014-07-15 21:00:49 +00:00
|
|
|
w := ACell.FontIndex shr 6; // was shl --> MUST BE shr!
|
2014-07-02 19:25:37 +00:00
|
|
|
b := Lo(w);
|
|
|
|
//b := ACell.FontIndex shl 6;
|
2014-04-22 23:10:32 +00:00
|
|
|
AStream.WriteByte(b);
|
2011-05-25 15:50:18 +00:00
|
|
|
|
2014-04-21 21:43:43 +00:00
|
|
|
// 3rd byte
|
|
|
|
// Mask $07: horizontal alignment
|
|
|
|
// Mask $08: Cell has left border
|
|
|
|
// Mask $10: Cell has right border
|
|
|
|
// Mask $20: Cell has top border
|
|
|
|
// Mask $40: Cell has bottom border
|
|
|
|
// Mask $80: Cell has shaded background
|
|
|
|
b := 0;
|
|
|
|
if uffHorAlign in ACell^.UsedFormattingFields then
|
|
|
|
b := ord (ACell^.HorAlignment);
|
|
|
|
if uffBorder in ACell^.UsedFormattingFields then begin
|
|
|
|
if cbNorth in ACell^.Border then b := b or $20;
|
|
|
|
if cbWest in ACell^.Border then b := b or $08;
|
|
|
|
if cbEast in ACell^.Border then b := b or $10;
|
|
|
|
if cbSouth in ACell^.Border then b := b or $40;
|
2011-05-25 15:50:18 +00:00
|
|
|
end;
|
|
|
|
if uffBackgroundColor in ACell^.UsedFormattingFields then
|
2014-04-21 21:43:43 +00:00
|
|
|
b := b or $80;
|
|
|
|
AStream.WriteByte(b);
|
2011-05-25 15:50:18 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-24 23:05:00 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 COLWIDTH record
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteColWidth(AStream: TStream; ACol: PCol);
|
2014-07-15 21:00:49 +00:00
|
|
|
type
|
|
|
|
TColRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
StartCol: Byte;
|
|
|
|
EndCol: Byte;
|
|
|
|
ColWidth: Word;
|
|
|
|
end;
|
2014-04-24 23:05:00 +00:00
|
|
|
var
|
2014-07-15 21:00:49 +00:00
|
|
|
rec: TColRecord;
|
2014-04-24 23:05:00 +00:00
|
|
|
w: Integer;
|
|
|
|
begin
|
|
|
|
if Assigned(ACol) then begin
|
2014-07-15 21:00:49 +00:00
|
|
|
{ BIFF record header }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_COLWIDTH);
|
|
|
|
rec.RecordSize := WordToLE(4);
|
|
|
|
|
|
|
|
{ Start and end column }
|
|
|
|
rec.StartCol := ACol^.Col;
|
|
|
|
rec.EndCol := ACol^.Col;
|
|
|
|
|
|
|
|
{ Column width }
|
|
|
|
{ calculate width to be in units of 1/256 of pixel width of character "0" }
|
|
|
|
w := round(ACol^.Width * 256);
|
|
|
|
rec.ColWidth := WordToLE(w);
|
|
|
|
|
|
|
|
{ Write out }
|
|
|
|
AStream.WriteBuffer(rec, SizeOf(rec));
|
|
|
|
|
|
|
|
(*
|
2014-04-24 23:05:00 +00:00
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_COLWIDTH)); // BIFF record header
|
|
|
|
AStream.WriteWord(WordToLE(4)); // Record size
|
|
|
|
AStream.WriteByte(ACol^.Col); // start column
|
|
|
|
AStream.WriteByte(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
|
2014-07-15 21:00:49 +00:00
|
|
|
*)
|
2014-04-24 23:05:00 +00:00
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{
|
|
|
|
Write COLWIDTH records for all columns
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteColWidths(AStream: TStream);
|
|
|
|
var
|
|
|
|
j: Integer;
|
|
|
|
sheet: TsWorksheet;
|
|
|
|
col: PCol;
|
|
|
|
begin
|
|
|
|
sheet := Workbook.GetFirstWorksheet;
|
|
|
|
for j := 0 to sheet.Cols.Count-1 do begin
|
|
|
|
col := PCol(sheet.Cols[j]);
|
|
|
|
WriteColWidth(AStream, col);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-08-08 19:00:27 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 DIMENSIONS record
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet);
|
|
|
|
var
|
|
|
|
firstRow, lastRow, firstCol, lastCol: Cardinal;
|
|
|
|
rec: TBIFF2DimensionsRecord;
|
|
|
|
begin
|
|
|
|
{ Determine sheet size }
|
|
|
|
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
|
|
|
|
|
|
|
|
{ Populate BIFF record }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
|
|
|
|
rec.RecordSize := WordToLE(8);
|
|
|
|
rec.FirstRow := WordToLE(firstRow);
|
2014-08-12 14:52:57 +00:00
|
|
|
if lastRow < $FFFF then // avoid WORD overflow when adding 1
|
|
|
|
rec.LastRowPlus1 := WordToLE(lastRow+1)
|
|
|
|
else
|
|
|
|
rec.LastRowPlus1 := $FFFF;
|
2014-08-08 19:00:27 +00:00
|
|
|
rec.FirstCol := WordToLE(firstCol);
|
|
|
|
rec.LastColPlus1 := WordToLE(lastCol+1);
|
|
|
|
|
|
|
|
{ Write BIFF record to stream }
|
|
|
|
AStream.WriteBuffer(rec, SizeOf(rec));
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 IXFE record
|
|
|
|
This record contains the "real" XF index if it is > 62.
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteIXFE(AStream: TStream; XFIndex: Word);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_IXFE));
|
|
|
|
AStream.WriteWord(WordToLE(2));
|
|
|
|
AStream.WriteWord(WordToLE(XFIndex));
|
|
|
|
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.
|
|
|
|
}
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream);
|
2014-05-04 18:07:54 +00:00
|
|
|
var
|
2014-06-24 22:47:15 +00:00
|
|
|
pane: Byte;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-08-30 18:03:22 +00:00
|
|
|
FWorksheet := Workbook.GetFirstWorksheet;
|
2014-05-04 18:07:54 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
WriteBOF(AStream);
|
2014-05-04 18:07:54 +00:00
|
|
|
WriteFonts(AStream);
|
2014-05-14 15:24:02 +00:00
|
|
|
WriteFormatCount(AStream);
|
2014-05-04 18:07:54 +00:00
|
|
|
WriteFormats(AStream);
|
|
|
|
WriteXFRecords(AStream);
|
|
|
|
WriteColWidths(AStream);
|
2014-08-30 18:03:22 +00:00
|
|
|
WriteDimensions(AStream, FWorksheet);
|
|
|
|
WriteRows(AStream, FWorksheet);
|
2014-07-11 13:20:14 +00:00
|
|
|
|
2014-07-22 15:59:29 +00:00
|
|
|
if (boVirtualMode in Workbook.Options) then
|
2014-07-11 13:20:14 +00:00
|
|
|
WriteVirtualCells(AStream)
|
|
|
|
else begin
|
2014-08-30 18:03:22 +00:00
|
|
|
WriteRows(AStream, FWorksheet);
|
|
|
|
WriteCellsToStream(AStream, FWorksheet.Cells);
|
2014-07-11 13:20:14 +00:00
|
|
|
end;
|
2014-05-04 20:01:21 +00:00
|
|
|
|
|
|
|
WriteWindow1(AStream);
|
|
|
|
// { -- currently not working
|
2014-08-30 18:03:22 +00:00
|
|
|
WriteWindow2(AStream, FWorksheet);
|
|
|
|
WritePane(AStream, FWorksheet, false, pane); // false for "is not BIFF5 or BIFF8"
|
|
|
|
WriteSelections(AStream, FWorksheet);
|
2014-05-04 20:01:21 +00:00
|
|
|
//}
|
2014-05-04 18:07:54 +00:00
|
|
|
WriteEOF(AStream);
|
|
|
|
end;
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-05-04 20:01:21 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 WINDOW1 record
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteWindow1(AStream: TStream);
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW1));
|
|
|
|
AStream.WriteWord(WordToLE(9));
|
|
|
|
|
|
|
|
{ Horizontal position of the document window, in twips = 1 / 20 of a point }
|
|
|
|
AStream.WriteWord(WordToLE(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));
|
|
|
|
|
|
|
|
{ Window is visible (1) / hidden (0) }
|
|
|
|
AStream.WriteByte(WordToLE(0));
|
|
|
|
end;
|
|
|
|
|
2014-05-04 18:07:54 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 WINDOW2 record
|
|
|
|
}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteWindow2(AStream: TStream;
|
|
|
|
ASheet: TsWorksheet);
|
|
|
|
var
|
|
|
|
b: Byte;
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW2));
|
|
|
|
AStream.WriteWord(WordToLE(14));
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-05-04 18:07:54 +00:00
|
|
|
{ Show formulas, not results }
|
|
|
|
AStream.WriteByte(0);
|
|
|
|
|
|
|
|
{ Show grid lines }
|
|
|
|
b := IfThen(soShowGridLines in ASheet.Options, 1, 0);
|
|
|
|
AStream.WriteByte(b);
|
|
|
|
|
|
|
|
{ Show sheet headers }
|
|
|
|
b := IfThen(soShowHeaders in ASheet.Options, 1, 0);
|
|
|
|
AStream.WriteByte(b);
|
|
|
|
|
|
|
|
{ Panes are frozen? }
|
2014-06-21 16:09:08 +00:00
|
|
|
b := 0;
|
|
|
|
if (soHasFrozenPanes in ASheet.Options) and
|
|
|
|
((ASheet.LeftPaneWidth > 0) or (ASheet.TopPaneHeight > 0))
|
|
|
|
then
|
|
|
|
b := 1;
|
2014-05-04 18:07:54 +00:00
|
|
|
AStream.WriteByte(b);
|
|
|
|
|
|
|
|
{ Show zero values as zeros, not empty cells }
|
|
|
|
AStream.WriteByte(1);
|
|
|
|
|
|
|
|
{ Index to first visible row }
|
|
|
|
AStream.WriteWord(0);
|
|
|
|
|
|
|
|
{ Index to first visible column }
|
|
|
|
AStream.WriteWord(0);
|
|
|
|
|
|
|
|
{ Use automatic grid line color }
|
|
|
|
AStream.WriteByte(1);
|
|
|
|
|
|
|
|
{ RGB of manual grid line color }
|
|
|
|
AStream.WriteDWord(0);
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteXF(AStream: TStream;
|
|
|
|
AFontIndex, AFormatIndex: byte; ABorders: TsCellBorders = [];
|
|
|
|
AHorAlign: TsHorAlignment = haLeft; AddBackground: Boolean = false);
|
|
|
|
var
|
|
|
|
b: Byte;
|
|
|
|
begin
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_XF));
|
|
|
|
AStream.WriteWord(WordToLE(4));
|
|
|
|
|
|
|
|
{ Index to FONT record }
|
|
|
|
AStream.WriteByte(AFontIndex);
|
|
|
|
|
|
|
|
{ not used }
|
|
|
|
AStream.WriteByte(0);
|
|
|
|
|
2014-05-02 18:49:05 +00:00
|
|
|
{ Number format index and cell flags }
|
2014-04-22 23:10:32 +00:00
|
|
|
b := AFormatIndex and $3F;
|
|
|
|
AStream.WriteByte(b);
|
|
|
|
|
|
|
|
{ Horizontal alignment, border style, and background }
|
|
|
|
b := byte(AHorAlign);
|
|
|
|
if cbWest in ABorders then b := b or $08;
|
|
|
|
if cbEast in ABorders then b := b or $10;
|
|
|
|
if cbNorth in ABorders then b := b or $20;
|
|
|
|
if cbSouth in ABorders then b := b or $40;
|
|
|
|
if AddBackground then b := b or $80;
|
|
|
|
AStream.WriteByte(b);
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteXFFieldsForFormattingStyles(AStream: TStream);
|
|
|
|
var
|
2014-05-14 15:24:02 +00:00
|
|
|
i, j: Integer;
|
2014-04-22 23:10:32 +00:00
|
|
|
lFontIndex: Word;
|
|
|
|
lFormatIndex: Word; //number format
|
|
|
|
lBorders: TsCellBorders;
|
|
|
|
lAddBackground: Boolean;
|
|
|
|
lHorAlign: TsHorAlignment;
|
|
|
|
begin
|
2014-05-02 19:02:24 +00:00
|
|
|
// The loop starts with the first style added manually.
|
|
|
|
// First style was already added (see AddDefaultFormats)
|
2014-04-22 23:10:32 +00:00
|
|
|
for i := 1 to Length(FFormattingStyles) - 1 do begin
|
|
|
|
// Default styles
|
|
|
|
lFontIndex := 0;
|
|
|
|
lFormatIndex := 0; //General format (one of the built-in number formats)
|
|
|
|
lBorders := [];
|
|
|
|
lHorAlign := FFormattingStyles[i].HorAlignment;
|
2014-05-02 18:49:05 +00:00
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
// Now apply the modifications.
|
2014-05-14 15:24:02 +00:00
|
|
|
if uffNumberFormat in FFormattingStyles[i].UsedFormattingFields then begin
|
2014-05-21 23:01:07 +00:00
|
|
|
j := NumFormatList.FindFormatOf(@FFormattingStyles[i]);
|
|
|
|
if j > -1 then
|
2014-05-14 15:24:02 +00:00
|
|
|
lFormatIndex := NumFormatList[j].Index;
|
|
|
|
end;
|
2014-05-02 18:49:05 +00:00
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
if uffBorder in FFormattingStyles[i].UsedFormattingFields then
|
|
|
|
lBorders := FFormattingStyles[i].Border;
|
|
|
|
|
|
|
|
if uffBold in FFormattingStyles[i].UsedFormattingFields then
|
|
|
|
lFontIndex := 1; // must be before uffFont which overrides uffBold
|
|
|
|
|
|
|
|
if uffFont in FFormattingStyles[i].UsedFormattingFields then
|
|
|
|
lFontIndex := FFormattingStyles[i].FontIndex;
|
|
|
|
|
|
|
|
lAddBackground := (uffBackgroundColor in FFormattingStyles[i].UsedFormattingFields);
|
|
|
|
|
|
|
|
// And finally write the style
|
|
|
|
WriteXF(AStream, lFontIndex, lFormatIndex, lBorders, lHorAlign, lAddBackground);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteXFRecords(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
begin
|
|
|
|
WriteXF(AStream, 0, 0); // XF0
|
|
|
|
WriteXF(AStream, 0, 0); // XF1
|
|
|
|
WriteXF(AStream, 0, 0); // XF2
|
|
|
|
WriteXF(AStream, 0, 0); // XF3
|
|
|
|
WriteXF(AStream, 0, 0); // XF4
|
|
|
|
WriteXF(AStream, 0, 0); // XF5
|
|
|
|
WriteXF(AStream, 0, 0); // XF6
|
|
|
|
WriteXF(AStream, 0, 0); // XF7
|
|
|
|
WriteXF(AStream, 0, 0); // XF8
|
|
|
|
WriteXF(AStream, 0, 0); // XF9
|
|
|
|
WriteXF(AStream, 0, 0); // XF10
|
|
|
|
WriteXF(AStream, 0, 0); // XF11
|
|
|
|
WriteXF(AStream, 0, 0); // XF12
|
|
|
|
WriteXF(AStream, 0, 0); // XF13
|
|
|
|
WriteXF(AStream, 0, 0); // XF14
|
|
|
|
WriteXF(AStream, 0, 0); // XF15 - Default, no formatting
|
|
|
|
|
|
|
|
// Add all further non-standard/built-in formatting styles
|
2014-04-23 22:29:32 +00:00
|
|
|
ListAllFormattingStyles;
|
2014-04-22 23:10:32 +00:00
|
|
|
WriteXFFieldsForFormattingStyles(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;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
{
|
|
|
|
Writes an Excel 2 font record
|
|
|
|
The font data is passed as font index.
|
|
|
|
}
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteFont(AStream: TStream; AFontIndex: Integer);
|
2014-04-22 23:10:32 +00:00
|
|
|
var
|
|
|
|
Len: Byte;
|
|
|
|
lFontName: AnsiString;
|
|
|
|
optn: Word;
|
|
|
|
font: TsFont;
|
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
font := Workbook.GetFont(AFontIndex);
|
2014-04-22 23:10:32 +00:00
|
|
|
if font = nil then // this happens for FONT4 in case of BIFF
|
|
|
|
exit;
|
|
|
|
|
|
|
|
if font.FontName = '' then
|
|
|
|
raise Exception.Create('Font name not specified.');
|
|
|
|
if font.Size <= 0.0 then
|
|
|
|
raise Exception.Create('Font size not specified.');
|
|
|
|
|
|
|
|
lFontName := font.FontName;
|
|
|
|
Len := Length(lFontName);
|
|
|
|
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FONT));
|
|
|
|
AStream.WriteWord(WordToLE(4 + 1 + Len * Sizeof(AnsiChar)));
|
|
|
|
|
|
|
|
{ Height of the font in twips = 1/20 of a point }
|
|
|
|
AStream.WriteWord(WordToLE(round(font.Size*20)));
|
|
|
|
|
|
|
|
{ Option flags }
|
|
|
|
optn := 0;
|
|
|
|
if fssBold in font.Style then optn := optn or $0001;
|
|
|
|
if fssItalic in font.Style then optn := optn or $0002;
|
|
|
|
if fssUnderline in font.Style then optn := optn or $0004;
|
|
|
|
if fssStrikeout in font.Style then optn := optn or $0008;
|
|
|
|
AStream.WriteWord(WordToLE(optn));
|
|
|
|
|
|
|
|
{ Font name: Unicodestring, char count in 1 byte }
|
|
|
|
AStream.WriteByte(Len);
|
|
|
|
AStream.WriteBuffer(lFontName[1], Len * Sizeof(AnsiChar));
|
|
|
|
|
|
|
|
{ Font color: goes into next record! }
|
|
|
|
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FONTCOLOR));
|
|
|
|
AStream.WriteWord(WordToLE(2));
|
|
|
|
|
|
|
|
{ Font color index, only first 8 palette entries allowed! }
|
2014-08-11 15:53:06 +00:00
|
|
|
AStream.WriteWord(WordToLE(word(FixColor(font.Color))));
|
2014-04-22 23:10:32 +00:00
|
|
|
end;
|
|
|
|
|
2014-04-23 22:29:32 +00:00
|
|
|
procedure TsSpreadBiff2Writer.WriteFonts(AStream: TStream);
|
2014-04-22 23:10:32 +00:00
|
|
|
var
|
|
|
|
i: Integer;
|
|
|
|
begin
|
2014-04-23 22:29:32 +00:00
|
|
|
for i:=0 to Workbook.GetFontCount-1 do
|
|
|
|
WriteFont(AStream, i);
|
2014-04-22 23:10:32 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
procedure TsSpreadBiff2Writer.WriteFormat(AStream: TStream;
|
|
|
|
AFormatData: TsNumFormatData; AListIndex: Integer);
|
2014-07-15 21:00:49 +00:00
|
|
|
type
|
|
|
|
TNumFormatRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
FormatLen: Byte;
|
|
|
|
end;
|
2014-05-14 15:24:02 +00:00
|
|
|
var
|
|
|
|
len: Integer;
|
|
|
|
s: ansistring;
|
2014-07-15 21:00:49 +00:00
|
|
|
rec: TNumFormatRecord;
|
|
|
|
buf: array of byte;
|
2014-05-14 15:24:02 +00:00
|
|
|
begin
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(AFormatData);
|
|
|
|
|
2014-05-14 15:24:02 +00:00
|
|
|
s := NumFormatList.FormatStringForWriting(AListIndex);
|
|
|
|
len := Length(s);
|
|
|
|
|
2014-07-15 21:00:49 +00:00
|
|
|
{ BIFF record header }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_FORMAT);
|
|
|
|
rec.RecordSize := WordToLE(1 + len);
|
|
|
|
|
|
|
|
{ Length byte of format string }
|
|
|
|
rec.FormatLen := len;
|
|
|
|
|
|
|
|
{ Copy the format string characters into a buffer immediately after rec }
|
|
|
|
SetLength(buf, SizeOf(rec) + SizeOf(ansiChar)*len);
|
|
|
|
Move(rec, buf[0], SizeOf(rec));
|
|
|
|
Move(s[1], buf[SizeOf(rec)], len*SizeOf(ansiChar));
|
|
|
|
|
|
|
|
{ Write out }
|
|
|
|
AStream.WriteBuffer(buf[0], SizeOf(Rec) + SizeOf(ansiChar)*len);
|
|
|
|
|
|
|
|
{ Clean up }
|
|
|
|
SetLength(buf, 0);
|
|
|
|
|
|
|
|
(*
|
2014-05-14 15:24:02 +00:00
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMAT));
|
|
|
|
AStream.WriteWord(WordToLE(1 + len));
|
|
|
|
|
|
|
|
{ Format string }
|
|
|
|
AStream.WriteByte(len); // AnsiString, char count in 1 byte
|
|
|
|
AStream.WriteBuffer(s[1], len); // String data
|
2014-07-15 21:00:49 +00:00
|
|
|
*)
|
2014-05-14 15:24:02 +00:00
|
|
|
end;
|
2014-05-02 18:49:05 +00:00
|
|
|
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteFormatCount(AStream: TStream);
|
|
|
|
begin
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMATCOUNT));
|
|
|
|
AStream.WriteWord(WordToLE(2));
|
2014-05-14 15:24:02 +00:00
|
|
|
AStream.WriteWord(WordToLE(21)); // there are 21 built-in formats
|
2014-05-02 18:49:05 +00:00
|
|
|
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; +
|
|
|
|
}
|
2014-07-01 22:47:10 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
|
2009-06-09 11:19:10 +00:00
|
|
|
var
|
2008-02-24 13:18:34 +00:00
|
|
|
RPNLength: Word;
|
2014-10-03 22:02:09 +00:00
|
|
|
RecordSizePos, FinalPos: Cardinal;
|
2014-04-22 23:10:32 +00:00
|
|
|
xf: Word;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-08-08 14:30:19 +00:00
|
|
|
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
2014-08-07 22:24:22 +00:00
|
|
|
exit;
|
|
|
|
|
2009-06-09 11:19:10 +00:00
|
|
|
RPNLength := 0;
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
xf := FindXFIndex(ACell);
|
|
|
|
if xf >= 63 then
|
|
|
|
WriteIXFE(AStream, xf);
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA));
|
|
|
|
RecordSizePos := AStream.Position;
|
2014-08-17 22:25:46 +00:00
|
|
|
AStream.WriteWord(0); // We don't know the record size yet. It will be replaced at end.
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
{ Row and column }
|
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 }
|
2014-04-22 23:10:32 +00:00
|
|
|
WriteCellFormatting(AStream, ACell, xf);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-07-01 22:47:10 +00:00
|
|
|
{ Encoded result of RPN formula }
|
|
|
|
WriteRPNResult(AStream, ACell);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
{ 0 = Do not recalculate
|
|
|
|
1 = Always recalculate }
|
2014-07-01 22:47:10 +00:00
|
|
|
AStream.WriteByte(1);
|
|
|
|
|
|
|
|
{ Formula data (RPN token array) }
|
2014-08-17 22:25:46 +00:00
|
|
|
if ACell^.SharedFormulaBase <> nil then
|
|
|
|
WriteRPNSharedFormulaLink(AStream, ACell, RPNLength)
|
|
|
|
else
|
2014-09-04 15:27:28 +00:00
|
|
|
WriteRPNTokenArray(AStream, ACell, AFormula, false, RPNLength);
|
2014-08-17 22:25:46 +00:00
|
|
|
|
2014-07-02 11:51:59 +00:00
|
|
|
{ Finally write sizes after we know them }
|
2014-07-01 22:47:10 +00:00
|
|
|
FinalPos := AStream.Position;
|
|
|
|
AStream.Position := RecordSizePos;
|
|
|
|
AStream.WriteWord(WordToLE(17 + RPNLength));
|
|
|
|
AStream.Position := FinalPos;
|
|
|
|
|
|
|
|
{ Write following STRING record if formula result is a non-empty string }
|
|
|
|
if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then
|
|
|
|
WriteStringRecord(AStream, ACell^.UTF8StringValue);
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-01 22:47:10 +00:00
|
|
|
{ Writes the identifier for an RPN function with fixed argument count and
|
|
|
|
returns the number of bytes written. }
|
|
|
|
function TsSpreadBIFF2Writer.WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word;
|
|
|
|
begin
|
|
|
|
AStream.WriteByte(Lo(AIdentifier));
|
|
|
|
Result := 1;
|
|
|
|
end;
|
|
|
|
|
2014-08-17 22:25:46 +00:00
|
|
|
{ This method is intended to write a link to the cell containing the shared
|
|
|
|
formula used by the cell. But since BIFF2 does not support shared formulas
|
|
|
|
the writer must copy the shared formula and adapt the relative
|
|
|
|
references. }
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteRPNSharedFormulaLink(AStream: TStream;
|
|
|
|
ACell: PCell; var RPNLength: Word);
|
|
|
|
var
|
|
|
|
formula: TsRPNFormula;
|
|
|
|
begin
|
2014-08-30 18:03:22 +00:00
|
|
|
// Create RPN formula from the shared formula base's string formula
|
2014-09-04 15:27:28 +00:00
|
|
|
formula := FWorksheet.BuildRPNFormula(ACell);
|
|
|
|
// Don't use ACell^.SharedFormulaBase here because this lookup is made
|
|
|
|
// by the worksheet automatically.
|
2014-08-17 22:25:46 +00:00
|
|
|
|
|
|
|
// Write adapted copy of shared formula to stream.
|
2014-09-04 15:27:28 +00:00
|
|
|
WriteRPNTokenArray(AStream, ACell, formula, false, RPNLength);
|
2014-10-07 14:02:11 +00:00
|
|
|
// false --> "do not convert cell addresses to relative offsets", because
|
2014-09-04 15:27:28 +00:00
|
|
|
// biff2 does not support shared formulas!
|
2014-08-17 22:25:46 +00:00
|
|
|
|
|
|
|
// Clean up
|
|
|
|
SetLength(formula, 0);
|
|
|
|
end;
|
|
|
|
|
2014-07-01 22:47:10 +00:00
|
|
|
{ Writes the size of the RPN token array. Called from WriteRPNFormula.
|
|
|
|
Overrides xlscommon. }
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteRPNTokenArraySize(AStream: TStream;
|
|
|
|
ASize: Word);
|
|
|
|
begin
|
2014-10-06 12:55:28 +00:00
|
|
|
AStream.WriteByte(ASize);
|
|
|
|
// AStream.WriteByte(Lo(ASize));
|
2014-07-01 22:47:10 +00:00
|
|
|
end;
|
|
|
|
|
2014-08-17 22:25:46 +00:00
|
|
|
{ Is intended to write the token array of a shared formula stored in ACell.
|
|
|
|
But since BIFF2 does not support shared formulas this method must not do
|
|
|
|
anything. }
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteSharedFormula(AStream: TStream; ACell: PCell);
|
|
|
|
begin
|
2014-10-03 22:02:09 +00:00
|
|
|
Unused(AStream, ACell);
|
2014-08-17 22:25:46 +00:00
|
|
|
end;
|
|
|
|
|
2014-07-01 22:47:10 +00:00
|
|
|
{ Writes an Excel 2 STRING record which immediately follows a FORMULA record
|
|
|
|
when the formula result is a string. }
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteStringRecord(AStream: TStream;
|
|
|
|
AString: String);
|
|
|
|
var
|
|
|
|
s: ansistring;
|
|
|
|
len: Integer;
|
|
|
|
begin
|
2014-07-02 11:51:59 +00:00
|
|
|
// s := AString; // Why not call UTF8ToAnsi?
|
|
|
|
s := UTF8ToAnsi(AString);
|
2014-07-01 22:47:10 +00:00
|
|
|
len := Length(s);
|
|
|
|
|
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_STRING));
|
|
|
|
AStream.WriteWord(WordToLE(1 + len*SizeOf(Char)));
|
|
|
|
|
|
|
|
{ Write string length }
|
|
|
|
AStream.WriteByte(len);
|
|
|
|
{ Write characters }
|
|
|
|
AStream.WriteBuffer(s[1], len * SizeOf(Char));
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2014-04-21 21:43:43 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* TsSpreadBIFF2Writer.WriteBlank ()
|
|
|
|
*
|
|
|
|
* DESCRIPTION: Writes an Excel 2 record for an empty cell
|
|
|
|
*
|
|
|
|
* Required if this cell should contain formatting
|
|
|
|
*
|
|
|
|
*******************************************************************}
|
|
|
|
procedure TsSpreadBIFF2Writer.WriteBlank(AStream: TStream;
|
|
|
|
const ARow, ACol: Cardinal; ACell: PCell);
|
2014-07-15 21:00:49 +00:00
|
|
|
type
|
|
|
|
TBlankRecord = packed record
|
|
|
|
RecordID: Word;
|
|
|
|
RecordSize: Word;
|
|
|
|
Row: Word;
|
|
|
|
Col: Word;
|
|
|
|
Attrib1, Attrib2, Attrib3: Byte;
|
|
|
|
end;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
var
|
|
|
|
xf: Word;
|
2014-07-15 21:00:49 +00:00
|
|
|
rec: TBlankRecord;
|
2014-04-21 21:43:43 +00:00
|
|
|
begin
|
2014-08-08 14:30:19 +00:00
|
|
|
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
2014-08-07 22:24:22 +00:00
|
|
|
exit;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
xf := FindXFIndex(ACell);
|
|
|
|
if xf >= 63 then
|
|
|
|
WriteIXFE(AStream, xf);
|
|
|
|
|
2014-07-15 21:00:49 +00:00
|
|
|
{ BIFF record header }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_BLANK);
|
|
|
|
rec.RecordSize := WordToLE(7);
|
|
|
|
|
|
|
|
{ BIFF record data }
|
|
|
|
rec.Row := WordToLE(ARow);
|
|
|
|
rec.Col := WordToLE(ACol);
|
|
|
|
|
|
|
|
{ BIFF2 attributes }
|
|
|
|
GetCellAttributes(ACell, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
|
|
|
|
|
|
|
{ Write out }
|
|
|
|
AStream.WriteBuffer(rec, Sizeof(rec));
|
|
|
|
|
|
|
|
(*
|
2014-04-21 21:43:43 +00:00
|
|
|
{ BIFF Record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_BLANK));
|
|
|
|
AStream.WriteWord(WordToLE(7));
|
|
|
|
|
|
|
|
{ BIFF Record data }
|
|
|
|
AStream.WriteWord(WordToLE(ARow));
|
|
|
|
AStream.WriteWord(WordToLE(ACol));
|
|
|
|
|
|
|
|
{ BIFF2 Attributes }
|
2014-04-22 23:10:32 +00:00
|
|
|
WriteCellFormatting(AStream, ACell, xf);
|
2014-07-15 21:00:49 +00:00
|
|
|
*)
|
2014-04-21 21:43:43 +00:00
|
|
|
end;
|
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* 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
|
2014-07-15 21:00:49 +00:00
|
|
|
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;
|
2014-07-21 20:33:25 +00:00
|
|
|
rec: TBIFF2LabelRecord;
|
2014-07-15 21:00:49 +00:00
|
|
|
buf: array of byte;
|
2014-04-22 23:10:32 +00:00
|
|
|
var
|
|
|
|
xf: Word;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-08-08 14:30:19 +00:00
|
|
|
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
2014-08-07 22:24:22 +00:00
|
|
|
exit;
|
|
|
|
|
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
|
|
|
|
2014-07-15 21:00:49 +00:00
|
|
|
if Length(AnsiText) > MAXBYTES then begin
|
2013-12-07 13:42:22 +00:00
|
|
|
// 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.
|
2014-07-15 21:00:49 +00:00
|
|
|
AnsiText := Copy(AnsiText, 1, MAXBYTES);
|
2014-08-07 22:24:22 +00:00
|
|
|
Workbook.AddErrorMsg(
|
|
|
|
'Text value exceeds %d character limit in cell %s. ' +
|
|
|
|
'Text has been truncated.', [
|
|
|
|
MAXBYTES, GetCellString(ARow, ACol)
|
|
|
|
]);
|
2013-12-07 13:42:22 +00:00
|
|
|
end;
|
2009-01-28 17:18:04 +00:00
|
|
|
L := Length(AnsiText);
|
2008-02-24 13:18:34 +00:00
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
xf := FindXFIndex(ACell);
|
|
|
|
if xf >= 63 then
|
|
|
|
WriteIXFE(AStream, xf);
|
|
|
|
|
2014-07-15 21:00:49 +00:00
|
|
|
{ BIFF record header }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_LABEL);
|
|
|
|
rec.RecordSize := WordToLE(8 + L);
|
|
|
|
|
|
|
|
{ BIFF record data }
|
|
|
|
rec.Row := WordToLE(ARow);
|
|
|
|
rec.Col := WordToLE(ACol);
|
|
|
|
|
|
|
|
{ BIFF2 attributes }
|
|
|
|
GetCellAttributes(ACell, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
|
|
|
|
|
|
|
{ Text length: 8 bit }
|
|
|
|
rec.TextLen := L;
|
|
|
|
|
|
|
|
{ Copy the text characters into a buffer immediately after rec }
|
|
|
|
SetLength(buf, SizeOf(rec) + SizeOf(ansiChar)*L);
|
|
|
|
Move(rec, buf[0], SizeOf(rec));
|
|
|
|
Move(AnsiText[1], buf[SizeOf(rec)], L*SizeOf(ansiChar));
|
|
|
|
|
|
|
|
{ Write out }
|
|
|
|
AStream.WriteBuffer(buf[0], SizeOf(Rec) + SizeOf(ansiChar)*L);
|
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);
|
2014-04-22 23:10:32 +00:00
|
|
|
var
|
|
|
|
xf: Word;
|
2014-07-21 20:33:25 +00:00
|
|
|
rec: TBIFF2NumberRecord;
|
2008-02-24 13:18:34 +00:00
|
|
|
begin
|
2014-08-08 14:30:19 +00:00
|
|
|
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
2014-08-07 22:24:22 +00:00
|
|
|
exit;
|
|
|
|
|
2014-04-22 23:10:32 +00:00
|
|
|
xf := FindXFIndex(ACell);
|
|
|
|
if xf >= 63 then
|
|
|
|
WriteIXFE(AStream, xf);
|
2014-07-15 21:00:49 +00:00
|
|
|
|
|
|
|
{ BIFF record header }
|
|
|
|
rec.RecordID := WordToLE(INT_EXCEL_ID_NUMBER);
|
|
|
|
rec.RecordSize := WordToLE(15);
|
|
|
|
|
|
|
|
{ BIFF record data }
|
|
|
|
rec.Row := WordToLE(ARow);
|
|
|
|
rec.Col := WordToLE(ACol);
|
|
|
|
|
|
|
|
{ BIFF2 attributes }
|
|
|
|
GetCellAttributes(ACell, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
|
|
|
|
|
|
|
{ Number value }
|
|
|
|
rec.Value := AValue;
|
|
|
|
|
|
|
|
{ Write out }
|
|
|
|
AStream.WriteBuffer(rec, SizeOf(Rec));
|
2008-02-24 13:18:34 +00:00
|
|
|
end;
|
|
|
|
|
2014-05-07 18:31:27 +00:00
|
|
|
procedure TsSpreadBIFF2Writer.WriteRow(AStream: TStream; ASheet: TsWorksheet;
|
|
|
|
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow);
|
|
|
|
var
|
|
|
|
containsXF: Boolean;
|
|
|
|
rowheight: Word;
|
|
|
|
w: Word;
|
2014-05-31 21:04:53 +00:00
|
|
|
h: Single;
|
2014-05-07 18:31:27 +00:00
|
|
|
begin
|
2014-08-08 14:30:19 +00:00
|
|
|
if (ARowIndex >= FLimitations.MaxRowCount) or (AFirstColIndex >= FLimitations.MaxColCount)
|
|
|
|
or (ALastColIndex >= FLimitations.MaxColCount)
|
2014-08-07 22:24:22 +00:00
|
|
|
then
|
|
|
|
exit;
|
|
|
|
|
2014-06-20 15:58:22 +00:00
|
|
|
Unused(ASheet);
|
|
|
|
|
2014-05-07 18:31:27 +00:00
|
|
|
containsXF := false;
|
|
|
|
|
|
|
|
{ BIFF record header }
|
|
|
|
AStream.WriteWord(WordToLE(INT_EXCEL_ID_ROW));
|
|
|
|
AStream.WriteWord(WordToLE(IfThen(containsXF, 18, 13)));
|
|
|
|
|
|
|
|
{ Index of row }
|
|
|
|
AStream.WriteWord(WordToLE(Word(ARowIndex)));
|
|
|
|
|
|
|
|
{ Index to column of the first cell which is described by a cell record }
|
|
|
|
AStream.WriteWord(WordToLE(Word(AFirstColIndex)));
|
|
|
|
|
|
|
|
{ Index to column of the last cell which is described by a cell record, increased by 1 }
|
|
|
|
AStream.WriteWord(WordToLE(Word(ALastColIndex) + 1));
|
|
|
|
|
|
|
|
{ Row height (in twips, 1/20 point) and info on custom row height }
|
2014-05-31 21:04:53 +00:00
|
|
|
h := Workbook.GetFont(0).Size;
|
2014-09-07 22:25:11 +00:00
|
|
|
if (ARow = nil) or (ARow^.Height = ASheet.DefaultRowHeight) then
|
|
|
|
rowheight := PtsToTwips((ASheet.DefaultRowHeight + ROW_HEIGHT_CORRECTION) * h)
|
2014-05-31 21:04:53 +00:00
|
|
|
else
|
|
|
|
if (ARow^.Height = 0) then
|
|
|
|
rowheight := 0
|
2014-05-07 18:31:27 +00:00
|
|
|
else
|
2014-05-31 21:04:53 +00:00
|
|
|
rowheight := PtsToTwips((ARow^.Height + ROW_HEIGHT_CORRECTION) * h);
|
2014-05-07 18:31:27 +00:00
|
|
|
w := rowheight and $7FFF;
|
|
|
|
AStream.WriteWord(WordToLE(w));
|
|
|
|
|
|
|
|
{ not used }
|
|
|
|
AStream.WriteWord(0);
|
|
|
|
|
|
|
|
{ Contains row attribute field and XF index }
|
|
|
|
AStream.WriteByte(ord(containsXF));
|
|
|
|
|
|
|
|
{ Relative offset to calculate stream position of the first cell record for this row }
|
|
|
|
AStream.WriteWord(0);
|
|
|
|
|
|
|
|
if containsXF then begin
|
|
|
|
{ Default row attributes }
|
|
|
|
AStream.WriteByte(0);
|
|
|
|
AStream.WriteByte(0);
|
|
|
|
AStream.WriteByte(0);
|
|
|
|
|
|
|
|
{ Index to XF record }
|
|
|
|
AStream.WriteWord(WordToLE(15));
|
|
|
|
end;
|
|
|
|
end;
|
2014-04-23 22:29:32 +00:00
|
|
|
|
2008-02-24 13:18:34 +00:00
|
|
|
{*******************************************************************
|
|
|
|
* Initialization section
|
|
|
|
*
|
|
|
|
* Registers this reader / writer on fpSpreadsheet
|
2014-04-24 21:27:57 +00:00
|
|
|
* Converts the palette to litte-endian
|
2008-02-24 13:18:34 +00:00
|
|
|
*
|
|
|
|
*******************************************************************}
|
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);
|
2014-04-24 21:27:57 +00:00
|
|
|
MakeLEPalette(@PALETTE_BIFF2, Length(PALETTE_BIFF2));
|
2008-02-24 13:18:34 +00:00
|
|
|
|
|
|
|
end.
|