7030 lines
157 KiB
ObjectPascal
7030 lines
157 KiB
ObjectPascal
|
|
|||
|
{******************************************}
|
|||
|
{ }
|
|||
|
{ FastReport VCL }
|
|||
|
{ BIFF8 Writing API }
|
|||
|
{ }
|
|||
|
{ Copyright (c) 1998-2021 }
|
|||
|
{ by Fast Reports Inc. }
|
|||
|
{ }
|
|||
|
{******************************************}
|
|||
|
|
|||
|
{ This module provides API for writing BIFF
|
|||
|
streams. This format is documented in
|
|||
|
MSDN [MS-XLS] section.
|
|||
|
|
|||
|
The only way to store BIFF2-BIFF4 documents is
|
|||
|
to write data to a simple stream file. Since BIFF5
|
|||
|
the documents can be stored as compound document files:
|
|||
|
see frxCBFF.pas }
|
|||
|
|
|||
|
unit frxBIFF;
|
|||
|
|
|||
|
interface
|
|||
|
|
|||
|
{$I frx.inc}
|
|||
|
|
|||
|
uses
|
|||
|
Windows,
|
|||
|
Graphics,
|
|||
|
SysUtils,
|
|||
|
Classes,
|
|||
|
Contnrs,
|
|||
|
frxUtils,
|
|||
|
frxStorage,
|
|||
|
frxEscher,
|
|||
|
frxCrypto
|
|||
|
{$IFDEF DELPHI16}
|
|||
|
, System.UITypes
|
|||
|
{$ENDIF}
|
|||
|
;
|
|||
|
|
|||
|
type
|
|||
|
TBiffPaperSize = Word;
|
|||
|
TBiffFormatIndex = LongInt;
|
|||
|
TBiffStreamKind = LongInt; // See bk*** values
|
|||
|
TBiffRecId = Word; // BiffId*** values
|
|||
|
|
|||
|
const
|
|||
|
|
|||
|
{ BIFF8 allows the maximium record
|
|||
|
length of 8228 bytes including the record
|
|||
|
header (4 bytes) }
|
|||
|
|
|||
|
BiffMaxRecLen = $2020;
|
|||
|
|
|||
|
{ The maximum number of blocks in a MERGEDCELLS record.
|
|||
|
Required by [MS-XLS] 2.4.168 }
|
|||
|
|
|||
|
BiffMaxMrgCellsNum = 1026;
|
|||
|
|
|||
|
//
|
|||
|
// Rows in BIFF8 are grouped into blocks.
|
|||
|
//
|
|||
|
|
|||
|
BiffRowBlockSz = 32;
|
|||
|
|
|||
|
//
|
|||
|
// BIFF8 palette size.
|
|||
|
// This size includes the first 8 built-in colors.
|
|||
|
//
|
|||
|
|
|||
|
BiffPaletteSize = $40;
|
|||
|
|
|||
|
{ RRD table contains unique identifiers
|
|||
|
for all sheets in a workbook. If the
|
|||
|
count of sheets is greater that BiffRrdMaxCount
|
|||
|
then the RRD table is not emitted. }
|
|||
|
|
|||
|
BiffRrdMaxCount = $1010;
|
|||
|
|
|||
|
//
|
|||
|
// BIFF8 allows cells with coordinates within
|
|||
|
// the following bounds:
|
|||
|
//
|
|||
|
// row = 0 .. BiffMaxRow
|
|||
|
// col = 0 .. BiffMaxCol
|
|||
|
//
|
|||
|
|
|||
|
BiffMaxRow = $fffe;
|
|||
|
BiffMaxCol = $fe;
|
|||
|
|
|||
|
//
|
|||
|
// Number formats are stored in a table.
|
|||
|
// The first 164 items are reserved and
|
|||
|
// define special widely used formats.
|
|||
|
// The first user format can begin from 164.
|
|||
|
//
|
|||
|
|
|||
|
BiffUserFormat = 164;
|
|||
|
|
|||
|
//
|
|||
|
// BOF records have a field which specifies
|
|||
|
// a type of a stream which they start.
|
|||
|
// TBiffStreamKind lists all possible stream types.
|
|||
|
//
|
|||
|
|
|||
|
bkWBGlobals = 5;
|
|||
|
bkVBModule = 6;
|
|||
|
bkSheet = $10;
|
|||
|
bkChart = $20;
|
|||
|
bkMacro = $40;
|
|||
|
bkWorkspace = $100;
|
|||
|
|
|||
|
//
|
|||
|
// Font options
|
|||
|
//
|
|||
|
|
|||
|
foBold = $1;
|
|||
|
foItalic = $2;
|
|||
|
foUnderline = $4;
|
|||
|
foStruckOut = $8;
|
|||
|
foOutline = $10;
|
|||
|
foShadow = $20;
|
|||
|
foCondense = $40;
|
|||
|
foExtended = $80;
|
|||
|
|
|||
|
//
|
|||
|
// Font underline values
|
|||
|
//
|
|||
|
|
|||
|
fuNone = 0;
|
|||
|
fuSingle = 1;
|
|||
|
fuDouble = 2;
|
|||
|
fuSingleAcc = $21;
|
|||
|
fuDoubleAcc = $22;
|
|||
|
|
|||
|
//
|
|||
|
// Normally, font weight value lies
|
|||
|
// in range 100..1000
|
|||
|
//
|
|||
|
|
|||
|
fwNormal = 400;
|
|||
|
fwBold = 700;
|
|||
|
|
|||
|
//
|
|||
|
// XF cell protection
|
|||
|
//
|
|||
|
|
|||
|
xftpCellLocked = $1;
|
|||
|
xftpHidden = $2;
|
|||
|
xftpStyle = $4;
|
|||
|
|
|||
|
//
|
|||
|
// XF used attributes flags
|
|||
|
//
|
|||
|
|
|||
|
BiffXfuaNumber = $01;
|
|||
|
BiffXfuaFont = $02;
|
|||
|
BiffXfuaText = $04;
|
|||
|
BiffXfuaBorders = $08;
|
|||
|
BiffXfuaBg = $10;
|
|||
|
BiffXfuaCellProt = $20;
|
|||
|
BiffXfuaAll = $3f;
|
|||
|
|
|||
|
//
|
|||
|
// Pattern style
|
|||
|
//
|
|||
|
|
|||
|
psNone = 0;
|
|||
|
psSolid = 1;
|
|||
|
psChess = 2;
|
|||
|
psHorThick = 5;
|
|||
|
psVerThick = 6;
|
|||
|
psChessThick = 9;
|
|||
|
psHor = $B;
|
|||
|
psVer = $C;
|
|||
|
psDiagBack = $D;
|
|||
|
psDiag = $E;
|
|||
|
psCross = $F;
|
|||
|
psCrossDiag = $10;
|
|||
|
|
|||
|
//
|
|||
|
// Window options
|
|||
|
//
|
|||
|
|
|||
|
woFormula = $1;
|
|||
|
woGrid = $2;
|
|||
|
woHeaders = $4;
|
|||
|
woFreezePanes = $8;
|
|||
|
woShowZeros = $10;
|
|||
|
woAutoGridCol = $20;
|
|||
|
woColRTL = $40;
|
|||
|
woOutline = $80;
|
|||
|
woNoSplits = $100;
|
|||
|
woSelected = $200;
|
|||
|
woActive = $400;
|
|||
|
woPageBreak = $800;
|
|||
|
|
|||
|
//
|
|||
|
// Sheet type
|
|||
|
//
|
|||
|
|
|||
|
skWorksheet = 0;
|
|||
|
skChart = 2;
|
|||
|
skVB = 6;
|
|||
|
|
|||
|
//
|
|||
|
// BIFF keeps all format strings in a single list.
|
|||
|
// Several entries in this list are reserved.
|
|||
|
// A few indexes to these reserved formats are listed here.
|
|||
|
//
|
|||
|
|
|||
|
BiffFmtGeneral = 0;
|
|||
|
BiffFmtFixedPoint = 2; // 0.00
|
|||
|
BiffFmtThSep = 4; // #,##0.00
|
|||
|
BiffFmtCurrency = 5; // "$"#,##0_);("$"#,##0)
|
|||
|
BiffFmtDateTime = 22;//32; // M/D/YY h:mm
|
|||
|
|
|||
|
//
|
|||
|
// Flags for options of WINDOW2 object
|
|||
|
//
|
|||
|
|
|||
|
BiffWoFormulas = 1; // show formulas; clear = show results of formulas
|
|||
|
BiffWoGridLines = 2; // show grid lines
|
|||
|
BiffWoHeaders = 4; // show sheet headers
|
|||
|
BiffWoFrozen = 8; // panes are frozen
|
|||
|
BiffWoZeros = $10; // show zeros; clear = show zeros as empty cells
|
|||
|
BiffWoAutoGridColor = $20; // automatic grid line color; clear = manual
|
|||
|
BiffWoColumnsRTL = $40; // columns from right to left; clear = left to right
|
|||
|
BiffWoOutline = $80; // show outline symbols
|
|||
|
BiffWoNoSplits = $100; // remove splits if pane freeze is removed
|
|||
|
BiffWoSelected = $200; // sheet selected
|
|||
|
BiffWoActive = $400; // sheet active
|
|||
|
BiffWoPageBreak = $800; // show in page break preview; clear = normal view
|
|||
|
|
|||
|
{ List of named paper sizes. }
|
|||
|
|
|||
|
BiffPsUnknown = 0;
|
|||
|
BiffPsA4 = 9;
|
|||
|
BiffPsReservedMin = 118;
|
|||
|
BiffPsReservedMax = 255;
|
|||
|
BiffPsCustomMin = 256;
|
|||
|
|
|||
|
{ Flags for BOF record as specified
|
|||
|
in [MS-XLS] section 2.4.21. }
|
|||
|
|
|||
|
BiffBoffWin = 1;
|
|||
|
BiffBoffRisc = 2;
|
|||
|
BiffBoffBeta = 4;
|
|||
|
BiffBoffWinAny = 8;
|
|||
|
BiffBoffMacAny = $10;
|
|||
|
BiffBoffBetaAny = $20;
|
|||
|
BiffBoffRiscAny = $100;
|
|||
|
BiffBoffFontLim = $2000;
|
|||
|
|
|||
|
{ Flags for WSBOOL record.
|
|||
|
See [MS-XLS] section 2.4.351 }
|
|||
|
|
|||
|
BiffWsbShowBreaks = $0001; // show page breaks that added automatically
|
|||
|
BiffWsbDialog = $0010; // sheet is dialog
|
|||
|
BiffWsbOutline = $0020;
|
|||
|
BiffWsbRowSums = $0040;
|
|||
|
BiffWsbColSums = $0080;
|
|||
|
BiffWsbFitPage = $0100; // fit the sheet to a print page
|
|||
|
BiffWsbHSync = $1000;
|
|||
|
BiffWsbVSync = $2000;
|
|||
|
BiffWsbAltExpr = $4000;
|
|||
|
BiffWsbAltFormulas = $8000;
|
|||
|
|
|||
|
type
|
|||
|
|
|||
|
{ BIFF stream.
|
|||
|
|
|||
|
BIFF stream is a sequence of small
|
|||
|
data blocks named BIFF records. Each record
|
|||
|
has a header (2 byte Id and 2 byte Size field)
|
|||
|
and an optional data block. Normally, a BIFF stream
|
|||
|
starts with a BOF record and ends with a EOF record. }
|
|||
|
|
|||
|
TBiffStream = class;
|
|||
|
|
|||
|
TBiffRecord = class
|
|||
|
private
|
|||
|
|
|||
|
FOwner: TBiffStream;
|
|||
|
FOffset: Cardinal;
|
|||
|
|
|||
|
function GetRecId: TBiffRecId;
|
|||
|
function GetSize: Cardinal;
|
|||
|
procedure SetSize(n: Cardinal);
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
constructor Create(Owner: TBiffStream; Offset: Cardinal);
|
|||
|
|
|||
|
{ These methods append data to the record }
|
|||
|
|
|||
|
procedure Write(const Buffer; Count: Cardinal);
|
|||
|
procedure WriteConst(Value, Size: Cardinal);
|
|||
|
procedure WriteZeros(Size: Cardinal);
|
|||
|
procedure WriteStream(Stream: TStream);
|
|||
|
|
|||
|
{ Allows to write data at arbitrary place inside the record }
|
|||
|
|
|||
|
procedure WriteBytes(Offset, Data, Count: Cardinal);
|
|||
|
|
|||
|
{ Saves and loads contents of the record }
|
|||
|
|
|||
|
procedure SaveToStream(Stream: TStream);
|
|||
|
procedure LoadFromStream(Stream: TStream);
|
|||
|
|
|||
|
{ Each BIFF record has an Id value that
|
|||
|
defines a type of the contents }
|
|||
|
|
|||
|
property Id: TBiffRecId read GetRecId;
|
|||
|
|
|||
|
{ Size of the record's contents. This size doesn't include
|
|||
|
the 4-byte record header. }
|
|||
|
|
|||
|
property Size: LongWord read GetSize write SetSize;
|
|||
|
|
|||
|
{ Each BIFF record is stored within a BIFF stream.
|
|||
|
This value contains an offset in bytes from the
|
|||
|
beginning of the BIFF stream to this BIFF record. }
|
|||
|
|
|||
|
property Offset: LongWord read FOffset;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TBiffStream = class
|
|||
|
private
|
|||
|
|
|||
|
FStream: TStream;
|
|||
|
FRecords: TListCache; // List of TBiffRecord
|
|||
|
FLastRec: TBiffRecord; // Last added record
|
|||
|
|
|||
|
function ReadBytes(Offset, Count: Cardinal): Cardinal;
|
|||
|
procedure WriteBytes(Offset, Data, Count: Cardinal);
|
|||
|
|
|||
|
procedure Append(Data: Cardinal; Count: Cardinal);
|
|||
|
procedure AppendData(const Data; Count: Cardinal);
|
|||
|
procedure AppendRecord(Rec: TBiffRecord; const Data; DataSize: Cardinal);
|
|||
|
|
|||
|
function GetRecord(Index: Integer): TBiffRecord;
|
|||
|
function GetRecCount: Integer;
|
|||
|
function GetSize: Cardinal;
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
constructor Create(Cached: Boolean = False);
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
function Add(Id: TBiffRecId): TBiffRecord;
|
|||
|
function AddBOF(k: TBiffStreamKind): TBiffRecord;
|
|||
|
function AddEOF: TBiffRecord;
|
|||
|
|
|||
|
procedure SaveToStream(Stream: TStream);
|
|||
|
|
|||
|
property Records[Index: LongInt]: TBiffRecord read GetRecord; default;
|
|||
|
property Count: Integer read GetRecCount;
|
|||
|
property Size: Cardinal read GetSize;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{ BIFF object.
|
|||
|
|
|||
|
BIFF object is anything that has
|
|||
|
representation in a BIFF stream.
|
|||
|
BIFF objects are: workbook, worksheet,
|
|||
|
text cell, unicode string and so on. }
|
|||
|
|
|||
|
TBiffObject = class
|
|||
|
|
|||
|
{ Every BIFF object must be capable to
|
|||
|
serialize itself in a BIFF stream. }
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); virtual; abstract;
|
|||
|
|
|||
|
function GetHashCode: LongInt; reintroduce; virtual;
|
|||
|
function Equals(s: TBiffObject): Boolean; reintroduce; virtual;
|
|||
|
|
|||
|
{ This method calls Flush method for each BIFF object
|
|||
|
in the list. }
|
|||
|
|
|||
|
class procedure FlushList(list: TObjList; Stream: TBiffStream);
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// BIFF8 unicode string
|
|||
|
//
|
|||
|
|
|||
|
TBiffUCS = class(TBiffObject)
|
|||
|
private
|
|||
|
|
|||
|
FData: WideString;
|
|||
|
FRuns: TMemoryStream;
|
|||
|
FHash: LongInt;
|
|||
|
FCompress: Boolean;
|
|||
|
|
|||
|
procedure SetData(const Value: WideString);
|
|||
|
|
|||
|
{ UCS16 string is compressible if it contains only
|
|||
|
symbols in range 0..255, i.e. the high byte of each symbol
|
|||
|
is zero. }
|
|||
|
|
|||
|
function IsCompressible: Boolean;
|
|||
|
|
|||
|
procedure Init;
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
Len16: Boolean; // set, if the length of this string must occupy two bytes
|
|||
|
Tag: LongInt; // not used by this class
|
|||
|
SstIndex: Integer; // not used by this class
|
|||
|
|
|||
|
constructor Create; overload;
|
|||
|
constructor Create(const S: WideString; UCS16: Boolean); overload;
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
procedure AddFormat(Position, Font: LongInt);
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
{ Compares itself with a TBiffUCS using
|
|||
|
a created hash. }
|
|||
|
|
|||
|
function Equals(s: TBiffObject): Boolean; override;
|
|||
|
|
|||
|
{ Returns a hash code for this string.
|
|||
|
The created hash code is cached. }
|
|||
|
|
|||
|
function GetHashCode: LongInt; override;
|
|||
|
|
|||
|
property Data: WideString read FData write SetData;
|
|||
|
property Compress: Boolean read FCompress write FCompress;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// STYLE
|
|||
|
//
|
|||
|
|
|||
|
TBiffBuiltinStyleId = (
|
|||
|
bsiNormal,
|
|||
|
bsiRowLevel,
|
|||
|
bsiColLevel,
|
|||
|
bsiCOmma,
|
|||
|
bsiCurrency,
|
|||
|
bsiPercent,
|
|||
|
bsiComma0,
|
|||
|
bsiCurrency0,
|
|||
|
bsiHyperlink,
|
|||
|
bsiFHyperlink);
|
|||
|
|
|||
|
TBiffStyle = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
XF: LongInt;
|
|||
|
StyleId: LongInt;
|
|||
|
Level: LongInt;
|
|||
|
Name: WideString;
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// BLANK
|
|||
|
//
|
|||
|
|
|||
|
TBiffCell = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
Row: LongInt;
|
|||
|
Col: LongInt;
|
|||
|
XF: LongInt;
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// Link Table (EXTRENALBOOK, EXTERNALSHEET, etc.)
|
|||
|
//
|
|||
|
|
|||
|
TBiffLinkTable = class(TBiffObject)
|
|||
|
private
|
|||
|
|
|||
|
{ i-th REF is associated with a sheet named FIntSheetNames[i],
|
|||
|
which has index FIntSheetIndex[i] in the workbook }
|
|||
|
|
|||
|
FIntSheetNames: array of string;
|
|||
|
FIntSheetIndex: array of Integer;
|
|||
|
FSheetsCount: Integer;
|
|||
|
|
|||
|
function FindSheet(const Name: string): Integer;
|
|||
|
|
|||
|
function GetRefsCount: Integer;
|
|||
|
procedure SetRefsCount(n: Integer);
|
|||
|
public
|
|||
|
|
|||
|
{ The Link Table contains a mapping from internal sheet names to
|
|||
|
indices to REF records. Formulas need this mapping: in expression
|
|||
|
like SheetABC!G8 the sheet name SheetABC is stored indirectly,
|
|||
|
via an associated with it an index to its REF record. }
|
|||
|
|
|||
|
function GetInternalSheetRef(const SheetName: string): Integer;
|
|||
|
|
|||
|
{ Links a sheet name with a corresponding sheet index. Sheet indices are known
|
|||
|
by TBiffWorkbook, so this method is called by the workbook. }
|
|||
|
|
|||
|
procedure SetSheetIndex(const SheetName: string; Index: Integer);
|
|||
|
|
|||
|
{ Writes itself as a series of record. A link table is repersented with several
|
|||
|
records, unlike the most of other objects, which are represented by one record. }
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
property RefsCount: Integer read GetRefsCount write SetRefsCount;
|
|||
|
property SheetsCount: Integer read FSheetsCount write FSheetsCount;
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// FORMULA
|
|||
|
//
|
|||
|
|
|||
|
TBiffFormulaCell = class(TBiffCell)
|
|||
|
private
|
|||
|
FInst: TStream;
|
|||
|
public
|
|||
|
constructor Create(Instructions: TStream);
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
end;
|
|||
|
|
|||
|
{ Formulas lexer }
|
|||
|
|
|||
|
TBiffFormulaLexemKind =
|
|||
|
(
|
|||
|
flkVoid, // indicates that a lexem does not exist
|
|||
|
flkSpace, // several characters that are considered to be whitespaces
|
|||
|
flkName, // a sequence of letters and numbers: ABS, LOG, but not LOG10, A11
|
|||
|
flkInt, // an unsigned integer
|
|||
|
flkString, // a string
|
|||
|
flkOp, // an operator, like < > <= >= <> & * + -
|
|||
|
flkSymbol // some special symbol of unrecognized meaning
|
|||
|
);
|
|||
|
|
|||
|
TBiffFormulaLexem = record
|
|||
|
Kind: TBiffFormulaLexemKind;
|
|||
|
Text: string;
|
|||
|
end;
|
|||
|
|
|||
|
TBiffFormulaLexer = class
|
|||
|
private
|
|||
|
FText: string;
|
|||
|
FPos: Integer;
|
|||
|
FLexems: array of TBiffFormulaLexem;
|
|||
|
|
|||
|
function IsAlpha(c: Char): Boolean;
|
|||
|
function IsDigit(c: Char): Boolean;
|
|||
|
|
|||
|
function Read(Len: Integer): string;
|
|||
|
function GetChar: Char;
|
|||
|
function NextChar: Char;
|
|||
|
procedure SkipChar;
|
|||
|
function SubStr(Pos, Len: Integer): string;
|
|||
|
|
|||
|
procedure Add(const Lex: TBiffFormulaLexem); overload;
|
|||
|
procedure Add(LexKind: TBiffFormulaLexemKind; const LexText: string); overload;
|
|||
|
|
|||
|
function AddSpace: Boolean;
|
|||
|
function AddNumber: Boolean;
|
|||
|
function AddSymbol: Boolean;
|
|||
|
function AddName: Boolean;
|
|||
|
function AddString(Quote: Char): Boolean;
|
|||
|
function AddOp: Boolean;
|
|||
|
|
|||
|
function GetLexemsCount: Integer;
|
|||
|
function GetLexem(i: Integer): TBiffFormulaLexem;
|
|||
|
|
|||
|
procedure Analyse(const Formula: string);
|
|||
|
public
|
|||
|
property Formula: string read FText write Analyse;
|
|||
|
property Count: Integer read GetLexemsCount;
|
|||
|
property Lexems[i: Integer]: TBiffFormulaLexem read GetLexem; default;
|
|||
|
end;
|
|||
|
|
|||
|
{ Formulas RPN stack }
|
|||
|
|
|||
|
TBiffFormulaTokenKind =
|
|||
|
(
|
|||
|
ftkVoid,
|
|||
|
ftkArg,
|
|||
|
ftkOp // pops a number of args and pushes one result back; Flags = the number of args
|
|||
|
);
|
|||
|
|
|||
|
TBiffFormulaOperatorKind =
|
|||
|
(
|
|||
|
fokVoid,
|
|||
|
fokPush, // pushes to stack one argument
|
|||
|
|
|||
|
fokDiv100, // %
|
|||
|
fokNeg, // -
|
|||
|
|
|||
|
fokAdd, // +
|
|||
|
fokSub, // -
|
|||
|
fokDiv, // /
|
|||
|
fokMul, // *
|
|||
|
fokPow, // ^
|
|||
|
|
|||
|
fokArea, // : creates a cell area (A2:G6)
|
|||
|
fokColon, // : creates a cell area like fokArea, but more general (A2:INDIRECT("G6"))
|
|||
|
fokIsect, // intersects two ranges, e.g. A1:G5 A2:H7; denoted by one space character
|
|||
|
fokExt, // ! is used to refer to an external cell; e.g. "Sheet 10"!G6
|
|||
|
fokJoin, // & joins two strings; e.g. "123" & "abc"
|
|||
|
|
|||
|
fokL, // <
|
|||
|
fokG, // >
|
|||
|
fokE, // =
|
|||
|
fokNE, // <>
|
|||
|
fokLE, // <=
|
|||
|
fokGE, // >=
|
|||
|
|
|||
|
fokNull, // null value; it's used for representing a missing argument: SUM(A12,,7)
|
|||
|
fokNumber, // a floating point number
|
|||
|
fokBool, // a boolean value; Flags = 1 means "true", Flags = 0 means "false"
|
|||
|
fokString, // a string
|
|||
|
fokCell, // a cell reference; Flags: bit 0 = row is relative; bit 1 = column is relative
|
|||
|
fokFunc, // a function call
|
|||
|
fokId // the identity operator; pops one arg and pushes it back unmodified
|
|||
|
);
|
|||
|
|
|||
|
TBiffFormulaToken = record
|
|||
|
Kind: TBiffFormulaTokenKind;
|
|||
|
Op: TBiffFormulaOperatorKind;
|
|||
|
Text: string;
|
|||
|
Flags: Integer;
|
|||
|
end;
|
|||
|
|
|||
|
TBiffFormulaTokenArray = array of TBiffFormulaToken;
|
|||
|
|
|||
|
TBiffFormulaRPNStack = class
|
|||
|
private
|
|||
|
FNumArgs: Integer; // number of arguments in FCode
|
|||
|
FCode: TBiffFormulaTokenArray; // resulting program
|
|||
|
FStack: TBiffFormulaTokenArray; // stack of operators awaiting to being unrolled
|
|||
|
FFrame: TBiffFormulaTokenArray; // stack of function calls
|
|||
|
|
|||
|
procedure Error(const Msg: string);
|
|||
|
procedure Ensure(b: Boolean; const Msg: string = 'RPN stack failed');
|
|||
|
|
|||
|
procedure Push(var Res: TBiffFormulaTokenArray; const t: TBiffFormulaToken);
|
|||
|
function Pop(var a: TBiffFormulaTokenArray): TBiffFormulaToken;
|
|||
|
function Top(const a: TBiffFormulaTokenArray): TBiffFormulaToken;
|
|||
|
|
|||
|
procedure PushOp(Op: TBiffFormulaToken); overload; // pushes an arith op recognizing the precedence order
|
|||
|
procedure PopOp; // moves one operator from FStack to FCode
|
|||
|
|
|||
|
procedure Unroll; // unrolls one frame
|
|||
|
function Joinable(Op: TBiffFormulaOperatorKind): Boolean; // whether Op can be added on top of FStack
|
|||
|
|
|||
|
function OpPriority(Kind: TBiffFormulaOperatorKind): Integer;
|
|||
|
|
|||
|
function GetCount: Integer;
|
|||
|
function GetInstruction(i: Integer): TBiffFormulaToken;
|
|||
|
function GetFrameArgs: Integer; // number of available arguments in the current frame
|
|||
|
function GetFrameOps: Integer; // number of operators in the current frame
|
|||
|
public
|
|||
|
procedure PushArg(Op: TBiffFormulaOperatorKind; const Text: string; Flags: Integer = 0);
|
|||
|
procedure PushOp(Op: TBiffFormulaOperatorKind; const Text: string; NumArgs: Integer = 0); overload;
|
|||
|
|
|||
|
procedure PushFrame(const Func: string = ''); // pushes a function call
|
|||
|
procedure PopFrame; // it's called whenever the closing parenthesis occurs
|
|||
|
|
|||
|
property Count: Integer read GetCount;
|
|||
|
property Instructions[i: Integer]: TBiffFormulaToken read GetInstruction; default;
|
|||
|
end;
|
|||
|
|
|||
|
EBiffFormulaRPNStackError = class(Exception);
|
|||
|
|
|||
|
{ Formulas parser
|
|||
|
|
|||
|
BNF of accepted formulas:
|
|||
|
|
|||
|
formula ::= ppterm (binop ppterm)*
|
|||
|
ppterm := [prefop] term [postop]
|
|||
|
term ::= cell | area | string | number | func | name | extcell | extarea
|
|||
|
func ::= [name [int]] "(" [formula] ("," formula)* ")"
|
|||
|
number ::= int [ "." int ]
|
|||
|
cell ::= ["$"] name ["$"] int
|
|||
|
extcell ::= sheet "!" cell
|
|||
|
extarea ::= sheet "!" area
|
|||
|
area ::= cell ":" cell
|
|||
|
sheet ::= string | name
|
|||
|
binop ::= "+" | "-" | "*" | "/" | "^" | etc.
|
|||
|
prefop ::= "-" | "+"
|
|||
|
postop ::= "%"
|
|||
|
|
|||
|
(string, int, name - indivisible lexems; they are emitted by the lexer)
|
|||
|
|
|||
|
After a formula analysed, the parser builds a sequence of tokens (instructions)
|
|||
|
available through its Tokens property. Each token represents an instruction or an argument
|
|||
|
for an instruction. This a token can have one of two kinds:
|
|||
|
|
|||
|
ftkOp A token of this kind represents an instruction that must be executed
|
|||
|
by a virtual processor. The Flags field defines the number of arguments
|
|||
|
taken.
|
|||
|
|
|||
|
ftkArg A token of this kind represents an argument, which can be a number, a string,
|
|||
|
a cell, the null arguments and so on. This token is not an instruction and
|
|||
|
its only purpose is to be an argument for a token of kind ftkOp.
|
|||
|
|
|||
|
Here is an example of tokens produced from the formula "SUM(A3:B4 G5) + SheetABC!$G$8^2":
|
|||
|
|
|||
|
cell(A3) This is an argument token. It represents a cell.
|
|||
|
cell(B4)
|
|||
|
area:2 This is an operator token. It takes two previous tokens, makes an area (A3:B4) and
|
|||
|
pushes the area to the stack.
|
|||
|
cell(G5)
|
|||
|
push:1 This is an operator token. It takes on previous token (G5) and pushes it to the stack.
|
|||
|
isect:2 This operator pops two values from the stack, intersects them and pushes the result back.
|
|||
|
call(SUM):1 This operator pops one value from the stack, calls SUM and pushes the result to the stack.
|
|||
|
str(SheetABC) This is an argument token. It represents a string.
|
|||
|
cell(G8):3 Here "3" is the Flags field. It has bits 0 and 1 set. This means that
|
|||
|
the row and the column are absolute, not relative.
|
|||
|
extcell:2 This operator takes 2 previous tokens ("SheetABC", "$G$8"), makes an external cell
|
|||
|
reference from them (SheetABC!$G$8) and pushes it onto the stack.
|
|||
|
num(2) This is an argument.
|
|||
|
pow:2 This operator takes two values from the stack (SheetABC!$G$8 and 2), does exponentiation
|
|||
|
and pushes the result (SheetABC!$G$8^2) onto the stack.
|
|||
|
add:2 Like as pow:2, but does addition. }
|
|||
|
|
|||
|
TBiffFormulaParser = class
|
|||
|
private
|
|||
|
FLexems: array of TBiffFormulaLexem;
|
|||
|
FPos: Integer; // current lexem
|
|||
|
FSavedPos: array of Integer;
|
|||
|
FRPN: TBiffFormulaRPNStack;
|
|||
|
FCode: TBiffFormulaTokenArray;
|
|||
|
|
|||
|
function CreateArgToken(Kind: TBiffFormulaOperatorKind;
|
|||
|
const Text: string = ''; Flags: Integer = 0): TBiffFormulaToken;
|
|||
|
|
|||
|
{ emits two tokens: arg and push }
|
|||
|
|
|||
|
procedure Push(Arg: TBiffFormulaOperatorKind; const Text: string = ''; Flags: Integer = 0);
|
|||
|
|
|||
|
{ errors reporing }
|
|||
|
|
|||
|
procedure Error(const ErrorMsg: string);
|
|||
|
procedure Ensure(b: Boolean; const ErrorMsg: string = 'Invalid formula');
|
|||
|
|
|||
|
{ lexems traversing }
|
|||
|
|
|||
|
function Lexem(i: Integer = 0): TBiffFormulaLexem; // returns (FPos + i) lexem or flkVoid
|
|||
|
procedure SkipLexem;
|
|||
|
function SkipLexemIf(const Text: string): Boolean; overload;
|
|||
|
function SkipLexemIf(Kind: TBiffFormulaLexemKind; out Text: string): Boolean; overload;
|
|||
|
function IsOp(const Lex: TBiffFormulaLexem; const Text: string): Boolean;
|
|||
|
function IsArgSep(const Lex: TBiffFormulaLexem): Boolean;
|
|||
|
|
|||
|
{ parser's state saving/restoring }
|
|||
|
|
|||
|
procedure Save; // saves the parser's state
|
|||
|
procedure Load; // loads the parser's state
|
|||
|
procedure Discard; // discards the last saved state
|
|||
|
|
|||
|
{ operators propeties }
|
|||
|
|
|||
|
function OpKind(const s: string): TBiffFormulaOperatorKind;
|
|||
|
|
|||
|
{ parsing methods }
|
|||
|
|
|||
|
procedure Parse(const s: string);
|
|||
|
procedure BuildLexems(const s: string);
|
|||
|
procedure CleanLexems;
|
|||
|
procedure CopyCodeFromRPNStack;
|
|||
|
|
|||
|
function ReadSym(const s: string): Boolean;
|
|||
|
function ReadString(out s: string): Boolean;
|
|||
|
function ReadNumber(out s: string): Boolean;
|
|||
|
function ReadName(out s: string): Boolean;
|
|||
|
function ReadOp(out s: string): Boolean;
|
|||
|
function ReadSheet(out s: string): Boolean;
|
|||
|
function ReadCell(out t: TBiffFormulaToken): Boolean;
|
|||
|
|
|||
|
function ParseFormula: Boolean;
|
|||
|
function ParsePPTerm: Boolean;
|
|||
|
function ParsePrefOp: Boolean;
|
|||
|
function ParsePostOp: Boolean;
|
|||
|
function ParseTerm: Boolean;
|
|||
|
function ParseString: Boolean;
|
|||
|
function ParseNumber: Boolean;
|
|||
|
function ParseCell: Boolean;
|
|||
|
function ParseArea: Boolean;
|
|||
|
function ParseBinOp: Boolean;
|
|||
|
function ParseFuncCall: Boolean;
|
|||
|
function ParseNameConst: Boolean;
|
|||
|
function ParseExtCell: Boolean;
|
|||
|
function ParseExtArea: Boolean;
|
|||
|
|
|||
|
{ property getters/setters }
|
|||
|
|
|||
|
function GetToken(i: Integer): TBiffFormulaToken;
|
|||
|
function GetTokensCount: Integer;
|
|||
|
public
|
|||
|
property Formula: string write Parse;
|
|||
|
property Tokens[i: Integer]: TBiffFormulaToken read GetToken; default;
|
|||
|
property Count: Integer read GetTokensCount;
|
|||
|
end;
|
|||
|
|
|||
|
EBiffFormulaParserError = class(Exception);
|
|||
|
|
|||
|
{ Formulas code emitter }
|
|||
|
|
|||
|
TBiffFormulaCellRef = record
|
|||
|
Row: Integer; // zero based row index
|
|||
|
Col: Integer; // zero based column index
|
|||
|
AbsRow: Boolean; // row index is absolute; in $G7 the index is absolute; in G7 it's relative
|
|||
|
AbsCol: Boolean; // column index is absolute; in G$7 the index is absolute; in G7 it's relative
|
|||
|
end;
|
|||
|
|
|||
|
TBiffFormulaRetType = (frtVoid, frtRef, frtVal, frtArray);
|
|||
|
|
|||
|
TBiffFormulaCodeEmitter = class
|
|||
|
private
|
|||
|
FInst: TStream;
|
|||
|
FRetTypeMode: TBiffFormulaRetType;
|
|||
|
|
|||
|
procedure Error(const ErrorMsg: string);
|
|||
|
procedure Ensure(b: Boolean; const ErrorMsg: string = 'Cannot emit opcode');
|
|||
|
procedure EnsureCellRange(const Cell: TBiffFormulaCellRef);
|
|||
|
|
|||
|
function RelFlags(RelRow, RelCol: Boolean): Byte;
|
|||
|
|
|||
|
procedure WriteOpCode(Op: Byte);
|
|||
|
procedure Write(Inst: Cardinal; Len: Cardinal = 1);
|
|||
|
procedure WriteCellRef(const Cell: TBiffFormulaCellRef);
|
|||
|
procedure WriteAreaRef(const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
public
|
|||
|
|
|||
|
{ commands that push a value on the stack }
|
|||
|
|
|||
|
procedure Push(Value: Integer); overload;
|
|||
|
procedure Push(Value: Double); overload;
|
|||
|
procedure Push(b: Boolean); overload;
|
|||
|
procedure Push(const s: string); overload;
|
|||
|
procedure PushNull;
|
|||
|
procedure PushCell(const Cell: TBiffFormulaCellRef);
|
|||
|
procedure PushArea(const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
procedure PushExtCell(SheetRefId: Integer; const Cell: TBiffFormulaCellRef);
|
|||
|
procedure PushExtArea(SheetRefId: Integer; const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
|
|||
|
{ function calls }
|
|||
|
|
|||
|
procedure Call(Func: Cardinal); overload;
|
|||
|
procedure Call(Func, NumArgs: Cardinal); overload;
|
|||
|
procedure CallId; // identity function; emits parethesis for display purposes
|
|||
|
|
|||
|
{ unary operators }
|
|||
|
|
|||
|
procedure Neg; // unary -
|
|||
|
procedure Div100; // %
|
|||
|
|
|||
|
{ binary arithmetic operators }
|
|||
|
|
|||
|
procedure Add; // +
|
|||
|
procedure Sub; // -
|
|||
|
procedure Mul; // *
|
|||
|
procedure Divide; // /
|
|||
|
procedure Pow; // ^
|
|||
|
|
|||
|
{ binary comparsion operators }
|
|||
|
|
|||
|
procedure CmpL; // <
|
|||
|
procedure CmpG; // >
|
|||
|
procedure CmpLE; // <=
|
|||
|
procedure CmpGE; // >=
|
|||
|
procedure CmpE; // =
|
|||
|
procedure CmpNE; // <>
|
|||
|
|
|||
|
{ cell operators }
|
|||
|
|
|||
|
procedure Intersect; // intersects two areas
|
|||
|
procedure Range; // :
|
|||
|
|
|||
|
{ string operators }
|
|||
|
|
|||
|
procedure Join; // &
|
|||
|
|
|||
|
property Output: TStream read FInst write FInst;
|
|||
|
property RetMode: TBiffFormulaRetType read FRetTypeMode write FRetTypeMode;
|
|||
|
end;
|
|||
|
|
|||
|
EBiffFormulaCodeEmitterError = class(Exception);
|
|||
|
|
|||
|
{ Functions list.
|
|||
|
|
|||
|
todo -cOptimisation: This clumsy class represents a map which stores items
|
|||
|
sorted by key. It can be represented by AVL, red-black or b-tree,
|
|||
|
but because of lack of generics in old Delphi versions,
|
|||
|
there's no easy way adapt existing classes in frxStorage for
|
|||
|
string keys. In far future this class should be replaced with something like
|
|||
|
TAVLTree<string, TBiffFormulaFunc>. }
|
|||
|
|
|||
|
TBiffFormulaFunc = record
|
|||
|
Name: string;
|
|||
|
Id: Integer;
|
|||
|
MinArgs: Integer;
|
|||
|
MaxArgs: Integer;
|
|||
|
Volatile: Boolean;
|
|||
|
RetType: Char;
|
|||
|
ArgTypes: string;
|
|||
|
end;
|
|||
|
|
|||
|
TBiffFormulaFuncArray = array of TBiffFormulaFunc;
|
|||
|
|
|||
|
TBiffFormulaFuncList = class
|
|||
|
private
|
|||
|
class procedure Init;
|
|||
|
class procedure Add(const f: TBiffFormulaFunc); overload;
|
|||
|
class function Find(Name: string): Integer;
|
|||
|
class function GetCount: Integer;
|
|||
|
class function GetFunc(i: Integer): TBiffFormulaFunc;
|
|||
|
class procedure SetFunc(i: Integer; const f: TBiffFormulaFunc);
|
|||
|
class procedure SetCount(n: Integer);
|
|||
|
public
|
|||
|
|
|||
|
{ [MS-XLS] Section 2.5.198.17
|
|||
|
|
|||
|
Name The function name. Letters case doesn't matter - this method upcases all letters.
|
|||
|
Id The function id. It can be found in the documentation.
|
|||
|
MinArgs The minimum number of arguments the function takes.
|
|||
|
MaxArgs The maximum number of arguments the function takes.
|
|||
|
RetType The result type. See note below.
|
|||
|
ArgTypes Arguments types. The actual length of this array can be less than MaxArgs. In this
|
|||
|
case all missing entries are assumed to be equal to the last entry; e.g.
|
|||
|
SUM takes up to 30 args and all of them are references, so ArgTypes can be set to
|
|||
|
"r" or "rr" or "rrr" - all these strings are equally assumed to be equal to the string
|
|||
|
with 30 r's - "rrr....rrr".
|
|||
|
|
|||
|
Result type of arguments types take one of three values: 'R' (reference), 'V' (value) and 'A' (array).
|
|||
|
See docs for meaning of these types. }
|
|||
|
|
|||
|
class procedure Add(Id: Integer; Name: string; MinArgs, MaxArgs: Integer;
|
|||
|
RetType: Char; ArgTypes: string; Volatile: Boolean = False); overload;
|
|||
|
|
|||
|
class function Exists(const Name: string): Boolean;
|
|||
|
class function Get(const Name: string): TBiffFormulaFunc;
|
|||
|
class function GetArgType(const Name: string; i: Integer): Char;
|
|||
|
end;
|
|||
|
|
|||
|
{ Formulas compiler }
|
|||
|
|
|||
|
TBiffFormulaCompiler = class
|
|||
|
private
|
|||
|
FParser: TBiffFormulaParser;
|
|||
|
FPos: Integer; // current token
|
|||
|
FEmitter: TBiffFormulaCodeEmitter;
|
|||
|
FCode: TStream;
|
|||
|
FLinkTable: TBiffLinkTable;
|
|||
|
FRetTypes: array of TBiffFormulaRetType;
|
|||
|
|
|||
|
function GetLinkTable: TBiffLinkTable;
|
|||
|
|
|||
|
function Token(i: Integer = 0): TBiffFormulaToken;
|
|||
|
procedure SkipToken;
|
|||
|
procedure SelectToken(i: Integer);
|
|||
|
|
|||
|
procedure Error(const Fmt: string; const Args: array of const);
|
|||
|
procedure Ensure(b: Boolean; const ErrorMsg: string = 'Cannot compile formula'); overload;
|
|||
|
procedure Ensure(b: Boolean; const Fmt: string; const Args: array of const); overload;
|
|||
|
|
|||
|
function IsCell(const t: TBiffFormulaToken): Boolean;
|
|||
|
function IsStr(const t: TBiffFormulaToken): Boolean;
|
|||
|
|
|||
|
function GetCellPos(const t: TBiffFormulaToken): TBiffFormulaCellRef;
|
|||
|
|
|||
|
{ Excel doesn't perform types coercion. This method finds for each token
|
|||
|
what type it should return. }
|
|||
|
|
|||
|
procedure CalcRetTypes;
|
|||
|
|
|||
|
procedure Compile(const s: string);
|
|||
|
procedure CompileToken;
|
|||
|
|
|||
|
procedure EmitNum(Num: Double);
|
|||
|
procedure EmitOp(Kind: TBiffFormulaOperatorKind; NumArgs: Integer);
|
|||
|
procedure EmitFunc(Name: string; NumArgs: Integer);
|
|||
|
procedure EmitIdFunc(NumArgs: Integer);
|
|||
|
procedure EmitArea(const Cell1, Cell2: TBiffFormulaToken);
|
|||
|
procedure EmitExtCell(const Sheet, Cell: TBiffFormulaToken);
|
|||
|
procedure EmitExtArea(const Sheet, Cell1, Cell2: TBiffFormulaToken);
|
|||
|
procedure EmitPush(const t: TBiffFormulaToken);
|
|||
|
public
|
|||
|
constructor Create;
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
procedure SaveToStream(Stream: TStream);
|
|||
|
|
|||
|
property Formula: string write Compile;
|
|||
|
|
|||
|
{ Link table is needed to compile external cell references: SheetABC!G8.
|
|||
|
If the formula doesn't contain such expressions, then LinkTable can be left nil. }
|
|||
|
|
|||
|
property LinkTable: TBiffLinkTable read GetLinkTable write FLinkTable;
|
|||
|
end;
|
|||
|
|
|||
|
EBiffFormulaCompilerError = class(Exception);
|
|||
|
|
|||
|
//
|
|||
|
// LABELSST
|
|||
|
//
|
|||
|
|
|||
|
TBiffTextCell = class(TBiffCell)
|
|||
|
public
|
|||
|
|
|||
|
SST: LongInt;
|
|||
|
|
|||
|
constructor Create(SST: LongInt);
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// NUMBER
|
|||
|
//
|
|||
|
|
|||
|
TBiffNumberCell = class(TBiffCell)
|
|||
|
public
|
|||
|
|
|||
|
Value: Double; // IEEE 754 floating-point value (64-bit double precision)
|
|||
|
|
|||
|
constructor Create(Value: Double);
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// Palette.
|
|||
|
//
|
|||
|
// BIFF document can contain a PALETTE record
|
|||
|
// which specifies an array of used colors.
|
|||
|
// To refer to a color in this array, a color index
|
|||
|
// is used. There're several special built-in
|
|||
|
// colors. Their indexes are defined below.
|
|||
|
//
|
|||
|
|
|||
|
TBiffColorIndex = (
|
|||
|
ciBlack,
|
|||
|
ciWhite,
|
|||
|
ciRed,
|
|||
|
ciGreen,
|
|||
|
ciBlue,
|
|||
|
ciYellow,
|
|||
|
ciMagenta,
|
|||
|
ciCyan
|
|||
|
);
|
|||
|
|
|||
|
//
|
|||
|
// Font.
|
|||
|
//
|
|||
|
|
|||
|
TBiffFontOptions = LongInt; // See fo*** values
|
|||
|
|
|||
|
TBiffFontFamily = (
|
|||
|
ffNone,
|
|||
|
ffRoman,
|
|||
|
ffSwiss,
|
|||
|
ffModern,
|
|||
|
ffScript,
|
|||
|
ffDecorative);
|
|||
|
|
|||
|
TBiffFontEscapement = (
|
|||
|
feNone,
|
|||
|
feSuperScript,
|
|||
|
feSubScript);
|
|||
|
|
|||
|
TBiffFontUnderline = LongInt; // See fu*** values
|
|||
|
TBiffFontWeight = LongInt; // See fw*** values
|
|||
|
|
|||
|
TBiffFontData = record
|
|||
|
|
|||
|
Height: LongInt; // Font height in twips = 1/20 of a point
|
|||
|
Options: Word; // See TBiffFontOptions
|
|||
|
Color: Word; // Index to a color
|
|||
|
Weight: Word;
|
|||
|
Esc: TBiffFontEscapement;
|
|||
|
Underline: TBiffFontUnderline;
|
|||
|
Family: TBiffFontFamily;
|
|||
|
Charset: Byte; // For byte strings
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TBiffFont = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
Data: TBiffFontData;
|
|||
|
Name: WideString;
|
|||
|
Hash: Integer;
|
|||
|
FontIndex: Integer; // not used by this class
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
function Equals(Font: TBiffObject): Boolean; override;
|
|||
|
function GetHashCode: Integer; override;
|
|||
|
|
|||
|
//
|
|||
|
// Returns width in points of a specified string as though
|
|||
|
// it's drawed with this font. On failure, returns 0.
|
|||
|
//
|
|||
|
// This function is complicated and its results
|
|||
|
// should be cached to increase performance.
|
|||
|
//
|
|||
|
|
|||
|
function StrWidth(const Str: WideString): LongInt;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// XF.
|
|||
|
//
|
|||
|
// Extended cell formatting.
|
|||
|
//
|
|||
|
|
|||
|
TBiffXFTypeProt = LongInt; // See xftp** values
|
|||
|
TBiffXFUsedAttrib = LongInt;// See BiffXfua*** values
|
|||
|
TBiffPatternStyle = LongInt;// See ps*** values
|
|||
|
|
|||
|
TBiffXFHAlign = (
|
|||
|
xfhaGeneral,
|
|||
|
xfhaLeft,
|
|||
|
xfhaCentered,
|
|||
|
xfhaRight,
|
|||
|
xfhaFilled,
|
|||
|
xfhaJustified,
|
|||
|
xfhaCAS, // Centred across selection
|
|||
|
xfhaDistributed);
|
|||
|
|
|||
|
TBiffXFVAlign = (
|
|||
|
xfvaTop,
|
|||
|
xfvaCentered,
|
|||
|
xfvaBottom,
|
|||
|
xfvaJustified,
|
|||
|
xfvaDistributed);
|
|||
|
|
|||
|
TBiffXFTextDir = (
|
|||
|
xftdAuto,
|
|||
|
xftdLTR,
|
|||
|
xftdRTL);
|
|||
|
|
|||
|
TBiffXFOrientation = (
|
|||
|
xfoNone,
|
|||
|
xfoTop, // Letters are stacked top-to-bottom, but not rotated
|
|||
|
xfo90CCW, // 90 degrees counterclockwise
|
|||
|
xfo90CW); // 90 degrees clockwise
|
|||
|
|
|||
|
TBiffLineStyle = (
|
|||
|
lsNone,
|
|||
|
lsThin,
|
|||
|
lsMedium,
|
|||
|
lsDashed,
|
|||
|
lsDotted,
|
|||
|
lsThick,
|
|||
|
lsDouble,
|
|||
|
lsHair,
|
|||
|
lsMediumDashed,
|
|||
|
lsThinDashDotted,
|
|||
|
lsMediumDashDotted,
|
|||
|
lsThinDashDotDotted,
|
|||
|
lsMediumDashDotDotted,
|
|||
|
lsSlantedMediumDashDotted
|
|||
|
);
|
|||
|
|
|||
|
TBiffLine = record
|
|||
|
|
|||
|
Style: TBiffLineStyle;
|
|||
|
Color: Byte; // Index to a color
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TBiffXfData = record
|
|||
|
|
|||
|
Font: LongInt; // Index to FONT record
|
|||
|
Format: LongInt; // Index to FORMAT record
|
|||
|
Prot: Byte; // See TBiffXFTypeProt
|
|||
|
Parent: LongInt; // Index to parent XF. Always -1 in style XFs.
|
|||
|
HAlign: TBiffXFHAlign;
|
|||
|
WordWrap: Boolean;
|
|||
|
VAlign: TBiffXFVAlign;
|
|||
|
Justify: Boolean; // Justify last line in justified or distibuted text
|
|||
|
Rotation: Byte;
|
|||
|
|
|||
|
//
|
|||
|
// Indent level is measured in units,
|
|||
|
// each of them equals to length of three
|
|||
|
// blank characters.
|
|||
|
//
|
|||
|
|
|||
|
Indent: Byte;
|
|||
|
|
|||
|
Shrink: Boolean; // Shrink content to fit into cell
|
|||
|
Direction: TBiffXFTextDir;
|
|||
|
UsedAttrs: Byte; // See TBiffXFUsedAttrib
|
|||
|
|
|||
|
L, T, R, B: TBiffLine; // Left, top, right and bottom lines
|
|||
|
D: TBiffLine; // Diagonal line
|
|||
|
LTRB: Boolean; // Diagonal line from left-top to right-bottom
|
|||
|
LBRT: Boolean; // Diagonal line from left-bottom to right-top
|
|||
|
|
|||
|
Patt: Byte; // See TBiffPatternStyle and docs
|
|||
|
PattColor: Word; // Index to a color
|
|||
|
PattBgColor:Word; // Index to a color
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TBiffXF = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
XfIndex: Integer; // Index to XF table. Not used by this class.
|
|||
|
Data: TBiffXfData;
|
|||
|
Hash: Integer;
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
function Equals(XF: TBiffObject): Boolean; override;
|
|||
|
function GetHashCode: Integer; override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// COLINFO
|
|||
|
// Defines formatting for several consequent columns.
|
|||
|
//
|
|||
|
|
|||
|
TBiffColInfo = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
First: LongInt; // Index to the first column
|
|||
|
Last: LongInt; // Index to the last column
|
|||
|
XF: LongInt; // Index to an XF record
|
|||
|
Hidden: Boolean;
|
|||
|
Collapsed: Boolean;
|
|||
|
Outline: Byte; // Must be in 0..7 range
|
|||
|
Width: LongInt; // In 1/256 of the zero character of the 0-th font
|
|||
|
|
|||
|
constructor Create(Column, XF, Width: LongInt);
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// WINDOW1
|
|||
|
//
|
|||
|
|
|||
|
TBiffWindowOptions = LongInt; // See wo*** values
|
|||
|
|
|||
|
TBiffWindow = class
|
|||
|
public
|
|||
|
|
|||
|
HPos, VPos: LongInt; // In twips = 1/20 of a point
|
|||
|
Width, Height: LongInt; // In twips = 1/20 of a point
|
|||
|
ActiveSheet: LongInt;
|
|||
|
FirstTab: LongInt;
|
|||
|
SelSheet: LongInt;
|
|||
|
TabWidth: LongInt; // Worksheet tab bar. 1/1000 of window width
|
|||
|
|
|||
|
Visible: Boolean; // Window
|
|||
|
Open: Boolean; // Open/minimized
|
|||
|
HSBVisible: Boolean; // Hor. scroll bar
|
|||
|
VSBVisible: Boolean; // Vert. scroll bar
|
|||
|
TabVisible: Boolean; // Worksheet tab
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream);
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// ROW
|
|||
|
//
|
|||
|
|
|||
|
TBiffRow = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
Row: LongInt; // Index to this row
|
|||
|
Cells: TObjList; // List of TBiffCell
|
|||
|
FirstCol: LongInt; // Index to column of the first cell
|
|||
|
LastCol: LongInt; // Index to column of the last cell
|
|||
|
Height: LongInt; // Height in twips
|
|||
|
Outline: Byte; // Valid outline levels are 0..7
|
|||
|
XF: LongInt; // Index to an XF record
|
|||
|
Hidden: Boolean;
|
|||
|
|
|||
|
{ These two fields are used by TBiffSheet, not
|
|||
|
by this class.
|
|||
|
|
|||
|
- FirstCell - stream position of the first cell of this row
|
|||
|
- Offset - stream position of this row }
|
|||
|
|
|||
|
FirstCell: LongWord;
|
|||
|
Offset: LongWord;
|
|||
|
|
|||
|
constructor Create;
|
|||
|
destructor Destroy; override;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// PAGESETUP
|
|||
|
//
|
|||
|
|
|||
|
TBiffPageOrientation = (
|
|||
|
bpoLandscape,
|
|||
|
bpoPortrait
|
|||
|
);
|
|||
|
|
|||
|
TBiffPageSetup = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
Size: TBiffPaperSize;
|
|||
|
Orient: TBiffPageOrientation;
|
|||
|
Copies: LongInt;
|
|||
|
Colored: Boolean; // Print in color or black and white
|
|||
|
Xdpi: Word;
|
|||
|
Ydpi: Word;
|
|||
|
PrintInRows:Boolean;
|
|||
|
Draft: Boolean; // Draft print quality
|
|||
|
CellNotes: Boolean; // Print cell notes
|
|||
|
SheetNotes: Boolean; // Print notes at the end of the sheet
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{ FILEPASS
|
|||
|
|
|||
|
TBiffRC4 allows to encrypt excel workbook data using the RC4 cipher.
|
|||
|
This class is serialized into the FILEPASS record. }
|
|||
|
|
|||
|
TBiffRC4 = class(TBiffObject)
|
|||
|
private
|
|||
|
|
|||
|
{ Specific data to be written to the output
|
|||
|
FILEPASS record }
|
|||
|
|
|||
|
FSalt: array[1..16] of Byte; // Random data
|
|||
|
FVerifier: array[1..16] of Byte; // Encrypted verifier (that is random data)
|
|||
|
FHash: array[1..16] of Byte; // Encrypted MD5 of the verifier
|
|||
|
|
|||
|
{ Partial hash required for creating
|
|||
|
the encryption key. }
|
|||
|
|
|||
|
FTruncHash: array[1..5] of Byte;
|
|||
|
|
|||
|
{ RC4 cipher }
|
|||
|
|
|||
|
FCipher: TCryptoRC4;
|
|||
|
|
|||
|
{ Positioon of the currently encrypted byte }
|
|||
|
|
|||
|
FTail: LongInt;
|
|||
|
FBlockId: LongInt;
|
|||
|
|
|||
|
procedure EncryptEx(Data: Pointer; Size: LongInt);
|
|||
|
procedure MD5(out Hash; const Data; Size: Integer);
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
{ RC4 cipher performs a self test. If the test has failed,
|
|||
|
an exception is raised. }
|
|||
|
|
|||
|
constructor Create;
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
{ Creates a partial hash as specified in [MS-OFFCRYPTO] section 2.3.6.2. }
|
|||
|
|
|||
|
procedure Prepare(const Password; PassLen: LongInt);
|
|||
|
|
|||
|
{ Creates the FILEPASS record in a BIFF stream.
|
|||
|
This routine must be called after the Prepare routine. }
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
{ Initializes the RC4 cipher with a specified key }
|
|||
|
|
|||
|
procedure Init(Key: Pointer; Len: LongInt);
|
|||
|
|
|||
|
{ Initializes the RC4 cipher as specified in [MS-OFFCRYPTO] section 2.3.6.2.
|
|||
|
This routine must be called after the Prepare routine, because
|
|||
|
Initialize uses the partial hash that's generated by Prepare }
|
|||
|
|
|||
|
procedure Initialize(BlockId: LongWord);
|
|||
|
|
|||
|
{ Encrypts a data block "in place".
|
|||
|
The RC4 cipher generates a crypto stream (based on the password),
|
|||
|
that is combined with the original data with XOR operation.
|
|||
|
If Data is nil, the crypto stream is generated but is written
|
|||
|
nowhere. }
|
|||
|
|
|||
|
procedure Encrypt(Data: Pointer; Size: LongInt); cdecl;
|
|||
|
|
|||
|
{ This routine encrypts a BIFF stream as specified in
|
|||
|
[MS-XLS] section 2.2.10. }
|
|||
|
|
|||
|
procedure EncryptStream(Stream: TBiffStream);
|
|||
|
end;
|
|||
|
|
|||
|
TBiffBounds = record
|
|||
|
|
|||
|
FR: LongInt; // First row
|
|||
|
LR: LongInt; // Last row
|
|||
|
FC: LongInt; // First column
|
|||
|
LC: LongInt; // Last column
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// WINDOW2
|
|||
|
//
|
|||
|
|
|||
|
TBiffWindow2 = class(TBiffObject)
|
|||
|
public
|
|||
|
|
|||
|
Options: Word; // See BiffWo*** values
|
|||
|
FirstRow: Word; // First visible row
|
|||
|
FirstCol: Word; // First visible column
|
|||
|
GridCol: Word; // Grid line color
|
|||
|
MFPBP: Word; // Cached magnification factor in page break preview
|
|||
|
CMFNV: Word; // Cached magnification factor in normal view
|
|||
|
|
|||
|
constructor Create;
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{ BIFF sheet.
|
|||
|
|
|||
|
Any XLS BIFF document consists of a few sheets.
|
|||
|
A sheet is represented by class TBiffSheet.
|
|||
|
An instance of this class can be accessed by a single
|
|||
|
thread at a time. }
|
|||
|
|
|||
|
TBiffSheetKind = LongInt; // See sk*** values
|
|||
|
|
|||
|
TBiffSheetVisibility = (
|
|||
|
svVisible,
|
|||
|
svHidden,
|
|||
|
svVeryHidden); // Such a sheet can only be shown/hidden by VB macro
|
|||
|
|
|||
|
TBiffMargin = record
|
|||
|
|
|||
|
Left: Double; // Inches
|
|||
|
Top: Double;
|
|||
|
Right: Double;
|
|||
|
Bottom: Double;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TRectangle = class
|
|||
|
private
|
|||
|
FRect: TRect;
|
|||
|
function GetBottom: Integer;
|
|||
|
function GetBottomRight: TPoint;
|
|||
|
function GetLeft: Integer;
|
|||
|
function GetRight: Integer;
|
|||
|
function GetTop: Integer;
|
|||
|
function GetTopLeft: TPoint;
|
|||
|
procedure SetBottom(const Value: Integer);
|
|||
|
procedure SetBottomRight(const Value: TPoint);
|
|||
|
procedure SetLeft(const Value: Integer);
|
|||
|
procedure SetRight(const Value: Integer);
|
|||
|
procedure SetTop(const Value: Integer);
|
|||
|
procedure SetTopLeft(const Value: TPoint);
|
|||
|
public
|
|||
|
property Bottom: Integer read GetBottom write SetBottom;
|
|||
|
property BottomRight: TPoint read GetBottomRight write SetBottomRight;
|
|||
|
property Left: Integer read GetLeft write SetLeft;
|
|||
|
property Right: Integer read GetRight write SetRight;
|
|||
|
property Top: Integer read GetTop write SetTop;
|
|||
|
property TopLeft: TPoint read GetTopLeft write SetTopLeft;
|
|||
|
end;
|
|||
|
|
|||
|
TBiffWorkbook = class;
|
|||
|
|
|||
|
TBiffSheet = class(TBiffObject)
|
|||
|
private
|
|||
|
|
|||
|
FRows: TObjList; // List of TBiffRow. Sorted by the row index.
|
|||
|
FCols: TObjList; // List of TBiffColInfo
|
|||
|
FMrgCells: TObjectList; // rectangles of merged cells
|
|||
|
FDGroup: TEscherGroup; // Drawing group. This class doesn't delete DGroup.
|
|||
|
FOwner: TBiffWorkbook;
|
|||
|
FKind: TBiffSheetKind;
|
|||
|
FVis: TBiffSheetVisibility;
|
|||
|
FPB: TList; // List of LongInt (indexes to row where a page break occurs)
|
|||
|
FStrCount: LongInt; // number of text cells
|
|||
|
|
|||
|
FLastReadRow: Integer;
|
|||
|
|
|||
|
procedure SetColWidth(i: LongInt; w: LongInt);
|
|||
|
procedure SetRowHeight(i: LongInt; h: LongInt);
|
|||
|
function GetColWidth(i: LongInt): LongInt;
|
|||
|
function GetRowHeight(i: LongInt): LongInt;
|
|||
|
function GetRow(Index: LongInt): TBiffRow; // optimised for sequential access
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
Name: WideString;
|
|||
|
Margin: TBiffMargin;
|
|||
|
PageSetup: TBiffPageSetup;
|
|||
|
View: TBiffWindow2;
|
|||
|
Bounds: TBiffBounds; // Bounding rectangle of all cells
|
|||
|
|
|||
|
{ Index to the corresponding SHEET
|
|||
|
record which is placed in the Workbook
|
|||
|
globals stream. This index is formed by
|
|||
|
TBiffSheet.Flush method and used by
|
|||
|
TBiffWorkbook.Flush method to alter the
|
|||
|
SHEET record after all sheets are written
|
|||
|
to a BIFF stream. }
|
|||
|
|
|||
|
RecIndex: Integer;
|
|||
|
|
|||
|
constructor Create(Owner: TBiffWorkbook);
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
//
|
|||
|
// Adda a new cell to the list of cells.
|
|||
|
//
|
|||
|
|
|||
|
procedure AddCell(Cell: TBiffCell);
|
|||
|
|
|||
|
{ Merges a rectangle area of cells }
|
|||
|
|
|||
|
procedure MergeCells(Rect: TRect);
|
|||
|
|
|||
|
//
|
|||
|
// Adds a colinfo block to the list of colinfos
|
|||
|
//
|
|||
|
|
|||
|
procedure AddColInfo(Info: TBiffColInfo);
|
|||
|
|
|||
|
{ Adds a new drawing to the sheet }
|
|||
|
|
|||
|
function AddDrawing: TEscherShape;
|
|||
|
|
|||
|
//
|
|||
|
// This function is to be called at the very end
|
|||
|
// to create a BIFF8 sheet substream.
|
|||
|
//
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
{ Adds a page break }
|
|||
|
|
|||
|
procedure AddPageBreak(Row: LongInt);
|
|||
|
|
|||
|
{ Returns the index to the last row in this sheet.
|
|||
|
If there're no rows, -1 is returned. }
|
|||
|
|
|||
|
function LastRowIndex: LongInt;
|
|||
|
|
|||
|
{ Column width is measured in 1/256 of width of the '0'
|
|||
|
character written by the 0-th font specified in
|
|||
|
the appropriate workbook. Row height is measured in twips.
|
|||
|
|
|||
|
1 point = 20 twips
|
|||
|
|
|||
|
When trying to read a non-existing column or a row, zero is
|
|||
|
returned. When trying to change size of an inaccesible column
|
|||
|
or a row (negative indexes, etc.), nothing is changed. }
|
|||
|
|
|||
|
property ColWidth[i: LongInt]: LongInt read GetColWidth write SetColWidth;
|
|||
|
property RowHeight[i: LongInt]: LongInt read GetRowHeight write SetRowHeight;
|
|||
|
|
|||
|
property TextCellsCount: Integer read FStrCount;
|
|||
|
property Visibility: TBiffSheetVisibility read FVis;
|
|||
|
property Kind: TBiffSheetKind read FKind;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{ BIFF workbook.
|
|||
|
|
|||
|
The workbook is an Excel document containing some information
|
|||
|
and several sheets. The workbook is represented by class TBiffWorkbook.
|
|||
|
An instance of this class can be accessed by multiple threads at a moment.
|
|||
|
This is achieved via usage of critical section objects. }
|
|||
|
|
|||
|
TBiffWorkbook = class(TBiffObject)
|
|||
|
private
|
|||
|
|
|||
|
{ Shared Strings Table }
|
|||
|
|
|||
|
SST: TObjList; // List of TBiffUCS
|
|||
|
SSTHash: TListHashTable;
|
|||
|
|
|||
|
Win: TBiffWindow;
|
|||
|
Sheets: TObjList; // List of TBiffSheet
|
|||
|
Styles: TObjList; // List of TBiffStyle
|
|||
|
FFormats: TObjList; // List of TBiffUCS
|
|||
|
Escher: TEscherStorage; // Global Escher storage
|
|||
|
FLinkTbl: TBiffLinkTable; // Contains external references; created on demand
|
|||
|
|
|||
|
{ Shared Fonts Table
|
|||
|
|
|||
|
First four FONT records are reserved
|
|||
|
and have special meaning }
|
|||
|
|
|||
|
Fonts: TObjList; // List of TBiffFont
|
|||
|
FontHash: TListHashTable;
|
|||
|
|
|||
|
{ Shared XFs Table
|
|||
|
|
|||
|
Any BIFF document must contain
|
|||
|
at least 16 XF records used specially. }
|
|||
|
|
|||
|
XFs: TObjList; // List of TBiffXF
|
|||
|
XFHash: TListHashTable;
|
|||
|
|
|||
|
{ Shared Colors Table
|
|||
|
|
|||
|
When anywhere a color is needed, this color
|
|||
|
is placed to PALETTE record and index to
|
|||
|
the color in the palette is used. Colors with
|
|||
|
indexes 0..7 are built-in, colors with indexes
|
|||
|
8..$3F are user defined.
|
|||
|
|
|||
|
PALETTE record may be omitted and then the
|
|||
|
default Excel palette is used.
|
|||
|
|
|||
|
To add a color to the palette
|
|||
|
use TboffWorkbook.AddColor method. }
|
|||
|
|
|||
|
Palette: array[0..BiffPaletteSize-1] of LongWord;
|
|||
|
PalUsed: LongWord;
|
|||
|
|
|||
|
{ Optionally, the workbook can be encrypted }
|
|||
|
|
|||
|
FCipher: TBiffRC4;
|
|||
|
|
|||
|
{ The following critical section objects are used for
|
|||
|
mutual access to different parts of a workbook. }
|
|||
|
|
|||
|
FCsStrings: TRTLCriticalSection;
|
|||
|
FCsFonts: TRTLCriticalSection;
|
|||
|
FCsXFs: TRTLCriticalSection;
|
|||
|
FCsSheets: TRTLCriticalSection;
|
|||
|
FCsStyles: TRTLCriticalSection;
|
|||
|
FCsColors: TRTLCriticalSection;
|
|||
|
FCsPictures: TRTLCriticalSection;
|
|||
|
FCsFormats: TRTLCriticalSection;
|
|||
|
|
|||
|
{ TBiffWorkbook can be accesses by a few threads.
|
|||
|
In this case, threads must synchronize access to
|
|||
|
the shared workbook object. Here is a typical example
|
|||
|
of use these functions:
|
|||
|
|
|||
|
<EFBFBD> thread wants to add a string to a workbook
|
|||
|
<EFBFBD> thread calls TBiffWorkbook.LockSst
|
|||
|
<EFBFBD> thread adds a string via TBiffWorkbook.AddString
|
|||
|
<EFBFBD> thread calls TBiffWorkbook.UnlockSst
|
|||
|
|
|||
|
TBiffWorkbook performs these locks automatically, i.e.
|
|||
|
client threads may not know about these. }
|
|||
|
|
|||
|
procedure LockSst;
|
|||
|
procedure UnlockSst;
|
|||
|
|
|||
|
procedure LockFonts;
|
|||
|
procedure UnlockFonts;
|
|||
|
|
|||
|
procedure LockXfs;
|
|||
|
procedure UnlockXfs;
|
|||
|
|
|||
|
procedure LockSheets;
|
|||
|
procedure UnlockSheets;
|
|||
|
|
|||
|
procedure LockStyles;
|
|||
|
procedure UnlockStyles;
|
|||
|
|
|||
|
procedure LockColors;
|
|||
|
procedure UnlockColors;
|
|||
|
|
|||
|
procedure LockPictures;
|
|||
|
procedure UnlockPictures;
|
|||
|
|
|||
|
procedure LockFormats;
|
|||
|
procedure UnlockFormats;
|
|||
|
|
|||
|
{ Internal methods }
|
|||
|
|
|||
|
function GetLinkTable: TBiffLinkTable;
|
|||
|
function GetSheetsCount: LongInt;
|
|||
|
function GetSheet(Index: LongInt): TBiffSheet;
|
|||
|
function GetFontsCount: LongInt;
|
|||
|
function GetFont(Index: LongInt): TBiffFont;
|
|||
|
procedure SetFormat(i: TBiffFormatIndex; const s: WideString);
|
|||
|
function AddBlip(Blip: TEscherPicture): LongInt;
|
|||
|
function AddColorInternal(C: LongWord): LongInt;
|
|||
|
|
|||
|
public
|
|||
|
|
|||
|
constructor Create;
|
|||
|
destructor Destroy; override;
|
|||
|
|
|||
|
{ Workbook can be protected with a password.
|
|||
|
The password can contain up to 255 unicode characters. }
|
|||
|
|
|||
|
procedure SetPassword(const s: WideString);
|
|||
|
|
|||
|
{ BIFF8 contains all used strings in the SST table,
|
|||
|
all cells with strings refers to the SST table.
|
|||
|
If a cell needs to use a string, this function must be called.
|
|||
|
|
|||
|
Returns an SST index.
|
|||
|
Multithread safe. }
|
|||
|
|
|||
|
function AddString(S: TBiffUCS): LongWord; overload;
|
|||
|
function AddString(S: WideString): LongWord; overload;
|
|||
|
|
|||
|
{ In BIFF8 fonts have specific indexing: the 4-th FONT record
|
|||
|
is omitted. Use this function to add a new font and receive
|
|||
|
its valid index.
|
|||
|
|
|||
|
Returns a font index.
|
|||
|
Multithread safe. }
|
|||
|
|
|||
|
function AddFont(F: TBiffFont): LongWord;
|
|||
|
|
|||
|
{ These routines add a specified kind of object to the workbook.
|
|||
|
They are multithread safe. }
|
|||
|
|
|||
|
function AddXF(X: TBiffXF): LongWord;
|
|||
|
function AddSheet(S: TBiffSheet): LongWord;
|
|||
|
function AddStyle(S: TBiffStyle): LongInt;
|
|||
|
function AddFormat(const FormatStr: WideString;
|
|||
|
FormatIndex: TBiffFormatIndex = -1): TBiffFormatIndex;
|
|||
|
|
|||
|
{ Adds a color to the palette and returns an index to the
|
|||
|
added color. If the color already exists, the index to
|
|||
|
the existing color is returned. If the palette is full
|
|||
|
and the color cannot be added, an index to the nearest color
|
|||
|
is returned.
|
|||
|
|
|||
|
Byte structure of the color: RR GG BB AA
|
|||
|
Multithread safe. }
|
|||
|
|
|||
|
function AddColor(C: LongWord): LongInt;
|
|||
|
|
|||
|
{ Adds a picture to the workbook.
|
|||
|
AddBitmap is applicable for BMP, PNG, JPEG, TIFF, etc.
|
|||
|
Multithread safe. }
|
|||
|
|
|||
|
function AddBitmap(Kind: TEscherBlipKind; Contents: TStream): LongInt;
|
|||
|
function AddMetafile(Metafile: Graphics.TMetafile): LongInt;
|
|||
|
|
|||
|
{ Returns a count of strings occured in the workbook.
|
|||
|
Identical strings are counted as much as they occur.
|
|||
|
|
|||
|
Multithread safe. }
|
|||
|
|
|||
|
function StringsCount: LongInt;
|
|||
|
|
|||
|
{ Writes the workbook to data stream in BIFF8 format. }
|
|||
|
|
|||
|
procedure Flush(Stream: TBiffStream); override;
|
|||
|
|
|||
|
{ The folowing properties are multithread safe. }
|
|||
|
|
|||
|
property SheetsCount: LongInt read GetSheetsCount;
|
|||
|
property Sheet[Index: LongInt]: TBiffSheet read GetSheet;
|
|||
|
|
|||
|
property FontsCount: LongInt read GetFontsCount;
|
|||
|
property Font[Index: LongInt]: TBiffFont read GetFont;
|
|||
|
|
|||
|
property Format[i: TBiffFormatIndex]: WideString write SetFormat;
|
|||
|
|
|||
|
property LinkTable: TBiffLinkTable read GetLinkTable;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
|
|||
|
{ MS Excel treats only #10 character as
|
|||
|
a line break, other characters (e.g. #13)
|
|||
|
it displays as squares (unknown symbols).
|
|||
|
|
|||
|
This function deletes #13 chracaters.
|
|||
|
Returns a new length of the string.
|
|||
|
|
|||
|
You must not use this function for an empty strings. }
|
|||
|
|
|||
|
procedure ValidateLineBreaks(var s: WideString);
|
|||
|
|
|||
|
{ Finds the nearest color within an array of colors. }
|
|||
|
|
|||
|
function NearestColor(Colors: Pointer; Count, Color: LongWord): LongWord;
|
|||
|
|
|||
|
implementation
|
|||
|
|
|||
|
uses
|
|||
|
Math, frxClass;
|
|||
|
|
|||
|
{ This global variable is actually a static veriable of TBiffFormulaFuncList.
|
|||
|
Delphi4 doesn't allow writing "class var FuncArray: ..." - that's why this var
|
|||
|
is declared as global. However, when Delphi4 is left behind, this var should be
|
|||
|
moved to the owner class. }
|
|||
|
|
|||
|
var
|
|||
|
FuncArray: TBiffFormulaFuncArray;
|
|||
|
|
|||
|
//
|
|||
|
// The following types describes structure
|
|||
|
// of several specific BIFF records.
|
|||
|
//
|
|||
|
|
|||
|
type
|
|||
|
|
|||
|
//
|
|||
|
// BOF
|
|||
|
//
|
|||
|
|
|||
|
TBiffrBOF = packed record
|
|||
|
|
|||
|
Version: Word;
|
|||
|
Kind: Word;
|
|||
|
Build: Word;
|
|||
|
Year: Word;
|
|||
|
Flags1: LongWord; // See BiffBoff values
|
|||
|
Flags2: LongWord;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// WINDOW1
|
|||
|
// 16 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrWindow = packed record
|
|||
|
|
|||
|
HPos: Word;
|
|||
|
VPos: Word;
|
|||
|
Width: Word;
|
|||
|
Height: Word;
|
|||
|
Flags: Word;
|
|||
|
Active: Word; // Index to active worksheet
|
|||
|
First: Word; // Index of first visible tab
|
|||
|
Sel: Word; // Number of selected worksheets
|
|||
|
TabW: Word; // Width of the tabbar in 1/1000 of window width
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// FONT
|
|||
|
// 14+ bytes
|
|||
|
// See docs for complete information
|
|||
|
//
|
|||
|
|
|||
|
TBiffrFont = packed record
|
|||
|
|
|||
|
Height: Word; // Height in 1/20 point
|
|||
|
Opts: Word; // Option flags, see TBiffFontOptions
|
|||
|
Color: Word; // Color index
|
|||
|
Weight: Word; // Weight in range 100..1000.
|
|||
|
Esc: Word; // Escapement, see TBiffFontEscapement
|
|||
|
Under: Byte; // Underline, see TBiffFontUnderline
|
|||
|
Family: Byte; // Font family, see TBiffFontFamily
|
|||
|
CharSet: Byte;
|
|||
|
NotUsed: Byte;
|
|||
|
|
|||
|
{
|
|||
|
Name: Unicode 16-bit length string
|
|||
|
}
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// XF
|
|||
|
// 20 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrXF = packed record
|
|||
|
|
|||
|
Font: Word;
|
|||
|
Format: Word;
|
|||
|
Style: Word; // For bits 0-2 see TBiffXFTypeProt
|
|||
|
Align: Byte; // See TBiffXFHAlign and TBiffXFVAlign
|
|||
|
Rotation: Byte;
|
|||
|
Indent: Byte;
|
|||
|
UsedAttrs:Byte; // See TBiffXFUsedAttrib
|
|||
|
BStyle1: LongWord; // 0-3 bytes of border style
|
|||
|
BStyle2: LongWord; // 4-7 bytes of border style
|
|||
|
Pattern: Word;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TBiffrBuiltinStyle = packed record
|
|||
|
|
|||
|
XF: Word; // 15-th bit is 1
|
|||
|
Id: Byte; // See TBiffBuiltinStyleId
|
|||
|
Level: Byte;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// SHEET
|
|||
|
// 6+ bytes
|
|||
|
// See docs for complete information
|
|||
|
//
|
|||
|
|
|||
|
TBiffrSheet = packed record
|
|||
|
|
|||
|
Offset: LongWord; // Offset to BOF record
|
|||
|
Vis: Byte; // See TBiffSheetVisibility
|
|||
|
Kind: Byte; // See TBiffSheetKind
|
|||
|
|
|||
|
{
|
|||
|
Name: Unicode 8-bit length string
|
|||
|
}
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// DIMENSION
|
|||
|
// 14 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrDimension = packed record
|
|||
|
|
|||
|
FirstRow: LongWord;
|
|||
|
LastRow: LongWord; // Increased by 1
|
|||
|
FirstCol: Word;
|
|||
|
LastCol: Word; // Increased by 1
|
|||
|
NotUsed: Word;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// WINDOW2
|
|||
|
// 18 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrWindow2 = packed record
|
|||
|
|
|||
|
Options: Word; // See TBiffWindowOptions
|
|||
|
FirstRow: Word; // First visible row
|
|||
|
FirstCol: Word; // First visible column
|
|||
|
GridCol: Word; // Color index of grid lines
|
|||
|
NotUsed1: Word;
|
|||
|
CMFPBV: Word; // Can be 0
|
|||
|
CMFNV: Word; // Can be 0
|
|||
|
NotUsed2: LongWord;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// ROW
|
|||
|
// 16 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrRow = packed record
|
|||
|
|
|||
|
Row: Word;
|
|||
|
FirstCol: Word;
|
|||
|
LastCol: Word; // Increased by 1
|
|||
|
Height: Word; // See docs
|
|||
|
NotUsed: Word;
|
|||
|
NotUsed2: Word;
|
|||
|
Format: LongWord; // See docs
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// SST
|
|||
|
// 8+ bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrSST = packed record
|
|||
|
|
|||
|
WBSCount: LongWord;
|
|||
|
SSTCount: LongWord;
|
|||
|
|
|||
|
{
|
|||
|
UCS16 strings
|
|||
|
}
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// BLANK
|
|||
|
//
|
|||
|
|
|||
|
TBiffrBlank = packed record
|
|||
|
|
|||
|
Row: Word;
|
|||
|
Column: Word;
|
|||
|
XF: Word;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
PBiffrBlank = ^TBiffrBlank;
|
|||
|
|
|||
|
//
|
|||
|
// FORMULA
|
|||
|
// [MS-XLS] 2.4.127
|
|||
|
//
|
|||
|
|
|||
|
TBiffrFormula = packed record
|
|||
|
|
|||
|
Cell: TBiffrBlank;
|
|||
|
Value: Double;
|
|||
|
Flags: Word; // set to 0
|
|||
|
Cache: Cardinal; // set to 0
|
|||
|
InstLen: Word; // size in bytes of formula's program
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// LABELSST
|
|||
|
//
|
|||
|
|
|||
|
TBiffrLabelSST = packed record
|
|||
|
|
|||
|
Row: Word;
|
|||
|
Column: Word;
|
|||
|
XF: Word;
|
|||
|
SSTIndex: LongWord;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
PBiffrLabelSST = ^TBiffrLabelSST;
|
|||
|
|
|||
|
//
|
|||
|
// NUMBER
|
|||
|
//
|
|||
|
|
|||
|
TBiffrNumber = packed record
|
|||
|
|
|||
|
Row: Word;
|
|||
|
Column: Word;
|
|||
|
XF: Word;
|
|||
|
Value: Double; // IEEE 754 floating-point value (64-bit double precision)
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
PBiffrNumber = ^TBiffrNumber;
|
|||
|
|
|||
|
//
|
|||
|
// RK
|
|||
|
//
|
|||
|
|
|||
|
TBiffrRK = packed record
|
|||
|
|
|||
|
Row: Word;
|
|||
|
Column: Word;
|
|||
|
XF: Word;
|
|||
|
RK: LongWord;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
PBiffrRK = ^TBiffrRK;
|
|||
|
|
|||
|
//
|
|||
|
// COLINFO
|
|||
|
// 12 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrColInfo = packed record
|
|||
|
|
|||
|
First: Word;
|
|||
|
Last: Word;
|
|||
|
Width: Word;
|
|||
|
XF: Word;
|
|||
|
Options: Word;
|
|||
|
Reserved: Word;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// Cells block
|
|||
|
// 8 bytes
|
|||
|
// Used by MERGEDCELLS record
|
|||
|
//
|
|||
|
|
|||
|
TBiffrCellsBlock = packed record
|
|||
|
|
|||
|
FR: Word; // Index to first row
|
|||
|
LR: Word; // Index to last row
|
|||
|
FC: Word; // Index to first column
|
|||
|
LC: Word; // Index to last column
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// PAGESETUP
|
|||
|
// 34 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrPageSetup = packed record
|
|||
|
|
|||
|
Size: Word; // See docs for the list of available values
|
|||
|
Scaling: Word; // In percents
|
|||
|
StartNum: Word; // Start page number
|
|||
|
Width: Word; // 0 = use default
|
|||
|
Height: Word; // 0 = use default
|
|||
|
Options: Word;
|
|||
|
Xdpi: Word;
|
|||
|
Ydpi: Word;
|
|||
|
Header: Double; // Header margin in inches
|
|||
|
Footer: Double; // Footer margin in inches
|
|||
|
Copies: Word; // Number of copies to print
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
TPackedRect = packed record
|
|||
|
|
|||
|
Left: LongInt;
|
|||
|
Top: LongInt;
|
|||
|
Right: LongInt;
|
|||
|
Bottom: LongInt;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// Common Object Record
|
|||
|
// 22 bytes
|
|||
|
//
|
|||
|
|
|||
|
TBiffrCOR = packed record
|
|||
|
|
|||
|
Kind: Word; // See BiffGo values
|
|||
|
Id: Word; // Object Id
|
|||
|
Opts: Word;
|
|||
|
Unused1: array[1..16] of Byte;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{ RC4 encryption header
|
|||
|
52 bytes }
|
|||
|
|
|||
|
TBiffrRC4 = packed record
|
|||
|
|
|||
|
Version: LongWord; // Must be $00010001
|
|||
|
Salt: array[1..16] of Byte;
|
|||
|
Verifier: array[1..16] of Byte;
|
|||
|
Hash: array[1..16] of Byte;
|
|||
|
|
|||
|
end;
|
|||
|
|
|||
|
{$IFDEF DELPHI16}
|
|||
|
frxCardinal = UInt64;
|
|||
|
{$ELSE}
|
|||
|
frxCardinal = {$IFDEF FPC}Cardinal{$ELSE}Cardinal{$ENDIF};
|
|||
|
{$ENDIF}
|
|||
|
|
|||
|
const
|
|||
|
|
|||
|
//
|
|||
|
// Graphics object types
|
|||
|
//
|
|||
|
|
|||
|
BiffGoPicture = 8;
|
|||
|
|
|||
|
//
|
|||
|
// The following constants are BIFF record types,
|
|||
|
// i.e. TBiffRecord.Id value.
|
|||
|
//
|
|||
|
|
|||
|
BiffIdFormula = $0006; // FORMULA
|
|||
|
BiffIdRString = $00d6; // RSTRING
|
|||
|
BiffIdRK = $027E; // RK
|
|||
|
BiffIdNumber = $0203; // NUMBER
|
|||
|
BiffIdMulRK = $00Bd; // MULRK
|
|||
|
BiffIdMulBlank = $00BE; // MULBLANK
|
|||
|
BiffIdLabelSST = $00Fd; // LABELSST
|
|||
|
BiffIdBOF = $0809; // BOF
|
|||
|
BiffIdWindow = $003d; // WINDOW1
|
|||
|
BiffIdFont = $0031; // FONT
|
|||
|
BiffIdXF = $00E0; // XF
|
|||
|
BiffIdStyle = $0293; // STYLE
|
|||
|
BiffIdSheet = $0085; // SHEET
|
|||
|
BiffIdEOF = $000A; // EOF
|
|||
|
BiffIdDim = $0200; // DIMENSION
|
|||
|
BiffIdWindow2 = $023E; // WINDOW2
|
|||
|
BiffIdRow = $0208; // ROW
|
|||
|
BiffIdDBCell = $00d7; // DBCELL
|
|||
|
BiffIdContinue = $003C; // CONTINUE
|
|||
|
BiffIdBlank = $0201; // BLANK
|
|||
|
BiffIdBoolErr = $0205; // BOOLERR
|
|||
|
BiffIdLabel = $0204; // LABEL
|
|||
|
BiffIdSST = $00FC; // SST
|
|||
|
BiffIdPalette = $0092; // PALETTE
|
|||
|
BiffIdColInfo = $007d; // COLINFO
|
|||
|
BiffIdMergedCells = $00E5; // MERGEDCELLS
|
|||
|
BiffIdLeftMargin = $0026; // LEFTMARGIN
|
|||
|
BiffIdRightMargin = $0027; // RIGHTMARGIN
|
|||
|
BiffIdTopMargin = $0028; // TOPMARGIN
|
|||
|
BiffIdBottomMargin = $0029; // BOTTOMMARGIN
|
|||
|
BiffIdPageSetup = $00a1; // PAGESETUP
|
|||
|
BiffIdFormat = $041e; // FORMAT
|
|||
|
BiffIdEscher = $00eb; // ESCHER
|
|||
|
BiffIdDrawing = $00ec; // DRAWING
|
|||
|
BiffIdGObj = $005d; // GOBJ
|
|||
|
BiffIdCOR = $0015; // Common Object Record
|
|||
|
BiffIdHorPageBreak = $001b; // HORIZONTALPAGEBREAK
|
|||
|
BiffIdFilePass = $002f; // FILEPASS
|
|||
|
BiffIdUserExcl = $0194; // USEREXCL
|
|||
|
BiffIdFileLock = $0195; // FILELOCK
|
|||
|
BiffIdInterfaceHdr = $00e1; // INTERFACEHDR
|
|||
|
BiffIdRRDInfo = $0196; // RRDINFO
|
|||
|
BiffIdRRDHead = $0138; // RRDHEAD
|
|||
|
BiffIdInterfaceEnd = $00e2; // INTERFACEEND
|
|||
|
BiffIdMms = $00c1; // MMS
|
|||
|
BiffIdWriteAccess = $005c; // WRITEACCESS
|
|||
|
BiffIdCodepage = $0042; // CODEPAGE
|
|||
|
BiffIdDsf = $0161; // DSF
|
|||
|
BiffIdRrd = $013d; // RRD
|
|||
|
BiffIdWinProt = $0019; // WINPROT
|
|||
|
BiffIdProt = $0012; // PROTECT
|
|||
|
BiffIdPassword = $0013; // PASSWORD
|
|||
|
BiffIdProtRev = $01af; // PROTREV
|
|||
|
BiffIdProtRevPass = $01bc; // PROTREVPASS
|
|||
|
BiffIdBackup = $0040; // BACKUP
|
|||
|
BiffIdHideObj = $008d; // HIDEOBJ
|
|||
|
BiffIdDate1904 = $0022; // DATE1904
|
|||
|
BiffIdCalcPrec = $000e; // CALCPRECISION
|
|||
|
BiffIdRefreshAll = $01b7; // REFRESHALL
|
|||
|
BiffIdBookBool = $00da; // BOOKBOOL
|
|||
|
BiffIdUserElf = $0160; // USERELF
|
|||
|
BiffIdCountry = $008c; // COUNTRY
|
|||
|
BiffIdExtSst = $00ff; // EXTSST
|
|||
|
BiffIdCalcMode = $000d; // CALCMODE
|
|||
|
BiffIdCalcCount = $000c; // CALCCOUNT
|
|||
|
BiffIdCalcRefMode = $000f; // CALCREFMODE
|
|||
|
BiffIdCalcIter = $0011; // CALCITER
|
|||
|
BiffIdCalcDelta = $0010; // CALCDELTA
|
|||
|
BiffIdSaveRecalc = $005f; // SAVERECALC
|
|||
|
BiffIdPrintRowCol = $002a; // PRINTROWCOL
|
|||
|
BiffIdPrintGrid = $002b; // PRINTGRID
|
|||
|
BiffIdGridSet = $0082; // GRIDSET
|
|||
|
BiffIdGuts = $0080; // GUTS
|
|||
|
BiffIdDefRowHeight = $0225; // DEFROWHEIGHT
|
|||
|
BiffIdWsBool = $0081; // WSBOOL
|
|||
|
BiffIdHeader = $0014; // HEADER
|
|||
|
BiffIdFooter = $0015; // FOOTER
|
|||
|
BiffIdHCenter = $0083; // HCENTER
|
|||
|
BiffIdVCenter = $0084; // VCENTER
|
|||
|
BiffIdDefColWidth = $0055; // DEFCOLWIDTH
|
|||
|
BiffIdExtBook = $01ae; // EXTERNALBOOK
|
|||
|
BiffIdExtSheet = $0017; // EXTERNALSHEET
|
|||
|
|
|||
|
function NearestColor(Colors: Pointer; Count, Color: LongWord): LongWord;
|
|||
|
|
|||
|
function Dist(i, c: LongWord): LongWord;
|
|||
|
var
|
|||
|
j: Integer;
|
|||
|
begin
|
|||
|
c := c xor PLongWord(LongWord(Colors) + i*SizeOf(Color))^;
|
|||
|
Result := 0;
|
|||
|
|
|||
|
for j := 1 to 4 do
|
|||
|
begin
|
|||
|
Inc(Result, c and $ff);
|
|||
|
c := c shr 8;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
Result := 0;
|
|||
|
|
|||
|
for i := 0 to Count - 1 do
|
|||
|
if Dist(i, Color) < Dist(Result, Color) then
|
|||
|
Result := i;
|
|||
|
end;
|
|||
|
|
|||
|
{ Sets or resets the N-th bit of a variable }
|
|||
|
|
|||
|
procedure SetBit(P: Pointer; N: Cardinal; V: Boolean);
|
|||
|
var
|
|||
|
B: PByte;
|
|||
|
M: Byte;
|
|||
|
begin
|
|||
|
B := PByte(frxCardinal(P) + N div 8);
|
|||
|
M := 1 shl (N mod 8);
|
|||
|
|
|||
|
if V then
|
|||
|
B^ := B^ or M
|
|||
|
else
|
|||
|
B^ := B^ and not M
|
|||
|
end;
|
|||
|
|
|||
|
procedure ValidateLineBreaks(var s: WideString);
|
|||
|
var
|
|||
|
i, j: Cardinal;
|
|||
|
begin
|
|||
|
j := 1;
|
|||
|
|
|||
|
for i := 1 to Length(s) do
|
|||
|
if s[i] <> #13 then
|
|||
|
begin
|
|||
|
s[j] := s[i];
|
|||
|
Inc(j);
|
|||
|
end;
|
|||
|
|
|||
|
SetLength(s, j - 1);
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffObject }
|
|||
|
|
|||
|
class procedure TBiffObject.FlushList(list: TObjList; Stream: TBiffStream);
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
for i := 0 to list.Count - 1 do
|
|||
|
TBiffObject(list[i]).Flush(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffObject.GetHashCode: LongInt;
|
|||
|
begin
|
|||
|
Result := 0;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffObject.Equals(s: TBiffObject): Boolean;
|
|||
|
begin
|
|||
|
Result := Self = s;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffRC4 }
|
|||
|
|
|||
|
constructor TBiffRC4.Create;
|
|||
|
var
|
|||
|
r: array[1..4] of LongWord;
|
|||
|
i: LongInt;
|
|||
|
g: TCryptoCMWC;
|
|||
|
begin
|
|||
|
FCipher := TCryptoRC4.Create;
|
|||
|
g := TCryptoCMWC.Create;
|
|||
|
|
|||
|
for i := 1 to 4 do
|
|||
|
r[i] := g.Next;
|
|||
|
|
|||
|
Move(r, FSalt, 16);
|
|||
|
|
|||
|
for i := 1 to 4 do
|
|||
|
r[i] := g.Next;
|
|||
|
|
|||
|
Move(r, FVerifier, 16);
|
|||
|
|
|||
|
g.Free;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffRC4.Destroy;
|
|||
|
begin
|
|||
|
FCipher.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.MD5(out Hash; const Data; Size: Integer);
|
|||
|
begin
|
|||
|
TCryptoHash.Hash('MD5', Hash, 16, Data, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.Prepare(const Password; PassLen: Integer);
|
|||
|
var
|
|||
|
h: array[1..16] of Byte;
|
|||
|
b: array[1..336] of Byte;
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
MD5(h, Password, PassLen);
|
|||
|
|
|||
|
Move(h, b, 5);
|
|||
|
Move(FSalt, b[6], 16);
|
|||
|
|
|||
|
for i := 1 to 15 do
|
|||
|
Move(b, b[1 + 21 * i], 21);
|
|||
|
|
|||
|
MD5(h, b, 336);
|
|||
|
Move(h, FTruncHash, 5);
|
|||
|
|
|||
|
Initialize(0);
|
|||
|
MD5(FHash, FVerifier, 16);
|
|||
|
Encrypt(@FVerifier, 16);
|
|||
|
Encrypt(@FHash, 16);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.Init(Key: Pointer; Len: LongInt);
|
|||
|
begin
|
|||
|
FCipher.Init(Key^, Len);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.Initialize(BlockId: LongWord);
|
|||
|
var
|
|||
|
h: array[1..9] of Byte;
|
|||
|
k: array[1..16] of Byte;
|
|||
|
begin
|
|||
|
Move(FTruncHash, h, 5);
|
|||
|
Move(BlockId, h[6], 4);
|
|||
|
MD5(k, h, 9);
|
|||
|
Init(@k, 16);
|
|||
|
|
|||
|
FTail := 1024;
|
|||
|
FBlockId := BlockId;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.Encrypt(Data: Pointer; Size: LongInt); cdecl;
|
|||
|
begin
|
|||
|
FCipher.Encrypt(Data, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.EncryptEx(Data: Pointer; Size: LongInt);
|
|||
|
var
|
|||
|
n: LongInt;
|
|||
|
begin
|
|||
|
{ If the data block fits to the current 1024-bytes block,
|
|||
|
encrypt the data and exit. }
|
|||
|
|
|||
|
if Size <= FTail then
|
|||
|
begin
|
|||
|
Encrypt(Data, Size);
|
|||
|
Dec(FTail, Size);
|
|||
|
if FTail = 0 then
|
|||
|
Initialize(FBlockId + 1);
|
|||
|
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
{ If the data block does not fit the current 1024-bytes block,
|
|||
|
it must be encrypted by parts. Firstly, encrypt a part of
|
|||
|
the data that fits the current 1024-bytes block. }
|
|||
|
|
|||
|
Encrypt(Data, FTail);
|
|||
|
if Data <> nil then Data := Pointer(frxInteger(Data) + FTail);
|
|||
|
Dec(Size, FTail);
|
|||
|
Initialize(FBlockId + 1);
|
|||
|
|
|||
|
{ Split the data into 1024-bytes blocks and encrypt them
|
|||
|
consequently. At the beginning of each 1024-bytes block,
|
|||
|
the RC4 cipher must be reinitialized using the index to
|
|||
|
the current 1024-bytes block. }
|
|||
|
|
|||
|
while Size > 0 do
|
|||
|
begin
|
|||
|
n := Size;
|
|||
|
if n > FTail then n := FTail;
|
|||
|
Encrypt(Data, n);
|
|||
|
Dec(Size, n);
|
|||
|
Dec(FTail, n);
|
|||
|
|
|||
|
if Data <> nil then
|
|||
|
Data := Pointer(frxInteger(Data) + n);
|
|||
|
|
|||
|
if FTail = 0 then
|
|||
|
Initialize(FBlockId + 1);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.EncryptStream(Stream: TBiffStream);
|
|||
|
|
|||
|
function GetSkipSize(RecId: TBiffRecId; RecSize: Cardinal): Cardinal;
|
|||
|
begin
|
|||
|
case RecId of
|
|||
|
BiffIdFilePass,
|
|||
|
BiffIdUserExcl,
|
|||
|
BiffIdFileLock,
|
|||
|
BiffIdInterfaceHdr,
|
|||
|
BiffIdRRDInfo,
|
|||
|
BiffIdRRDHead,
|
|||
|
BiffIdBOF:
|
|||
|
Result := RecSize;
|
|||
|
|
|||
|
BiffIdSheet:
|
|||
|
Result := 4;
|
|||
|
|
|||
|
else
|
|||
|
Result := 0;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
n: LongWord;
|
|||
|
RecData: TMemoryStream;
|
|||
|
begin
|
|||
|
Initialize(0);
|
|||
|
RecData := TMemoryStream.Create;
|
|||
|
|
|||
|
try
|
|||
|
for i := 0 to Stream.Count - 1 do
|
|||
|
with Stream[i] do
|
|||
|
begin
|
|||
|
n := GetSkipSize(Id, Size);
|
|||
|
|
|||
|
RecData.Position := 0;
|
|||
|
SaveToStream(RecData);
|
|||
|
|
|||
|
EncryptEx(nil, 4 + n); // 4 byte header and a few first bytes are not encrypted
|
|||
|
EncryptEx(Pointer(frxCardinal(RecData.Memory) + n), Size - n);
|
|||
|
|
|||
|
RecData.Position := 0;
|
|||
|
LoadFromStream(RecData);
|
|||
|
end;
|
|||
|
finally
|
|||
|
RecData.Free;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRC4.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
r: TBiffrRC4;
|
|||
|
begin
|
|||
|
ZeroMemory(@r, SizeOf(r));
|
|||
|
|
|||
|
r.Version := $00010001;
|
|||
|
Move(FSalt, r.Salt, 16);
|
|||
|
Move(FVerifier, r.Verifier, 16);
|
|||
|
Move(FHash, r.Hash, 16);
|
|||
|
|
|||
|
with Stream.Add(BiffIdFilePass) do
|
|||
|
begin
|
|||
|
WriteConst(1, 2);
|
|||
|
Write(r, SizeOf(r));
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffWindow2 }
|
|||
|
|
|||
|
constructor TBiffWindow2.Create;
|
|||
|
begin
|
|||
|
Options := BiffWoGridLines or BiffWoHeaders or BiffWoZeros or
|
|||
|
BiffWoAutoGridColor or BiffWoOutline or {BiffWoSelected or}
|
|||
|
BiffWoActive;
|
|||
|
|
|||
|
{ There are only 64 colors in the palette.
|
|||
|
The following assignment emphasizes the fact
|
|||
|
that the grid color is selected automatically }
|
|||
|
|
|||
|
GridCol := 64;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWindow2.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
r: TBiffrWindow2;
|
|||
|
begin
|
|||
|
ZeroMemory(@r, SizeOf(r));
|
|||
|
|
|||
|
r.Options := Options;
|
|||
|
r.FirstRow := FirstRow;
|
|||
|
r.FirstCol := FirstCol;
|
|||
|
r.GridCol := GridCol;
|
|||
|
r.CMFPBV := MFPBP;
|
|||
|
r.CMFNV := CMFNV;
|
|||
|
|
|||
|
Stream.Add(BiffIdWindow2).Write(r, SizeOf(r));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffPageSetup }
|
|||
|
|
|||
|
constructor TBiffPageSetup.Create;
|
|||
|
begin
|
|||
|
Size := BiffPsA4;
|
|||
|
Orient := bpoPortrait;
|
|||
|
Colored := True;
|
|||
|
Xdpi := 300;
|
|||
|
Ydpi := 300;
|
|||
|
Copies := 1;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffPageSetup.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
r: TBiffrPageSetup;
|
|||
|
o: PWord;
|
|||
|
begin
|
|||
|
ZeroMemory(@r, SizeOf(r));
|
|||
|
|
|||
|
r.Scaling := 100;
|
|||
|
r.Size := Size;
|
|||
|
r.StartNum := 0;
|
|||
|
r.Width := 1;
|
|||
|
r.Height := 1;
|
|||
|
r.Xdpi := Xdpi;
|
|||
|
r.Ydpi := Ydpi;
|
|||
|
r.Header := 0.0;
|
|||
|
r.Footer := 0.0;
|
|||
|
r.Copies := Word(Copies);
|
|||
|
r.Options := 0;
|
|||
|
|
|||
|
o := @r.Options;
|
|||
|
|
|||
|
SetBit(o, 0, PrintInRows);
|
|||
|
SetBit(o, 1, Orient = bpoPortrait);
|
|||
|
SetBit(o, 3, not Colored);
|
|||
|
SetBit(o, 4, Draft);
|
|||
|
SetBit(o, 5, CellNotes);
|
|||
|
SetBit(o, 7, True);
|
|||
|
SetBit(o, 9, SheetNotes);
|
|||
|
SetBit(o, 10, True); // Do not print errors
|
|||
|
|
|||
|
Stream.Add(BiffIdPageSetup).Write(r, SizeOf(r));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffNumberCell }
|
|||
|
|
|||
|
procedure TBiffNumberCell.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
r: TBiffrNumber;
|
|||
|
begin
|
|||
|
ZeroMemory(@r, SizeOf(r));
|
|||
|
|
|||
|
r.Row := Row;
|
|||
|
r.Column := Col;
|
|||
|
r.XF := XF;
|
|||
|
r.Value := Value;
|
|||
|
|
|||
|
Stream.Add(BiffIdNumber).Write(r, SizeOf(r));
|
|||
|
end;
|
|||
|
|
|||
|
constructor TBiffNumberCell.Create(Value: Double);
|
|||
|
begin
|
|||
|
Self.Value := Value;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffStyle }
|
|||
|
|
|||
|
constructor TBiffStyle.Create;
|
|||
|
begin
|
|||
|
Level := -1;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStyle.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
bs: TBiffrBuiltinStyle;
|
|||
|
ucs: TBiffUCS;
|
|||
|
begin
|
|||
|
ZeroMemory(@bs, SizeOf(bs));
|
|||
|
|
|||
|
//
|
|||
|
// Built-in style
|
|||
|
//
|
|||
|
|
|||
|
if Name = '' then
|
|||
|
begin
|
|||
|
bs.XF := XF or $8000;
|
|||
|
bs.Id := StyleId;
|
|||
|
bs.Level := Byte(Level);
|
|||
|
|
|||
|
Stream.Add(BiffIdStyle).Write(bs, SizeOf(bs));
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// User defined style
|
|||
|
//
|
|||
|
|
|||
|
Stream.Add(BiffIdStyle).Write(XF, 2);
|
|||
|
|
|||
|
ucs := TBiffucs.Create(Name, True);
|
|||
|
ucs.Flush(Stream);
|
|||
|
ucs.Free;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffColInfo }
|
|||
|
|
|||
|
constructor TBiffColInfo.Create(Column, XF, Width: LongInt);
|
|||
|
begin
|
|||
|
First := Column;
|
|||
|
Last := Column;
|
|||
|
Self.XF := XF;
|
|||
|
Self.Width:= Width;
|
|||
|
Hidden := False;
|
|||
|
Collapsed := False;
|
|||
|
Outline := 0;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffColInfo.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
R: TBiffrColInfo;
|
|||
|
begin
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.First := First;
|
|||
|
R.Last := Last;
|
|||
|
R.Width := Width;
|
|||
|
R.XF := XF;
|
|||
|
R.Options := 0;
|
|||
|
|
|||
|
SetBit(@R.Options, 0, Hidden);
|
|||
|
SetBit(@R.Options, 12, Collapsed);
|
|||
|
R.Options := R.Options and ((7 and Outline) shl 8);
|
|||
|
|
|||
|
Stream.Add(BiffIdColInfo).Write(R, SizeOf(R));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffWindow }
|
|||
|
|
|||
|
constructor TBiffWindow.Create;
|
|||
|
begin
|
|||
|
HPos := 0;
|
|||
|
VPos := 0;
|
|||
|
Width := 15600;
|
|||
|
Height := 8190;
|
|||
|
ActiveSheet := 0;
|
|||
|
FirstTab := 0;
|
|||
|
SelSheet := 1;
|
|||
|
TabWidth := 400;
|
|||
|
|
|||
|
Visible := True;
|
|||
|
Open := True;
|
|||
|
HSBVisible := True;
|
|||
|
VSBVisible := True;
|
|||
|
TabVisible := True;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWindow.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
R: TBiffrWindow;
|
|||
|
F: Word;
|
|||
|
begin
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.HPos := HPos;
|
|||
|
R.VPos := VPos;
|
|||
|
R.Width := Width;
|
|||
|
R.Height := Height;
|
|||
|
R.Active := ActiveSheet;
|
|||
|
R.First := FirstTab;
|
|||
|
R.Sel := SelSheet;
|
|||
|
R.TabW := TabWidth;
|
|||
|
|
|||
|
F := 0;
|
|||
|
|
|||
|
SetBit(@F, 0, not Visible);
|
|||
|
SetBit(@F, 1, not Open);
|
|||
|
SetBit(@F, 3, HSBVisible);
|
|||
|
SetBit(@F, 4, VSBVisible);
|
|||
|
SetBit(@F, 5, TabVisible);
|
|||
|
|
|||
|
F := 56;
|
|||
|
R.Flags := F;
|
|||
|
Stream.Add(BiffIdWindow).Write(R, SizeOf(R));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffCell }
|
|||
|
|
|||
|
constructor TBiffCell.Create;
|
|||
|
begin
|
|||
|
XF := 15; // default XF for cells
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffCell.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
R: TBiffrBlank;
|
|||
|
begin
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.Row := Row;
|
|||
|
R.Column := Col;
|
|||
|
R.XF := XF;
|
|||
|
|
|||
|
with Stream.Add(BiffIdBlank) do
|
|||
|
Write(R, SizeOf(R));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffTextCell }
|
|||
|
|
|||
|
constructor TBiffTextCell.Create(SST: LongInt);
|
|||
|
begin
|
|||
|
inherited Create;
|
|||
|
Self.SST := SST;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffTextCell.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
R: TBiffrLabelSST;
|
|||
|
begin
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.Row := Row;
|
|||
|
R.Column := Col;
|
|||
|
R.XF := XF;
|
|||
|
R.SSTIndex := SST;
|
|||
|
|
|||
|
Stream.Add(BiffIdLabelSST).Write(R, SizeOf(R));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFont }
|
|||
|
|
|||
|
constructor TBiffFont.Create;
|
|||
|
begin
|
|||
|
with Data do
|
|||
|
begin
|
|||
|
Weight := Word(fwNormal);
|
|||
|
Name := 'Arial';
|
|||
|
Color := Word(ciBlack);
|
|||
|
Height := 12*20;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFont.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
R: TBiffrFont;
|
|||
|
begin
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
with Data do
|
|||
|
begin
|
|||
|
{ Font height must be zero or in bounds [20, 8191].
|
|||
|
Required by [MS-XLS] Section 2.4.122. }
|
|||
|
|
|||
|
if Height < 20 then
|
|||
|
R.Height := 0
|
|||
|
else if Height > 8191 then
|
|||
|
R.Height := 8191
|
|||
|
else
|
|||
|
R.Height := Height;
|
|||
|
|
|||
|
R.Opts := Options;
|
|||
|
R.Color := Color;
|
|||
|
R.Weight := Weight;
|
|||
|
R.Esc := Word(Esc);
|
|||
|
R.Under := Byte(Underline);
|
|||
|
R.Family := Byte(Family);
|
|||
|
R.CharSet := Charset;
|
|||
|
end;
|
|||
|
|
|||
|
Stream.Add(BiffIdFont).Write(R, SizeOf(R));
|
|||
|
|
|||
|
with TBiffUCS.Create(Name, False) do
|
|||
|
try
|
|||
|
Compress := False;
|
|||
|
Flush(Stream);
|
|||
|
finally
|
|||
|
Free;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFont.Equals(Font: TBiffObject): Boolean;
|
|||
|
begin
|
|||
|
Result := GetHashCode = Font.GetHashCode;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFont.GetHashCode: Integer;
|
|||
|
var
|
|||
|
H: TCryptoHash;
|
|||
|
begin
|
|||
|
if Hash = 0 then
|
|||
|
begin
|
|||
|
H := TCryptoJenkins.Create;
|
|||
|
|
|||
|
try
|
|||
|
H.Push(Data, SizeOf(Data));
|
|||
|
|
|||
|
if Name <> '' then
|
|||
|
H.Push(Name[1], SizeOf(Name[1]) * Length(Name));
|
|||
|
|
|||
|
H.GetDigest(Hash, SizeOf(Hash));
|
|||
|
finally
|
|||
|
H.Free;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
Result := Hash;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFont.StrWidth(const Str: WideString): LongInt;
|
|||
|
var
|
|||
|
dc: HDC; // HDC
|
|||
|
font: HFONT; // HFONT
|
|||
|
fam: LongWord; // Font family
|
|||
|
sz: SIZE; // String size
|
|||
|
begin
|
|||
|
with Data do
|
|||
|
begin
|
|||
|
Result := 0;
|
|||
|
|
|||
|
dc := CreateDC('DISPLAY', nil, nil, nil);
|
|||
|
if dc = 0 then
|
|||
|
Exit;
|
|||
|
|
|||
|
case Family of
|
|||
|
ffRoman: fam := FF_ROMAN;
|
|||
|
ffSwiss: fam := FF_SWISS;
|
|||
|
ffModern: fam := FF_MODERN;
|
|||
|
ffScript: fam := FF_SCRIPT;
|
|||
|
ffDecorative: fam := FF_DECORATIVE;
|
|||
|
else fam := FF_DONTCARE;
|
|||
|
end;
|
|||
|
|
|||
|
font := CreateFontW(
|
|||
|
Height div 10, // Height in logical units
|
|||
|
0, // Width in logical units
|
|||
|
0, // Escapement in 1/10 of a degree
|
|||
|
0, // Orientation in 1/10 of a degree
|
|||
|
Weight, // Font weight
|
|||
|
|
|||
|
//
|
|||
|
// Flags: italic, underline, struck out
|
|||
|
//
|
|||
|
|
|||
|
Word(foItalic) and Options,
|
|||
|
LongWord(Underline <> fuNone),
|
|||
|
Word(foStruckOut) and Options,
|
|||
|
|
|||
|
Charset, // Font charset
|
|||
|
OUT_DEFAULT_PRECIS, // Output precision
|
|||
|
CLIP_DEFAULT_PRECIS, // Clip precision
|
|||
|
DEFAULT_QUALITY, // Output quality
|
|||
|
fam, // Font family
|
|||
|
PWideChar(Name)); // Font name
|
|||
|
|
|||
|
if font = 0 then
|
|||
|
begin
|
|||
|
DeleteDC(dc);
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
SelectObject(dc, font);
|
|||
|
|
|||
|
if GetTextExtentPoint32W(dc, PWideChar(Str), Length(Str), sz) then
|
|||
|
Result := sz.cx;
|
|||
|
|
|||
|
DeleteObject(font);
|
|||
|
DeleteDC(dc);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffXF }
|
|||
|
|
|||
|
constructor TBiffXF.Create;
|
|||
|
begin
|
|||
|
Data.Parent := $FFF;
|
|||
|
Data.HAlign := xfhaLeft;
|
|||
|
Data.VAlign := xfvaTop;
|
|||
|
Data.WordWrap := True;
|
|||
|
Data.Prot := Byte(xftpCellLocked);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffXF.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
X: TBiffrXF;
|
|||
|
begin
|
|||
|
ZeroMemory(@X, SizeOf(X));
|
|||
|
|
|||
|
with Data do
|
|||
|
begin
|
|||
|
X.Font := Font;
|
|||
|
X.Format := Format;
|
|||
|
X.Style := Prot and 7 or (Parent shl 4);
|
|||
|
X.Rotation := Rotation;
|
|||
|
X.UsedAttrs := Byte(UsedAttrs shl 2);
|
|||
|
|
|||
|
if (Prot and xftpStyle) <> 0 then
|
|||
|
X.UsedAttrs := not X.UsedAttrs;
|
|||
|
|
|||
|
X.Align :=
|
|||
|
$03 and Byte(HAlign) or
|
|||
|
$08 and (Byte(WordWrap) shl 3) or
|
|||
|
$70 and (Byte(VAlign) shl 4) or
|
|||
|
$80 and (Byte(Justify) shl 7);
|
|||
|
|
|||
|
X.Indent :=
|
|||
|
$0f and Indent or
|
|||
|
$10 and (Byte(Shrink) shl 4) or
|
|||
|
$c0 and (Byte(Direction) shl 6);
|
|||
|
|
|||
|
X.BStyle1 :=
|
|||
|
$0000000f and LongWord(L.Style) or
|
|||
|
$000000f0 and (LongWord(R.Style) shl 4) or
|
|||
|
$00000f00 and (LongWord(T.Style) shl 8) or
|
|||
|
$0000f000 and (LongWord(B.Style) shl 12) or
|
|||
|
$007f0000 and (LongWord(L.Color) shl 16) or
|
|||
|
$3f800000 and (LongWord(R.Color) shl 23) or
|
|||
|
$40000000 and (LongWord(LTRB) shl 30) or
|
|||
|
$80000000 and (LongWord(LBRT) shl 31);
|
|||
|
|
|||
|
X.BStyle2 :=
|
|||
|
$0000007f and LongWord(T.Color) or
|
|||
|
$00003f80 and (LongWord(B.Color) shl 7) or
|
|||
|
$001fc000 and (LongWord(D.Color) shl 14) or
|
|||
|
$01e00000 and (LongWord(D.Style) shl 21) or
|
|||
|
$fc000000 and (LongWord(Patt) shl 26);
|
|||
|
|
|||
|
X.Pattern :=
|
|||
|
$007f and Word(PattColor) or
|
|||
|
$3f80 and (Word(PattBgColor) shl 7);
|
|||
|
end;
|
|||
|
|
|||
|
Stream.Add(BiffIdXF).Write(X, SizeOf(X));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffXF.Equals(XF: TBiffObject): Boolean;
|
|||
|
begin
|
|||
|
Result := GetHashCode = XF.GetHashCode;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffXF.GetHashCode: Integer;
|
|||
|
begin
|
|||
|
if Hash = 0 then
|
|||
|
TCryptoHash.Hash('Jenkins', Hash, SizeOf(Hash), Data, SizeOf(Data));
|
|||
|
|
|||
|
Result := Hash;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffRow }
|
|||
|
|
|||
|
constructor TBiffRow.Create;
|
|||
|
begin
|
|||
|
Cells := TObjList.Create;
|
|||
|
XF := 15;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffRow.Destroy;
|
|||
|
begin
|
|||
|
Cells.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRow.Flush(Stream: TBiffStream);
|
|||
|
|
|||
|
function GetHeight: Word;
|
|||
|
begin
|
|||
|
Result := $8000;
|
|||
|
if Height > 0 then Result := Height and $7FFF;
|
|||
|
end;
|
|||
|
|
|||
|
function GetFormat: LongWord;
|
|||
|
var
|
|||
|
f: LongWord;
|
|||
|
begin
|
|||
|
f :=
|
|||
|
$00000007 and Outline or
|
|||
|
$0FFF0000 and (XF shl 16);
|
|||
|
|
|||
|
SetBit(@f, 5, Hidden);
|
|||
|
SetBit(@f, 6, Height > 0);
|
|||
|
//SetBit(@f, 7, XF >= 0);
|
|||
|
SetBit(@f, 8, True);
|
|||
|
|
|||
|
Result := f;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
Rec: TBiffrRow;
|
|||
|
begin
|
|||
|
ZeroMemory(@Rec, SizeOf(Rec));
|
|||
|
|
|||
|
Rec.Row := Row;
|
|||
|
Rec.FirstCol := FirstCol;
|
|||
|
Rec.LastCol := LastCol + 1;
|
|||
|
Rec.Height := GetHeight;
|
|||
|
Rec.Format := GetFormat;
|
|||
|
|
|||
|
Stream.Add(BiffIdRow).Write(Rec, SizeOf(Rec));
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffWorkbook }
|
|||
|
|
|||
|
constructor TBiffWorkbook.Create;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
x: TBiffXF;
|
|||
|
sc: Integer;
|
|||
|
begin
|
|||
|
Sheets := TObjList.Create;
|
|||
|
Fonts := TObjList.Create;
|
|||
|
XFs := TObjList.Create;
|
|||
|
SST := TObjList.Create;
|
|||
|
Styles := TObjList.Create;
|
|||
|
FFormats := TObjList.Create;
|
|||
|
Win := TBiffWindow.Create;
|
|||
|
Escher := TEscherStorage.Create;
|
|||
|
|
|||
|
{ Hashes }
|
|||
|
|
|||
|
XFHash := TListHashTable.Create;
|
|||
|
SSTHash := TListHashTable.Create;
|
|||
|
FontHash := TListHashTable.Create;
|
|||
|
|
|||
|
{ Critical section objects }
|
|||
|
|
|||
|
sc := 4000;
|
|||
|
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsStrings, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsFonts, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsXFs, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsSheets, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsStyles, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsColors, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsPictures, sc);
|
|||
|
InitializeCriticalSectionAndSpinCount(FCsFormats, sc);
|
|||
|
|
|||
|
{ 8 built-in colors }
|
|||
|
|
|||
|
PalUsed := 0;
|
|||
|
|
|||
|
AddColor($000000); // black
|
|||
|
AddColor($FFFFFF); // white
|
|||
|
AddColor($0000FF); // red
|
|||
|
AddColor($00FF00); // green
|
|||
|
AddColor($FF0000); // blue
|
|||
|
AddColor($00FFFF); // yellow
|
|||
|
AddColor($FF00FF); // magenta
|
|||
|
AddColor($FFFF00); // cyan
|
|||
|
|
|||
|
{ 16 built-in XFs }
|
|||
|
|
|||
|
for i := 0 to 15 do
|
|||
|
begin
|
|||
|
x := TBiffXF.Create;
|
|||
|
|
|||
|
with x.Data do
|
|||
|
begin
|
|||
|
Prot := Prot or Byte(xftpStyle);
|
|||
|
UsedAttrs := UsedAttrs or Byte(BiffXfuaFont);
|
|||
|
end;
|
|||
|
|
|||
|
XFs.Add(x);
|
|||
|
end;
|
|||
|
|
|||
|
with TBiffXF(XFs[0]).Data do
|
|||
|
begin
|
|||
|
UsedAttrs := $ff;
|
|||
|
end;
|
|||
|
|
|||
|
with TBiffXF(XFs[15]).Data do
|
|||
|
begin
|
|||
|
Parent := 0;
|
|||
|
Prot := Byte(xftpCellLocked);
|
|||
|
UsedAttrs := 0;
|
|||
|
end;
|
|||
|
|
|||
|
{ 1 built-in style for 0-th XF }
|
|||
|
|
|||
|
AddStyle(TBiffStyle.Create);
|
|||
|
|
|||
|
{ 4 built-in fonts }
|
|||
|
|
|||
|
Fonts.Add(TBiffFont.Create);
|
|||
|
Fonts.Add(TBiffFont.Create);
|
|||
|
Fonts.Add(TBiffFont.Create);
|
|||
|
Fonts.Add(TBiffFont.Create);
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffWorkbook.Destroy;
|
|||
|
begin
|
|||
|
Sheets.Free;
|
|||
|
Fonts.Free;
|
|||
|
XFs.Free;
|
|||
|
SST.Free;
|
|||
|
Styles.Free;
|
|||
|
Win.Free;
|
|||
|
FFormats.Free;
|
|||
|
Escher.Free;
|
|||
|
FCipher.Free;
|
|||
|
SSTHash.Free;
|
|||
|
XFHash.Free;
|
|||
|
FontHash.Free;
|
|||
|
FLinkTbl.Free;
|
|||
|
|
|||
|
DeleteCriticalSection(FCsStrings);
|
|||
|
DeleteCriticalSection(FCsFonts);
|
|||
|
DeleteCriticalSection(FCsXFs);
|
|||
|
DeleteCriticalSection(FCsSheets);
|
|||
|
DeleteCriticalSection(FCsStyles);
|
|||
|
DeleteCriticalSection(FCsColors);
|
|||
|
DeleteCriticalSection(FCsPictures);
|
|||
|
DeleteCriticalSection(FCsFormats);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockSst;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsStrings);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockSst;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsStrings);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockSheets;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsSheets);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockSheets;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsSheets);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockXfs;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsXfs);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockXfs;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsXfs);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockColors;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsColors);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockColors;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsColors);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockStyles;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsStyles);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockStyles;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsStyles);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockPictures;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsPictures);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockPictures;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsPictures);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockFonts;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsFonts);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockFonts;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsFonts);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.LockFormats;
|
|||
|
begin
|
|||
|
EnterCriticalSection(FCsFormats);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.UnlockFormats;
|
|||
|
begin
|
|||
|
LeaveCriticalSection(FCsFormats);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.SetPassword(const s: WideString);
|
|||
|
begin
|
|||
|
if s = '' then
|
|||
|
begin
|
|||
|
FCipher.Free;
|
|||
|
FCipher := nil;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
if FCipher = nil then
|
|||
|
FCipher := TBiffRC4.Create;
|
|||
|
|
|||
|
with FCipher do
|
|||
|
Prepare(s[1], 2 * Length(s));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddSheet(S: TBiffSheet): LongWord;
|
|||
|
begin
|
|||
|
Assert(S <> nil);
|
|||
|
LockSheets;
|
|||
|
Result := Sheets.Add(S);
|
|||
|
UnlockSheets;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddXF(X: TBiffXF): LongWord;
|
|||
|
var
|
|||
|
p: TBiffXF;
|
|||
|
begin
|
|||
|
LockXfs;
|
|||
|
|
|||
|
p := TBiffXF(XFHash.SetValue(X.GetHashCode, X));
|
|||
|
|
|||
|
{ The XF is new for this workbook }
|
|||
|
|
|||
|
if p = nil then
|
|||
|
begin
|
|||
|
Result := XFs.Add(X);
|
|||
|
X.XfIndex := Result;
|
|||
|
end
|
|||
|
|
|||
|
{ The XF already exists in the workbook }
|
|||
|
|
|||
|
else
|
|||
|
begin
|
|||
|
Result := p.XfIndex;
|
|||
|
X.Free;
|
|||
|
end;
|
|||
|
|
|||
|
UnlockXfs;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddFont(F: TBiffFont): LongWord;
|
|||
|
var
|
|||
|
p: TBiffFont;
|
|||
|
begin
|
|||
|
LockFonts;
|
|||
|
|
|||
|
p := TBiffFont(FontHash.SetValue(f.GetHashCode, Pointer(f)));
|
|||
|
|
|||
|
{ The font is already in the workbook }
|
|||
|
|
|||
|
if p = nil then
|
|||
|
begin
|
|||
|
Result := Fonts.Add(f);
|
|||
|
f.FontIndex := Result;
|
|||
|
end
|
|||
|
else
|
|||
|
|
|||
|
{ The font is new for the workbook }
|
|||
|
|
|||
|
begin
|
|||
|
Result := p.FontIndex;
|
|||
|
f.Free;
|
|||
|
end;
|
|||
|
|
|||
|
if Result > 3 then
|
|||
|
Inc(Result);
|
|||
|
|
|||
|
UnlockFonts;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.SetFormat(i: TBiffFormatIndex; const s: WideString);
|
|||
|
begin
|
|||
|
AddFormat(s, i);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddFormat(const FormatStr: WideString;
|
|||
|
FormatIndex: TBiffFormatIndex): TBiffFormatIndex;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
s: TBiffUCS;
|
|||
|
begin
|
|||
|
if FormatStr = '' then
|
|||
|
begin
|
|||
|
|
|||
|
{ This is the general format.
|
|||
|
See the documentation for
|
|||
|
the complete list of 164
|
|||
|
predefined format values }
|
|||
|
|
|||
|
Result := 0;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
LockFormats;
|
|||
|
|
|||
|
for i := 0 to FFormats.Count - 1 do
|
|||
|
begin
|
|||
|
s := TBiffUCS(FFormats[i]);
|
|||
|
|
|||
|
if (FormatIndex < 0) and (s.Tag = FormatIndex) then
|
|||
|
begin
|
|||
|
UnlockFormats;
|
|||
|
raise Exception.CreateFmt('Format #%d already exists',
|
|||
|
[FormatIndex]);
|
|||
|
end;
|
|||
|
|
|||
|
if s.Data = FormatStr then
|
|||
|
begin
|
|||
|
UnlockFormats;
|
|||
|
Result := s.Tag;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
s := TBiffUCS.Create(FormatStr, True);
|
|||
|
s.Tag := FormatIndex;
|
|||
|
LockFormats;
|
|||
|
|
|||
|
if s.Tag < 0 then
|
|||
|
s.Tag := BiffUserFormat + FFormats.Count;
|
|||
|
|
|||
|
FFormats.Add(s);
|
|||
|
UnlockFormats;
|
|||
|
Result := s.Tag;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddBlip(Blip: TEscherPicture): LongInt;
|
|||
|
begin
|
|||
|
LockPictures;
|
|||
|
|
|||
|
with Escher.AddImage(Blip) do
|
|||
|
begin
|
|||
|
Inc(RefCount);
|
|||
|
Result := Index;
|
|||
|
end;
|
|||
|
|
|||
|
UnlockPictures;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddBitmap(Kind: TEscherBlipKind; Contents: TStream): LongInt;
|
|||
|
var
|
|||
|
p: TEscherBitmap;
|
|||
|
begin
|
|||
|
p := TEscherBitmap.Create;
|
|||
|
p.Kind := Kind;
|
|||
|
p.CopyFrom(Contents, 0);
|
|||
|
Result := AddBlip(p);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddMetafile(Metafile: Graphics.TMetafile): LongInt;
|
|||
|
var
|
|||
|
p: TEscherMetafile;
|
|||
|
fx, fy: Extended;
|
|||
|
begin
|
|||
|
p := TEscherMetafile.Create;
|
|||
|
p.Kind := EscherBkEMF;
|
|||
|
p.Compr := False;
|
|||
|
Metafile.Enhanced := True;
|
|||
|
Metafile.SaveToStream(p);
|
|||
|
|
|||
|
with p.Bounds do
|
|||
|
begin
|
|||
|
Left := 0;
|
|||
|
Top := 0;
|
|||
|
Right := Metafile.Width;
|
|||
|
Bottom := Metafile.Height;
|
|||
|
end;
|
|||
|
|
|||
|
fx := 2.54 * 360000 / 96;
|
|||
|
fy := 2.54 * 360000 / 96;
|
|||
|
|
|||
|
with p.MFSize do
|
|||
|
begin
|
|||
|
X := Round(Metafile.Width * fx);
|
|||
|
Y := Round(Metafile.Height * fy);
|
|||
|
end;
|
|||
|
|
|||
|
Result := AddBlip(p);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.GetFontsCount: LongInt;
|
|||
|
begin
|
|||
|
Result := Fonts.Count;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.GetLinkTable: TBiffLinkTable;
|
|||
|
begin
|
|||
|
if FLinkTbl = nil then
|
|||
|
FLinkTbl := TBiffLinkTable.Create;
|
|||
|
|
|||
|
Result := FLinkTbl;
|
|||
|
Assert(Result <> nil);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.GetFont(Index: LongInt): TBiffFont;
|
|||
|
begin
|
|||
|
if (Index < 0) or (Index >= Fonts.Count) then
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
LockFonts;
|
|||
|
Result := TBiffFont(Fonts[Index]);
|
|||
|
UnlockFonts;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.GetSheetsCount: LongInt;
|
|||
|
begin
|
|||
|
Result := Sheets.Count;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.GetSheet(Index: LongInt): TBiffSheet;
|
|||
|
begin
|
|||
|
if (Index < 0) or (Index >= Sheets.Count) then
|
|||
|
begin
|
|||
|
Result := nil;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
LockSheets;
|
|||
|
Result := TBiffSheet(Sheets[Index]);
|
|||
|
UnlockSheets;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddString(S: WideString): LongWord;
|
|||
|
begin
|
|||
|
Result := AddString(TBiffUCS.Create(S, True));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddString(S: TBiffUCS): LongWord;
|
|||
|
var
|
|||
|
p: TBiffUCS;
|
|||
|
begin
|
|||
|
LockSst;
|
|||
|
|
|||
|
p := TBIffUCS(SSTHash.SetValue(S.GetHashCode, S));
|
|||
|
|
|||
|
{ If the string aleready exists }
|
|||
|
|
|||
|
if p <> nil then
|
|||
|
begin
|
|||
|
S.Free;
|
|||
|
Result := p.SstIndex;
|
|||
|
end
|
|||
|
else
|
|||
|
|
|||
|
{ If the string is new for the workbook }
|
|||
|
|
|||
|
begin
|
|||
|
Result := SST.Add(S);
|
|||
|
S.SstIndex := Result;
|
|||
|
end;
|
|||
|
|
|||
|
UnlockSst;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddStyle(S: TBiffStyle): LongInt;
|
|||
|
begin
|
|||
|
LockStyles;
|
|||
|
Result := Styles.Add(S);
|
|||
|
UnlockStyles;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddColor(C: LongWord): LongInt;
|
|||
|
begin
|
|||
|
LockColors;
|
|||
|
Result := AddColorInternal(C);
|
|||
|
UnlockColors;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.AddColorInternal(C: LongWord): LongInt;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
C := ColorToRGB(Integer(C)) and $ffffff;
|
|||
|
|
|||
|
{ Find the nearest color.
|
|||
|
|
|||
|
Note that the new color is found starting from the Palette[7],
|
|||
|
not from Palette[0], as may be expected. Excel don't like
|
|||
|
colors with indexes 0..6, that's why these palette indexes are
|
|||
|
ignored in this search. }
|
|||
|
|
|||
|
if PalUsed < 8 then
|
|||
|
i := -1
|
|||
|
else
|
|||
|
i := 7 + NearestColor(@Palette[7], PalUsed - 7, C);
|
|||
|
|
|||
|
//
|
|||
|
// If an exact match found, return the index.
|
|||
|
//
|
|||
|
|
|||
|
if (i >= 0) and (Palette[i] = C) then
|
|||
|
begin
|
|||
|
Result := i;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// If no exact match found but palette is full,
|
|||
|
// return an index to the nearest color.
|
|||
|
//
|
|||
|
|
|||
|
if (i >= 0) and (PalUsed = BiffPaletteSize) then
|
|||
|
begin
|
|||
|
Result := i;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// If no exact match found and the palette is not full,
|
|||
|
// add the new color.
|
|||
|
//
|
|||
|
|
|||
|
Palette[PalUsed] := C;
|
|||
|
Result := PalUsed;
|
|||
|
Inc(PalUsed);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffWorkbook.StringsCount: LongInt;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
LockSst;
|
|||
|
Result := 0;
|
|||
|
|
|||
|
for i := 0 to Sheets.Count - 1 do
|
|||
|
Inc(Result, TBiffSheet(Sheets[i]).TextCellsCount);
|
|||
|
|
|||
|
UnlockSst;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffWorkbook.Flush(Stream: TBiffStream);
|
|||
|
|
|||
|
procedure WriteInt(RecId: TBiffRecId; Value, Size: LongInt);
|
|||
|
begin
|
|||
|
Stream.Add(RecId).Write(Value, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteFonts;
|
|||
|
begin
|
|||
|
TBiffObject.FlushList(Fonts, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteXFs;
|
|||
|
begin
|
|||
|
TBiffObject.FlushList(XFs, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteSheets;
|
|||
|
var
|
|||
|
S: TBiffSheet;
|
|||
|
R: TBiffrSheet;
|
|||
|
UCS: TBiffUCS;
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
for i := 0 to Sheets.Count - 1 do
|
|||
|
begin
|
|||
|
S := TBiffSheet(Sheets[i]);
|
|||
|
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.Offset := 0; // Anything here. This will be overwritten. See docs.
|
|||
|
R.Vis := Byte(S.Visibility);
|
|||
|
R.Kind := Byte(S.Kind);
|
|||
|
|
|||
|
if i = 0 then
|
|||
|
S.View.Options := S.View.Options or BiffWoSelected;
|
|||
|
S.RecIndex := Stream.Count;
|
|||
|
Stream.Add(BiffIdSheet).Write(R, SizeOf(R));
|
|||
|
|
|||
|
UCS := TBiffUCS.Create(S.Name, False);
|
|||
|
UCS.Flush(Stream);
|
|||
|
UCS.Free;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteSST;
|
|||
|
var
|
|||
|
R: TBiffrSST;
|
|||
|
begin
|
|||
|
if SST.Count = 0 then
|
|||
|
Exit;
|
|||
|
|
|||
|
ZeroMemory(@R, SizeOf(R));
|
|||
|
|
|||
|
R.WBSCount := StringsCount;
|
|||
|
R.SSTCount := SST.Count;
|
|||
|
|
|||
|
Stream.Add(BiffIdSST).Write(R, SizeOf(R));
|
|||
|
TBiffObject.FlushList(SST, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WritePalette;
|
|||
|
var
|
|||
|
n: Integer;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdPalette) do
|
|||
|
begin
|
|||
|
n := System.Length(Palette) - 8;
|
|||
|
Assert(n = 56); // required by [MS-XLS] Section 2.4.188
|
|||
|
|
|||
|
WriteConst(n, 2);
|
|||
|
Write(Palette[8], 4 * n);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteStyles;
|
|||
|
begin
|
|||
|
TBiffObject.FlushList(Styles, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteFormats;
|
|||
|
var
|
|||
|
ucs: TBiffUCS;
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
for i := 0 to FFormats.Count - 1 do
|
|||
|
begin
|
|||
|
ucs := FFormats[i];
|
|||
|
Stream.Add(BiffIdFormat).Write(ucs.Tag, 2);
|
|||
|
ucs.Flush(Stream);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteEscher;
|
|||
|
var
|
|||
|
es: TEscherStream;
|
|||
|
ms: TMemoryStream;
|
|||
|
begin
|
|||
|
if Escher.Empty then
|
|||
|
Exit;
|
|||
|
|
|||
|
es := TEscherStream.Create;
|
|||
|
ms := TMemoryStream.Create;
|
|||
|
|
|||
|
Escher.Flush(es);
|
|||
|
es.Flush(ms);
|
|||
|
|
|||
|
Stream.Add(BiffIdEscher).Write(ms.Memory^, ms.Size);
|
|||
|
|
|||
|
ms.Free;
|
|||
|
es.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteFilePass;
|
|||
|
begin
|
|||
|
if FCipher <> nil then
|
|||
|
FCipher.Flush(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteInterface;
|
|||
|
begin
|
|||
|
with Stream do
|
|||
|
begin
|
|||
|
Add(BiffIdInterfaceHdr).WriteConst(1200, 2);
|
|||
|
Add(BiffIdMms).WriteConst(0, 2);
|
|||
|
Add(BiffIdInterfaceEnd);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ [MS-XLS] 2.4.349 }
|
|||
|
|
|||
|
procedure WriteAccess;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdWriteAccess) do
|
|||
|
begin
|
|||
|
WriteConst($02, 2);
|
|||
|
WriteConst($00, 2);
|
|||
|
WriteCOnst($20, 1);
|
|||
|
WriteConst($20, 1);
|
|||
|
|
|||
|
while Size < 112 do
|
|||
|
WriteConst($20, 1);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteRRD;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
if SheetsCount > BiffRrdMaxCount then
|
|||
|
Exit;
|
|||
|
|
|||
|
with Stream.Add(BiffIdRrd) do
|
|||
|
for i := 0 to SheetsCount - 1 do
|
|||
|
WriteConst(i + 1, 2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteProt;
|
|||
|
begin
|
|||
|
with Stream do
|
|||
|
begin
|
|||
|
Add(BiffIdWinProt).WriteConst(0, 2);
|
|||
|
Add(BiffIdProt).WriteConst(0, 2);
|
|||
|
Add(BiffIdPassword).WriteConst(0, 2);
|
|||
|
Add(BiffIdProtRev).WriteConst(0, 2);
|
|||
|
Add(BiffIdProtRevPass).WriteConst(0, 2);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteExtSST;
|
|||
|
begin
|
|||
|
{ todo: EXTSST not emitted yet }
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteLinkTable;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
if FLinkTbl = nil then
|
|||
|
Exit;
|
|||
|
|
|||
|
FLinkTbl.SheetsCount := SheetsCount;
|
|||
|
|
|||
|
for i := 0 to Sheets.Count - 1 do
|
|||
|
FLinkTbl.SetSheetIndex(TBiffSheet(Sheets[i]).Name, i);
|
|||
|
|
|||
|
FLinkTbl.Flush(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
Stream.AddBOF(bkWBGlobals); // BOF
|
|||
|
WriteFilePass; // FILEPASS
|
|||
|
WriteInterface; // INTERFACEHDR MMS INTERFACEEND
|
|||
|
WriteAccess; // WRITEACCESS
|
|||
|
WriteInt(BiffIdCodepage, 1200, 2); // CODEPAGE
|
|||
|
WriteInt(BiffIdDsf, 0, 2); // DSF
|
|||
|
WriteRRD; // RRD
|
|||
|
WriteProt; // WINPROTECT PROTECT PASSWORD PROTREV PROTREVPASS
|
|||
|
Win.Flush(Stream); // WINDOW1
|
|||
|
WriteInt(BiffIdBackup, 0, 2); // BACKUP
|
|||
|
WriteInt(BiffIdHideObj, 0, 2); // HIDEOBJ
|
|||
|
WriteInt(BiffIdDate1904, 0, 2); // DATE1904
|
|||
|
WriteInt(BiffIdCalcPrec, 1, 2); // CALCPRECISION
|
|||
|
WriteInt(BiffIdRefreshAll, 0, 2); // REFRESHALL
|
|||
|
WriteInt(BiffIdBookBool, 0, 2); // BOOKBOOL
|
|||
|
WriteFonts; // 1*510 FONT
|
|||
|
WriteFormats; // 8*218 FORMAT
|
|||
|
WriteXFs; // 16* XF
|
|||
|
WriteStyles; // 1* STYLE
|
|||
|
WritePalette; // PALETTE
|
|||
|
WriteInt(BiffIdUserElf, 0, 2); // USERELF
|
|||
|
WriteSheets; // SHEET
|
|||
|
WriteInt(BiffIdCountry, $70007, 4); // COUNTRY
|
|||
|
WriteEscher; // *ESCHER
|
|||
|
WriteSST; // SST
|
|||
|
WriteExtSST; // EXTSST
|
|||
|
WriteLinkTable; // EXTRENALBOOK, EXTERNALSHEET, etc.
|
|||
|
Stream.AddEOF; // EOF
|
|||
|
|
|||
|
{ Write sheets }
|
|||
|
|
|||
|
for i := 0 to Sheets.Count - 1 do
|
|||
|
with TBiffSheet(Sheets[i]) do
|
|||
|
begin
|
|||
|
Stream[RecIndex].WriteBytes(0, Stream.Size, 4);
|
|||
|
Flush(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
{ Encrypt the workbook if it needs }
|
|||
|
|
|||
|
if FCipher <> nil then
|
|||
|
FCipher.EncryptStream(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffSheet }
|
|||
|
|
|||
|
constructor TBiffSheet.Create(Owner: TBiffWorkbook);
|
|||
|
begin
|
|||
|
FOwner := Owner;
|
|||
|
|
|||
|
FRows := TObjList.Create;
|
|||
|
FCols := TObjList.Create;
|
|||
|
|
|||
|
FMrgCells := TObjectList.Create(True);
|
|||
|
|
|||
|
PageSetup := TBiffPageSetup.Create;
|
|||
|
View := TBiffWindow2.Create;
|
|||
|
|
|||
|
Bounds.FR := -1;
|
|||
|
Bounds.LR := -1;
|
|||
|
Bounds.FC := -1;
|
|||
|
Bounds.LC := -1;
|
|||
|
|
|||
|
FStrCount := 0;
|
|||
|
Name := 'Sheet';
|
|||
|
FKind := skWorksheet;
|
|||
|
FVis := svVisible;
|
|||
|
FPB := TList.Create;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffSheet.Destroy;
|
|||
|
begin
|
|||
|
FRows.Free;
|
|||
|
FCols.Free;
|
|||
|
FMrgCells.Free;
|
|||
|
PageSetup.Free;
|
|||
|
View.Free;
|
|||
|
FPB.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.SetColWidth(i: LongInt; w: LongInt);
|
|||
|
begin
|
|||
|
if (i >= 0) and (i <= BiffMaxCol) then
|
|||
|
AddColInfo(TBiffColInfo.Create(i, 15, w));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.SetRowHeight(i: LongInt; h: LongInt);
|
|||
|
var
|
|||
|
r: TBiffRow;
|
|||
|
c: TBiffCell;
|
|||
|
begin
|
|||
|
if (i < 0) or (i > BiffMaxRow) then
|
|||
|
Exit;
|
|||
|
|
|||
|
r := GetRow(i);
|
|||
|
|
|||
|
if r = nil then
|
|||
|
begin
|
|||
|
c := TBiffCell.Create;
|
|||
|
with c do
|
|||
|
begin
|
|||
|
Row := i;
|
|||
|
Col := 0;
|
|||
|
end;
|
|||
|
|
|||
|
AddCell(c);
|
|||
|
r := GetRow(i);
|
|||
|
end;
|
|||
|
|
|||
|
if r <> nil then
|
|||
|
r.Height := h;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffSheet.GetColWidth(i: LongInt): LongInt;
|
|||
|
var
|
|||
|
j: LongInt;
|
|||
|
begin
|
|||
|
for j := FCols.Count - 1 downto 0 do
|
|||
|
with TBiffColInfo(FCols[j]) do
|
|||
|
if (First <= i) and (i <= Last) then
|
|||
|
begin
|
|||
|
Result := Width;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
Result := 0;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffSheet.GetRowHeight(i: LongInt): LongInt;
|
|||
|
var
|
|||
|
r: TBiffRow;
|
|||
|
begin
|
|||
|
r := GetRow(i);
|
|||
|
if r = nil then
|
|||
|
Result := 0
|
|||
|
else
|
|||
|
Result := r.Height;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffSheet.LastRowIndex: LongInt;
|
|||
|
begin
|
|||
|
Result := Bounds.LR;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.MergeCells(Rect: TRect);
|
|||
|
var
|
|||
|
_Rectangle: TRectangle;
|
|||
|
begin
|
|||
|
Assert((Rect.Left <= Rect.Right) and (Rect.Top <= Rect.Bottom));
|
|||
|
|
|||
|
{ There's no need to merge a 1x1 rectangle of cells }
|
|||
|
|
|||
|
if (Rect.Left = Rect.Right) and (Rect.Top = Rect.Bottom) then
|
|||
|
Exit;
|
|||
|
|
|||
|
{ todo: Rect should be checked for intersections with all existing
|
|||
|
merged blocks, but it'd be slow or it'd require a complex algorithm }
|
|||
|
|
|||
|
_Rectangle := TRectangle.Create;
|
|||
|
_Rectangle.TopLeft := Rect.TopLeft;
|
|||
|
_Rectangle.BottomRight := Rect.BottomRight;
|
|||
|
FMrgCells.Add(_Rectangle);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.AddPageBreak(Row: LongInt);
|
|||
|
begin
|
|||
|
FPB.Add(Pointer(Row));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffSheet.AddDrawing: TEscherShape;
|
|||
|
begin
|
|||
|
if FDGroup = nil then
|
|||
|
FDGroup := FOwner.Escher.AddGroup;
|
|||
|
|
|||
|
Result := FDGRoup.Add;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.AddColInfo(Info: TBiffColInfo);
|
|||
|
begin
|
|||
|
FCols.Add(Info);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffSheet.GetRow(Index: LongInt): TBiffRow;
|
|||
|
var
|
|||
|
i, r: Longint;
|
|||
|
begin
|
|||
|
{ In most cases, only one iteration of this loop is executed,
|
|||
|
because this function is optimised for sequential access. }
|
|||
|
|
|||
|
for i := 0 to FRows.Count - 1 do
|
|||
|
begin
|
|||
|
r := (FLastReadRow + i) mod FRows.Count;
|
|||
|
|
|||
|
if TBiffRow(FRows[r]).Row = Index then
|
|||
|
begin
|
|||
|
Result := TBiffRow(FRows[r]);
|
|||
|
FLastReadRow := r + 1;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
Result := nil;
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// There is a list of rows, where each row contains
|
|||
|
// a list of cells. The rows in the list are ordered
|
|||
|
// by the row index by ascending. This function
|
|||
|
// looks for a place in the list of rows and adds to there
|
|||
|
// a cell with the specified coordinates.
|
|||
|
//
|
|||
|
|
|||
|
procedure TBiffSheet.AddCell(Cell: TBiffCell);
|
|||
|
|
|||
|
function CreateRow(Cell: TBiffCell): TBiffRow;
|
|||
|
var
|
|||
|
R: TBiffRow;
|
|||
|
begin
|
|||
|
R := TBiffRow.Create;
|
|||
|
R.Row := Cell.Row;
|
|||
|
R.Cells.Add(Cell);
|
|||
|
|
|||
|
R.FirstCol := Cell.Col;
|
|||
|
R.LastCol := Cell.Col;
|
|||
|
|
|||
|
Result := R;
|
|||
|
end;
|
|||
|
|
|||
|
procedure AttachToRow(R: TBiffRow; Cell: TBiffCell);
|
|||
|
begin
|
|||
|
R.Cells.Add(Cell);
|
|||
|
|
|||
|
if Cell.Col < R.FirstCol then
|
|||
|
R.FirstCol := Cell.Col;
|
|||
|
|
|||
|
if Cell.Col > R.LastCol then
|
|||
|
R.LastCol := Cell.Col;
|
|||
|
end;
|
|||
|
|
|||
|
procedure InsertRow(Cell: TBiffCell);
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FRows.Count - 1;
|
|||
|
|
|||
|
while (i >= 0) and (TBiffRow(FRows[i]).Row > Cell.Row) do
|
|||
|
Dec(i);
|
|||
|
|
|||
|
if (i >= 0) and (TBiffRow(FRows[i]).Row = Cell.Row) then
|
|||
|
AttachToRow(TBiffRow(FRows[i]), Cell)
|
|||
|
else
|
|||
|
FRows.Insert(i + 1, CreateRow(Cell));
|
|||
|
end;
|
|||
|
|
|||
|
procedure AdjustBounds(r, c: LongInt; var b: TBiffBounds);
|
|||
|
begin
|
|||
|
with b do
|
|||
|
begin
|
|||
|
if (r < FR) or (FR < 0) then
|
|||
|
FR := r;
|
|||
|
|
|||
|
if (r > LR) or (LR < 0) then
|
|||
|
LR := r;
|
|||
|
|
|||
|
if (c < FC) or (FC < 0) then
|
|||
|
FC := c;
|
|||
|
|
|||
|
if (c > LC) or (LC < 0) then
|
|||
|
LC := c;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
InsertRow(Cell);
|
|||
|
AdjustBounds(Cell.Row, Cell.Col, Bounds);
|
|||
|
|
|||
|
if Cell is TBiffTextCell then
|
|||
|
Inc(FStrCount);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffSheet.Flush(Stream: TBiffStream);
|
|||
|
|
|||
|
procedure WriteInt(RecId: TBiffRecId; Value, Size: LongInt);
|
|||
|
begin
|
|||
|
Stream.Add(RecId).Write(Value, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteF8(RecId: TBiffRecId; Value: Double);
|
|||
|
begin
|
|||
|
Stream.Add(RecId).Write(Value, 8);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteDIMENSION;
|
|||
|
var
|
|||
|
Dim: TBiffrDimension;
|
|||
|
begin
|
|||
|
ZeroMemory(@Dim, SizeOf(Dim));
|
|||
|
|
|||
|
with Bounds do
|
|||
|
begin
|
|||
|
{ If FR or FC values are negative, then
|
|||
|
this sheet contains no cells }
|
|||
|
|
|||
|
if FR < 0 then
|
|||
|
FR := 0;
|
|||
|
|
|||
|
if FC < 0 then
|
|||
|
FC := 0;
|
|||
|
|
|||
|
Dim.FirstRow := Bounds.FR;
|
|||
|
Dim.LastRow := Bounds.LR + 1;
|
|||
|
Dim.FirstCol := Bounds.FC;
|
|||
|
Dim.LastCol := Bounds.LC + 1;
|
|||
|
end;
|
|||
|
|
|||
|
Stream.Add(BiffIdDim).Write(Dim, Sizeof(Dim));
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// todo
|
|||
|
//
|
|||
|
|
|||
|
procedure WriteCells(R: TBiffRow);
|
|||
|
begin
|
|||
|
TBiffObject.FlushList(R.Cells, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteCOLINFO;
|
|||
|
begin
|
|||
|
TBiffObject.FlushList(FCols, Stream);
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// BIFF8 requires that a MERGEDCELLS record
|
|||
|
// is not continued by a CONTINUE record, so
|
|||
|
// merged blocks will be grouped by 1024 items
|
|||
|
// and written in different MERGEDCELLS records.
|
|||
|
//
|
|||
|
|
|||
|
procedure WriteMergedCells;
|
|||
|
|
|||
|
procedure WriteBlock(First, Last: Integer);
|
|||
|
var
|
|||
|
m: TBiffrCellsBlock;
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdMergedCells) do
|
|||
|
begin
|
|||
|
WriteConst(Last - First + 1, 2);
|
|||
|
|
|||
|
for i := First to Last do
|
|||
|
begin
|
|||
|
with m, TRectangle(FMrgCells[i]) do
|
|||
|
begin
|
|||
|
FR := Top;
|
|||
|
LR := Bottom;
|
|||
|
FC := Left;
|
|||
|
LC := Right;
|
|||
|
end;
|
|||
|
|
|||
|
Write(m, SizeOf(m));
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i, n: Integer;
|
|||
|
begin
|
|||
|
n := Min(BiffMaxMrgCellsNum, (BiffMaxRecLen - 2) div 8);
|
|||
|
i := 0;
|
|||
|
|
|||
|
while i < FMrgCells.Count do
|
|||
|
begin
|
|||
|
WriteBlock(i, Min(i + n - 1, FMrgCells.Count - 1));
|
|||
|
Inc(i, n);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteMargins;
|
|||
|
|
|||
|
procedure WM(Id: LongInt; Value: Double);
|
|||
|
begin
|
|||
|
Stream.Add(Id).Write(Value, 8);
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
with Margin do
|
|||
|
begin
|
|||
|
WM(BiffIdLeftMargin, Left);
|
|||
|
WM(BiffIdTopMargin, Top);
|
|||
|
WM(BiffIdRightMargin, Right);
|
|||
|
WM(BiffIdBottomMargin, Bottom);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteDrawings;
|
|||
|
var
|
|||
|
es: TEscherStream;
|
|||
|
ms: TMemoryStream;
|
|||
|
|
|||
|
begin
|
|||
|
if FDGroup = nil then
|
|||
|
Exit;
|
|||
|
|
|||
|
es := TEscherStream.Create;
|
|||
|
ms := TMemoryStream.Create;
|
|||
|
|
|||
|
FDGroup.Flush(es);
|
|||
|
es.Flush(ms);
|
|||
|
|
|||
|
Stream.Add(BiffIdDrawing).Write(ms.Memory^, ms.Size);
|
|||
|
|
|||
|
ms.Free;
|
|||
|
es.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteGOBJ;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
if FDGroup = nil then
|
|||
|
Exit;
|
|||
|
|
|||
|
// [MS-XLS] 2.4.181
|
|||
|
|
|||
|
for i := 0 to FDGroup.Count - 1 do
|
|||
|
with Stream.Add(BiffIdGObj) do
|
|||
|
begin
|
|||
|
// [MS-XLS] 2.5.143
|
|||
|
|
|||
|
WriteConst($15, 2); // record id
|
|||
|
WriteConst($12, 2); // record len
|
|||
|
WriteConst($08, 2);
|
|||
|
WriteZeros(16);
|
|||
|
|
|||
|
// [MS-XLS] 2.5.142
|
|||
|
|
|||
|
WriteConst($07, 2); // record id
|
|||
|
WriteConst($02, 2); // record len
|
|||
|
WriteConst($ffff, 2);
|
|||
|
|
|||
|
// [MS-XLS} 2.5.151
|
|||
|
|
|||
|
WriteConst($08, 2); // record id
|
|||
|
WriteConst($02, 2); // record len
|
|||
|
WriteConst($00, 2);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WritePageBreak;
|
|||
|
var
|
|||
|
i: LongInt;
|
|||
|
begin
|
|||
|
if FPB.Count = 0 then Exit;
|
|||
|
with Stream.Add(BiffIdHorPageBreak) do
|
|||
|
begin
|
|||
|
Write(FPB.Count, 2);
|
|||
|
|
|||
|
for i := 0 to FPB.Count - 1 do
|
|||
|
begin
|
|||
|
WriteConst(LongInt(FPB[i]), 2);
|
|||
|
WriteConst(0, 2);
|
|||
|
WriteConst(BiffMaxCol, 2);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteIndex;
|
|||
|
begin
|
|||
|
{ todo: INDEX not emitted yet }
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteGuts;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdGuts) do
|
|||
|
begin
|
|||
|
WriteConst(0, 4);
|
|||
|
WriteConst(0, 4);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ Actual format of DEFROWHEIGHT see
|
|||
|
in [MS-XLS] section 2.4.87 }
|
|||
|
|
|||
|
procedure WriteDefRowHeight;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdDefRowHeight) do
|
|||
|
begin
|
|||
|
WriteConst(0, 2);
|
|||
|
WriteConst(300, 2);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure WriteWsBool;
|
|||
|
begin
|
|||
|
WriteInt(
|
|||
|
BiffIdWsBool,
|
|||
|
|
|||
|
//BiffWsbFitPage or
|
|||
|
BiffWsbShowBreaks or
|
|||
|
BiffWsbRowSums or
|
|||
|
BiffWsbColSums,
|
|||
|
|
|||
|
2);
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i, j, fi, li, mi: LongInt;
|
|||
|
r, fr: TBiffRow;
|
|||
|
|
|||
|
begin
|
|||
|
Stream.AddBOF(bkSheet); // BOF
|
|||
|
WriteIndex; // INDEX
|
|||
|
WriteInt(BiffIdCalcMode, 1, 2); // CALCMODE
|
|||
|
WriteInt(BiffIdCalcCount, 100, 2); // CALCCOUNT
|
|||
|
WriteInt(BiffIdCalcRefMode, 1, 2); // CALCREFMODE
|
|||
|
WriteInt(BiffIdCalcIter, 0, 2); // CALCITER
|
|||
|
WriteF8(BiffIdCalcDelta, 0.001); // CALCDELTA
|
|||
|
WriteInt(BiffIdSaveRecalc, 1, 2); // SAVERECALC
|
|||
|
WriteInt(BiffIdPrintRowCol, 0, 2); // PRINTROWCOL
|
|||
|
WriteInt(BiffIdPrintGrid, 0, 2); // PRINTGRID
|
|||
|
WriteInt(BiffIdGridSet, 1, 2); // GRIDSET
|
|||
|
WriteGuts; // GUTS
|
|||
|
WriteDefRowHeight; // DEFROWHEIGHT
|
|||
|
WriteWsBool; // WSBOOL
|
|||
|
WritePageBreak; // HPAGEBREAK
|
|||
|
Stream.Add(BiffIdHeader); // HEADER
|
|||
|
Stream.Add(BiffIdFooter); // FOOTER
|
|||
|
WriteInt(BiffIdHCenter, 0, 2); // HCENTER
|
|||
|
WriteInt(BiffIdVCenter, 0, 2); // VCENTER
|
|||
|
WriteMargins; // LEFTMARGIN TOPMARGIN RIGHTMARGIN BOTTOMMARGIN
|
|||
|
PageSetup.Flush(Stream); // PAGESETUP
|
|||
|
WriteInt(BiffIdDefColWidth, 8, 2); // DEFCOLWIDTH
|
|||
|
WriteCOLINFO; // COLINFO
|
|||
|
WriteDIMENSION; // DIMENSION
|
|||
|
|
|||
|
fi := 0;
|
|||
|
|
|||
|
if FRows.Count > 0 then
|
|||
|
repeat
|
|||
|
//
|
|||
|
// MS Excel allows up to 32 rows written
|
|||
|
// consequently. Each block of 32 rows must be ended
|
|||
|
// with a DBCELL record.
|
|||
|
//
|
|||
|
|
|||
|
fr := TBiffRow(FRows[fi]);
|
|||
|
mi := fr.Row + BiffRowBlockSz - 1;
|
|||
|
|
|||
|
//
|
|||
|
// Write ROW records
|
|||
|
//
|
|||
|
|
|||
|
i := fi;
|
|||
|
|
|||
|
repeat
|
|||
|
r := TBiffRow(FRows[i]);
|
|||
|
if r.Row > mi then
|
|||
|
Break;
|
|||
|
|
|||
|
r.Offset := Stream.Size;
|
|||
|
r.Flush(Stream);
|
|||
|
Inc(i);
|
|||
|
until i = FRows.Count;
|
|||
|
|
|||
|
li := i - 1;
|
|||
|
|
|||
|
//
|
|||
|
// Write cells
|
|||
|
//
|
|||
|
|
|||
|
for i := fi to li do
|
|||
|
begin
|
|||
|
r := TBiffRow(FRows[i]);
|
|||
|
if r.Cells.Count = 0 then
|
|||
|
Continue;
|
|||
|
|
|||
|
r.FirstCell := Stream.Size;
|
|||
|
TBiffObject(r.Cells[0]).Flush(Stream);
|
|||
|
|
|||
|
for j := 1 to r.Cells.Count - 1 do
|
|||
|
TBiffObject(r.Cells[j]).Flush(Stream);
|
|||
|
end;
|
|||
|
|
|||
|
//
|
|||
|
// Write DBCELL
|
|||
|
//
|
|||
|
|
|||
|
with Stream.Add(BiffIdDBCell) do
|
|||
|
begin
|
|||
|
WriteConst(Offset - fr.Offset, 4);
|
|||
|
for i := fi to li do
|
|||
|
begin
|
|||
|
r := TBiffRow(FRows[i]);
|
|||
|
WriteConst(r.FirstCell - r.Offset - 4 - SizeOf(TBiffrRow), 2);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
fi := li + 1;
|
|||
|
until fi = FRows.Count;
|
|||
|
|
|||
|
WriteDrawings; // DRAWING
|
|||
|
//WriteGOBJ; // GOBJ
|
|||
|
View.Flush(Stream); // WINDOW2
|
|||
|
WriteMergedCells; // MERGEDCELLS
|
|||
|
|
|||
|
Stream.AddEOF; // EOF
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffUCS }
|
|||
|
|
|||
|
constructor TBiffUCS.Create;
|
|||
|
begin
|
|||
|
Init;
|
|||
|
end;
|
|||
|
|
|||
|
constructor TBiffUCS.Create(const S: WideString; UCS16: Boolean);
|
|||
|
begin
|
|||
|
Init;
|
|||
|
Data := S;
|
|||
|
Len16 := UCS16;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffUCS.Destroy;
|
|||
|
begin
|
|||
|
FRuns.Free;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffUCS.Init;
|
|||
|
begin
|
|||
|
FRuns := TMemoryStream.Create;
|
|||
|
FCompress := True;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffUCS.AddFormat(Position, Font: LongInt);
|
|||
|
begin
|
|||
|
FRuns.Write(Position, 2);
|
|||
|
FRuns.Write(Font, 2);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffUCS.IsCompressible: Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
if not FCompress then
|
|||
|
begin
|
|||
|
Result := False;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
for i := 1 to Length(Data) do
|
|||
|
if Word(Ord(Data[i])) > 255 then
|
|||
|
begin
|
|||
|
Result := False;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffUCS.Flush(Stream: TBiffStream);
|
|||
|
|
|||
|
procedure WriteStr(s: PWideChar; len: LongWord; flags: Byte);
|
|||
|
var
|
|||
|
n: LongWord;
|
|||
|
buffer: array[0..BiffMaxRecLen - 2] of AnsiChar;
|
|||
|
|
|||
|
//
|
|||
|
// Prepares up to "count" bytes from the string
|
|||
|
// to be written to a record
|
|||
|
//
|
|||
|
|
|||
|
procedure Prepare(count: LongWord);
|
|||
|
label
|
|||
|
copychar;
|
|||
|
var
|
|||
|
x, i: LongWord;
|
|||
|
begin
|
|||
|
if (flags and 1) = 1 then
|
|||
|
begin
|
|||
|
x := count shr 1;
|
|||
|
if x > len then
|
|||
|
x := len;
|
|||
|
|
|||
|
n := x*2;
|
|||
|
CopyMemory(@buffer[0], s, n);
|
|||
|
|
|||
|
s := PWideChar(LongWord(s) + n);
|
|||
|
Dec(len, x);
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
n := count;
|
|||
|
|
|||
|
if n > len then
|
|||
|
n := len;
|
|||
|
|
|||
|
for i := 0 to n - 1 do
|
|||
|
buffer[i] := PAnsiChar(LongWord(s) + 2*i)^;
|
|||
|
|
|||
|
Dec(len, n);
|
|||
|
s := PWideChar(LongWord(s) + 2*n);
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
n := BiffMaxRecLen - Stream[Stream.Count - 1].Size;
|
|||
|
|
|||
|
if n > 0 then
|
|||
|
begin
|
|||
|
Prepare(n);
|
|||
|
Stream[Stream.Count - 1].Write(buffer[0], n);
|
|||
|
end;
|
|||
|
|
|||
|
while len > 0 do
|
|||
|
begin
|
|||
|
Prepare(BiffMaxRecLen - 1);
|
|||
|
with Stream.Add(BiffIdContinue) do
|
|||
|
begin
|
|||
|
Write(flags, 1);
|
|||
|
Write(buffer[0], n);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
hSize: LongWord; // header size with the first character
|
|||
|
f: Byte; // flags
|
|||
|
r: TBiffRecord; // first available record
|
|||
|
compr: Boolean; // set if the string is compressible
|
|||
|
frn: LongWord; // count of formatting runs
|
|||
|
lDataSize: Cardinal;
|
|||
|
begin
|
|||
|
compr := IsCompressible;
|
|||
|
frn := FRuns.Size shr 2;
|
|||
|
|
|||
|
//
|
|||
|
// Calculate the size of the header.
|
|||
|
//
|
|||
|
|
|||
|
hSize := 2;
|
|||
|
|
|||
|
if Len16 then
|
|||
|
Inc(hSize);
|
|||
|
|
|||
|
if frn > 0 then
|
|||
|
Inc(hSize, 2);
|
|||
|
|
|||
|
if Data <> '' then
|
|||
|
if compr then
|
|||
|
Inc(hSize, 1)
|
|||
|
else
|
|||
|
Inc(hSize, 2);
|
|||
|
|
|||
|
f := 0;
|
|||
|
|
|||
|
if not compr then
|
|||
|
f := 1; // compression flag
|
|||
|
|
|||
|
if frn > 0 then
|
|||
|
f := f or 8; // formatting runs flag
|
|||
|
|
|||
|
//
|
|||
|
// Find the first available record
|
|||
|
// that capable to keep the header
|
|||
|
// and write the header.
|
|||
|
//
|
|||
|
lDataSize := Length(Data);
|
|||
|
// keep with runs as well #486459
|
|||
|
r := Stream[Stream.Count - 1];
|
|||
|
if (r = nil) or (r.Size + hSize > BiffMaxRecLen) or
|
|||
|
(r.Size + hSize + lDataSize * 2 + FRuns.Size + 1 > BiffMaxRecLen) then
|
|||
|
r := Stream.Add(BiffIdContinue);
|
|||
|
|
|||
|
if Len16 then
|
|||
|
r.WriteConst(lDataSize, 2)
|
|||
|
else
|
|||
|
r.WriteConst(lDataSize, 1);
|
|||
|
|
|||
|
r.Write(f, 1);
|
|||
|
if frn > 0 then
|
|||
|
r.Write(frn, 2);
|
|||
|
|
|||
|
if Data <> '' then
|
|||
|
begin
|
|||
|
if compr then
|
|||
|
r.Write(Data[1], 1)
|
|||
|
else
|
|||
|
r.Write(Data[1], 2);
|
|||
|
|
|||
|
//
|
|||
|
// Write characters
|
|||
|
// and formatting runs.
|
|||
|
//
|
|||
|
|
|||
|
if lDataSize > 1 then
|
|||
|
WriteStr(@Data[2], lDataSize - 1, f and 1);
|
|||
|
|
|||
|
if frn > 0 then
|
|||
|
Stream[Stream.Count - 1].Write(FRuns.Memory^, FRuns.Size);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffUCS.SetData(const Value: WideString);
|
|||
|
begin
|
|||
|
FData := Value;
|
|||
|
FHash := 0;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffUCS.Equals(s: TBiffObject): Boolean;
|
|||
|
begin
|
|||
|
if s.GetHashCode = GetHashCode then
|
|||
|
Result := TBiffUCS(s).Data = Data
|
|||
|
else
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffUCS.GetHashCode: LongInt;
|
|||
|
begin
|
|||
|
if FHash <> 0 then
|
|||
|
begin
|
|||
|
Result := FHash;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
if FData <> '' then
|
|||
|
TCryptoHash.Hash('Jenkins', FHash, SizeOf(FHash),
|
|||
|
FData[1], Length(FData) * SizeOf(FData[1]))
|
|||
|
else
|
|||
|
FHash := 0;
|
|||
|
|
|||
|
Result := FHash;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffRecord }
|
|||
|
|
|||
|
constructor TBiffRecord.Create(Owner: TBiffStream; Offset: Cardinal);
|
|||
|
begin
|
|||
|
FOwner := Owner;
|
|||
|
FOffset := Offset;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffRecord.GetRecId: TBiffRecId;
|
|||
|
begin
|
|||
|
Result := TBiffRecId(FOwner.ReadBytes(Offset, 2))
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffRecord.GetSize: Cardinal;
|
|||
|
begin
|
|||
|
Result := FOwner.ReadBytes(Offset + 2, 2)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.SetSize(n: Cardinal);
|
|||
|
begin
|
|||
|
Assert(n <= BiffMaxRecLen);
|
|||
|
FOwner.WriteBytes(Offset + 2, n, 2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.Write(const Buffer; Count: Cardinal);
|
|||
|
begin
|
|||
|
if Count > 0 then
|
|||
|
FOwner.AppendRecord(Self, Buffer, Count);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.WriteConst(Value, Size: Cardinal);
|
|||
|
begin
|
|||
|
Assert((Size > 0) and (Size <= SizeOf(Value)));
|
|||
|
Write(Value, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.WriteStream(Stream: TStream);
|
|||
|
var
|
|||
|
Buffer: array[0..63] of Byte;
|
|||
|
n: Integer;
|
|||
|
begin
|
|||
|
Stream.Position := 0;
|
|||
|
|
|||
|
repeat
|
|||
|
n := Stream.Read(Buffer[0], SizeOf(Buffer));
|
|||
|
|
|||
|
if n > 0 then
|
|||
|
Write(Buffer[0], n);
|
|||
|
until n = 0;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.WriteZeros(Size: Cardinal);
|
|||
|
begin
|
|||
|
Assert(Size > 0);
|
|||
|
|
|||
|
while Size > SizeOf(Integer) do
|
|||
|
begin
|
|||
|
WriteConst(0, SizeOf(Integer));
|
|||
|
Dec(Size, SizeOf(Integer));
|
|||
|
end;
|
|||
|
|
|||
|
if Size > 0 then
|
|||
|
WriteConst(0, Size);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.WriteBytes(Offset, Data, Count: Cardinal);
|
|||
|
begin
|
|||
|
Assert(Count in [1..SizeOf(Data)]);
|
|||
|
Assert(Offset + Count <= Size);
|
|||
|
|
|||
|
FOwner.FStream.Position := FOffset + 4 + Offset;
|
|||
|
FOwner.FStream.WriteBuffer(Data, Count);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.LoadFromStream(Stream: TStream);
|
|||
|
begin
|
|||
|
if Size > 0 then
|
|||
|
begin
|
|||
|
FOwner.FStream.Position := Offset + 4;
|
|||
|
FOwner.FStream.CopyFrom(Stream, Size);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffRecord.SaveToStream(Stream: TStream);
|
|||
|
begin
|
|||
|
if Size > 0 then
|
|||
|
begin
|
|||
|
FOwner.FStream.Position := Offset + 4;
|
|||
|
Stream.CopyFrom(FOwner.FStream, Size);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure SerialiseBiffRecord(Stream: TStream; Rec: TObject);
|
|||
|
begin
|
|||
|
with Rec as TBiffRecord do
|
|||
|
begin
|
|||
|
Stream.WriteBuffer(FOwner, SizeOf(FOwner));
|
|||
|
Stream.WriteBuffer(FOffset, SizeOf(FOffset));
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function DeserialiseBiffRecord(Stream: TStream): TObject;
|
|||
|
var
|
|||
|
FOwner: TBiffStream;
|
|||
|
FOffset: Cardinal;
|
|||
|
begin
|
|||
|
with Stream do
|
|||
|
begin
|
|||
|
ReadBuffer(FOwner, SizeOf(FOwner));
|
|||
|
ReadBuffer(FOffset, SizeOf(FOffset));
|
|||
|
end;
|
|||
|
|
|||
|
Result := TBiffRecord.Create(FOwner, FOffset);
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffStream }
|
|||
|
|
|||
|
constructor TBiffStream.Create(Cached: Boolean);
|
|||
|
begin
|
|||
|
if Cached then
|
|||
|
begin
|
|||
|
FStream := TFileStream.Create(GetTempFile, fmCreate);
|
|||
|
FRecords := TListCache.Create(GetTempFile, GetTempFile);
|
|||
|
FRecords.WriteObj := SerialiseBiffRecord;
|
|||
|
FRecords.ReadObj := DeserialiseBiffRecord;
|
|||
|
end
|
|||
|
else
|
|||
|
begin
|
|||
|
FStream := TMemoryStream.Create;
|
|||
|
FRecords := TListCache.Create;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffStream.Destroy;
|
|||
|
begin
|
|||
|
FStream.Free;
|
|||
|
FRecords.Free;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.GetRecCount: Integer;
|
|||
|
begin
|
|||
|
Result := FRecords.Count
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.GetRecord(Index: LongInt): TBiffRecord;
|
|||
|
begin
|
|||
|
Result := TBiffRecord(FRecords[Index])
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.GetSize: Cardinal;
|
|||
|
begin
|
|||
|
Result := FStream.Size
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStream.SaveToStream(Stream: TStream);
|
|||
|
begin
|
|||
|
Stream.CopyFrom(FStream, 0)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.ReadBytes(Offset, Count: Cardinal): Cardinal;
|
|||
|
begin
|
|||
|
Assert((Count in [1..SizeOf(Result)]) and (Offset + Count <= Size));
|
|||
|
Result := 0;
|
|||
|
FStream.Position := Offset;
|
|||
|
FStream.ReadBuffer(Result, Count);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStream.WriteBytes(Offset, Data, Count: Cardinal);
|
|||
|
begin
|
|||
|
Assert((Count in [1..SizeOf(Data)]) and (Offset + Count <= Size));
|
|||
|
FStream.Position := Offset;
|
|||
|
FStream.WriteBuffer(Data, Count);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.Add(Id: TBiffRecId): TBiffRecord;
|
|||
|
begin
|
|||
|
Result := TBiffRecord.Create(Self, Size);
|
|||
|
FRecords.Add(Result);
|
|||
|
|
|||
|
if Id <> BiffIdContinue then
|
|||
|
FLastRec := Result;
|
|||
|
|
|||
|
Append(Id, 2); // record id
|
|||
|
Append(0, 2); // record size (will be updated)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.AddBOF(K: TBiffStreamKind): TBiffRecord;
|
|||
|
var
|
|||
|
r: TBiffrBOF;
|
|||
|
begin
|
|||
|
ZeroMemory(@r, SizeOf(r));
|
|||
|
|
|||
|
r.Version := $600;
|
|||
|
r.Year := 1997;
|
|||
|
r.Kind := K;
|
|||
|
r.Build := 8211;
|
|||
|
r.Flags1 := BiffBoffWin or (3 shl 14);
|
|||
|
r.Flags1 := r.Flags1 or BiffBoffWinAny;
|
|||
|
r.Flags2 := 6 or (3 shl 8);
|
|||
|
|
|||
|
Result := Add(BiffIdBOF);
|
|||
|
Result.Write(r, SizeOf(r));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffStream.AddEOF: TBiffRecord;
|
|||
|
begin
|
|||
|
Result := Add(BiffIdEOF)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStream.AppendData(const Data; Count: Cardinal);
|
|||
|
begin
|
|||
|
Assert(@Data <> nil);
|
|||
|
Assert(Count > 0);
|
|||
|
FStream.Seek(0, soFromEnd);
|
|||
|
FStream.WriteBuffer(Data, Count);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStream.Append(Data, Count: Cardinal);
|
|||
|
begin
|
|||
|
Assert(Count in [1..SizeOf(Data)]);
|
|||
|
AppendData(Data, Count)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffStream.AppendRecord(Rec: TBiffRecord; const Data; DataSize: Cardinal);
|
|||
|
|
|||
|
procedure MoveData(var Src: Pointer; var Size: Cardinal; Count: Cardinal);
|
|||
|
begin
|
|||
|
Assert(Count <= Size);
|
|||
|
|
|||
|
if Count > 0 then
|
|||
|
begin
|
|||
|
AppendData(Src^, Count);
|
|||
|
Dec(Size, Count);
|
|||
|
Src := Pointer(frxCardinal(Src) + Count);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
n: Cardinal;
|
|||
|
DataPtr: Pointer;
|
|||
|
begin
|
|||
|
Assert(Count > 0);
|
|||
|
Assert(@Data <> nil);
|
|||
|
Assert((Rec = FLastRec) or (Rec = Records[Count - 1]), 'Only the last record can be modified');
|
|||
|
|
|||
|
DataPtr := @Data;
|
|||
|
n := Min(DataSize, BiffMaxRecLen - Records[Count - 1].Size);
|
|||
|
MoveData(DataPtr, DataSize, n);
|
|||
|
|
|||
|
with Records[Count - 1] do
|
|||
|
Size := Size + n;
|
|||
|
|
|||
|
{ The data chunk is too big: CONTINUE records are needed.
|
|||
|
[MS-XLS] 2.4.58 }
|
|||
|
|
|||
|
while DataSize > 0 do
|
|||
|
begin
|
|||
|
FRecords.Add(TBiffRecord.Create(Self, Size));
|
|||
|
n := Min(BiffMaxRecLen, DataSize);
|
|||
|
|
|||
|
Append(BiffIdContinue, 2);
|
|||
|
Append(n, 2);
|
|||
|
MoveData(DataPtr, DataSize, n);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffLinkTable }
|
|||
|
|
|||
|
function TBiffLinkTable.GetInternalSheetRef(const SheetName: string): Integer;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FindSheet(SheetName);
|
|||
|
|
|||
|
if i = RefsCount then
|
|||
|
begin
|
|||
|
RefsCount := RefsCount + 1;
|
|||
|
FIntSheetNames[RefsCount - 1] := SheetName;
|
|||
|
end;
|
|||
|
|
|||
|
Result := i;
|
|||
|
Assert(FIntSheetNames[i] = SheetName);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffLinkTable.GetRefsCount: Integer;
|
|||
|
begin
|
|||
|
Result := Length(FIntSheetNames)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffLinkTable.SetRefsCount(n: Integer);
|
|||
|
begin
|
|||
|
SetLength(FIntSheetNames, n);
|
|||
|
SetLength(FIntSheetIndex, n);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffLinkTable.SetSheetIndex(const SheetName: string; Index: Integer);
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FindSheet(SheetName);
|
|||
|
|
|||
|
if i < RefsCount then
|
|||
|
FIntSheetIndex[i] := Index;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffLinkTable.FindSheet(const Name: string): Integer;
|
|||
|
begin
|
|||
|
for Result := 0 to RefsCount - 1 do
|
|||
|
if FIntSheetNames[Result] = Name then
|
|||
|
Exit;
|
|||
|
|
|||
|
Result := RefsCount;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffLinkTable.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
with Stream.Add(BiffIdExtBook) do
|
|||
|
begin
|
|||
|
WriteConst(SheetsCount, 2);
|
|||
|
WriteConst(1, 1);
|
|||
|
WriteConst(4, 1);
|
|||
|
end;
|
|||
|
|
|||
|
with Stream.Add(BiffIdExtSheet) do
|
|||
|
begin
|
|||
|
WriteConst(RefsCount, 2);
|
|||
|
|
|||
|
for i := 0 to RefsCount - 1 do
|
|||
|
begin
|
|||
|
WriteConst(0, 2); // index to EXTBOOK
|
|||
|
WriteConst(FIntSheetIndex[i], 2); // index to first sheet
|
|||
|
WriteConst(FIntSheetIndex[i], 2); // index to last sheet
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaCell }
|
|||
|
|
|||
|
constructor TBiffFormulaCell.Create(Instructions: TStream);
|
|||
|
begin
|
|||
|
FInst := Instructions;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffFormulaCell.Destroy;
|
|||
|
begin
|
|||
|
FInst.Free;
|
|||
|
inherited;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCell.Flush(Stream: TBiffStream);
|
|||
|
var
|
|||
|
f: TBiffrFormula;
|
|||
|
begin
|
|||
|
ZeroMemory(@f, SizeOf(f));
|
|||
|
|
|||
|
with f do
|
|||
|
begin
|
|||
|
Cell.Row := Row;
|
|||
|
Cell.Column := Col;
|
|||
|
Cell.XF := XF;
|
|||
|
|
|||
|
Value := 0;
|
|||
|
InstLen := FInst.Size;
|
|||
|
end;
|
|||
|
|
|||
|
with Stream.Add(BiffIdFormula) do
|
|||
|
begin
|
|||
|
Write(f, Sizeof(f));
|
|||
|
WriteStream(FInst);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaCodeEmitter }
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Error(const ErrorMsg: string);
|
|||
|
begin
|
|||
|
raise EBiffFormulaCodeEmitterError.Create(ErrorMsg)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Ensure(b: Boolean; const ErrorMsg: string);
|
|||
|
begin
|
|||
|
if not b then
|
|||
|
Error(ErrorMsg)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.EnsureCellRange(const Cell: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
Ensure((Cell.Row >= 0) and (Cell.Row < $10000), 'Row index is out of bounds');
|
|||
|
Ensure((Cell.Col >= 0) and (Cell.Col < $100), 'Column index is out of bounds');
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Write(Inst, Len: Cardinal);
|
|||
|
begin
|
|||
|
Assert((Len > 0) and (Len <= SizeOf(Inst)));
|
|||
|
Assert(FInst <> nil);
|
|||
|
FInst.WriteBuffer(Inst, Len);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.WriteAreaRef(const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
EnsureCellRange(Cell1);
|
|||
|
EnsureCellRange(Cell2);
|
|||
|
|
|||
|
Write(Cell1.Row, 2);
|
|||
|
Write(Cell2.Row, 2);
|
|||
|
Write(Cell1.Col, 1);
|
|||
|
Write(RelFlags(not Cell1.AbsRow, not Cell1.AbsCol));
|
|||
|
Write(Cell2.Col, 1);
|
|||
|
Write(RelFlags(not Cell2.AbsRow, not Cell2.AbsCol));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.WriteCellRef(const Cell: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
EnsureCellRange(Cell);
|
|||
|
|
|||
|
Write(Cell.Row, 2);
|
|||
|
Write(Cell.Col, 1);
|
|||
|
Write(RelFlags(not Cell.AbsRow, not Cell.AbsCol));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCodeEmitter.RelFlags(RelRow, RelCol: Boolean): Byte;
|
|||
|
begin
|
|||
|
Result := 0;
|
|||
|
|
|||
|
if RelCol then
|
|||
|
Result := Result or $40;
|
|||
|
|
|||
|
if RelRow then
|
|||
|
Result := Result or $80;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.WriteOpCode(Op: Byte);
|
|||
|
begin
|
|||
|
Assert(Op and $e0 = 0);
|
|||
|
|
|||
|
case RetMode of
|
|||
|
frtRef: Op := Op or $20;
|
|||
|
frtVal: Op := Op or $40;
|
|||
|
frtArray: Op := Op or $60;
|
|||
|
|
|||
|
else Error('Invalid RetType mode');
|
|||
|
end;
|
|||
|
|
|||
|
Write(Op, 1)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Range;
|
|||
|
begin
|
|||
|
Write($11)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Intersect;
|
|||
|
begin
|
|||
|
Write($0f)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Join;
|
|||
|
begin
|
|||
|
Write($08)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CallId;
|
|||
|
begin
|
|||
|
Write($15)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpE;
|
|||
|
begin
|
|||
|
Write($0b)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpG;
|
|||
|
begin
|
|||
|
Write($0d)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpGE;
|
|||
|
begin
|
|||
|
Write($0c)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpL;
|
|||
|
begin
|
|||
|
Write($09)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpLE;
|
|||
|
begin
|
|||
|
Write($0a)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.CmpNE;
|
|||
|
begin
|
|||
|
Write($0e)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Div100;
|
|||
|
begin
|
|||
|
Write($14)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Divide;
|
|||
|
begin
|
|||
|
Write($06)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Mul;
|
|||
|
begin
|
|||
|
Write($05)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Neg;
|
|||
|
begin
|
|||
|
Write($13)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Add;
|
|||
|
begin
|
|||
|
Write($03)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Sub;
|
|||
|
begin
|
|||
|
Write($04)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Pow;
|
|||
|
begin
|
|||
|
Write($07);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Push(Value: Integer);
|
|||
|
begin
|
|||
|
Ensure((Value >= 0) and (Value < $10000));
|
|||
|
Write($1e);
|
|||
|
Write(Value, 2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Push(Value: Double);
|
|||
|
begin
|
|||
|
Write($1f);
|
|||
|
FInst.WriteBuffer(Value, 8);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.PushNull;
|
|||
|
begin
|
|||
|
Write($16);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Push(const s: string);
|
|||
|
begin
|
|||
|
Ensure(Length(s) < $100, 'String is too long');
|
|||
|
|
|||
|
Write($17);
|
|||
|
Write(Length(s));
|
|||
|
|
|||
|
if SizeOf(s[1]) = 1 then
|
|||
|
Write(0)
|
|||
|
else
|
|||
|
Write(1);
|
|||
|
|
|||
|
if s <> '' then
|
|||
|
FInst.WriteBuffer(s[1], Length(s) * SizeOf(s[1]));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Push(b: Boolean);
|
|||
|
begin
|
|||
|
Write($1d);
|
|||
|
|
|||
|
if b then
|
|||
|
Write(1)
|
|||
|
else
|
|||
|
Write(0);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Call(Func: Cardinal);
|
|||
|
begin
|
|||
|
Ensure(Func < $10000);
|
|||
|
|
|||
|
WriteOpCode($01);
|
|||
|
Write(Func, 2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.Call(Func, NumArgs: Cardinal);
|
|||
|
begin
|
|||
|
Ensure(Func < $8000);
|
|||
|
Ensure(NumArgs < $80);
|
|||
|
|
|||
|
WriteOpCode($02);
|
|||
|
Write(NumArgs and $7f);
|
|||
|
Write(Func and $7fff, 2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.PushArea(const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
WriteOpCode($05);
|
|||
|
WriteAreaRef(Cell1, Cell2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.PushCell(const Cell: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
WriteOpCode($04);
|
|||
|
WriteCellRef(Cell);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.PushExtArea(SheetRefId: Integer; const Cell1, Cell2: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
Ensure((SheetRefId >= 0) and (SheetRefId < $10000));
|
|||
|
|
|||
|
WriteOpCode($1b);
|
|||
|
Write(SheetRefId, 2);
|
|||
|
WriteAreaRef(Cell1, Cell2);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCodeEmitter.PushExtCell(SheetRefId: Integer; const Cell: TBiffFormulaCellRef);
|
|||
|
begin
|
|||
|
Ensure((SheetRefId >= 0) and (SheetRefId < $10000));
|
|||
|
|
|||
|
WriteOpCode($1a);
|
|||
|
Write(SheetRefId, 2);
|
|||
|
WriteCellRef(Cell);
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaLexer }
|
|||
|
|
|||
|
procedure TBiffFormulaLexer.Add(const Lex: TBiffFormulaLexem);
|
|||
|
begin
|
|||
|
SetLength(FLexems, Length(FLexems) + 1);
|
|||
|
FLexems[High(FLexems)] := Lex;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaLexer.Add(LexKind: TBiffFormulaLexemKind; const LexText: string);
|
|||
|
var
|
|||
|
Lex: TBiffFormulaLexem;
|
|||
|
begin
|
|||
|
Lex.Kind := LexKind;
|
|||
|
Lex.Text := LexText;
|
|||
|
Add(Lex);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.GetLexemsCount: Integer;
|
|||
|
begin
|
|||
|
Result := Length(FLexems)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.IsAlpha(c: Char): Boolean;
|
|||
|
begin
|
|||
|
Result := AnsiChar(c) in ['a'..'z', 'A'..'Z']
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.IsDigit(c: Char): Boolean;
|
|||
|
begin
|
|||
|
Result := AnsiChar(c) in ['0'..'9']
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.NextChar: Char;
|
|||
|
begin
|
|||
|
Assert(FPos <= Length(FText));
|
|||
|
Result := FText[FPos + 1];
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.Read(Len: Integer): string;
|
|||
|
begin
|
|||
|
Assert(FPos + Len <= Length(FText) + 1);
|
|||
|
Result := Copy(FText, FPos, Len);
|
|||
|
Inc(FPos, Len);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaLexer.SkipChar;
|
|||
|
begin
|
|||
|
Assert(FPos <= Length(FText));
|
|||
|
Inc(FPos);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.SubStr(Pos, Len: Integer): string;
|
|||
|
begin
|
|||
|
Assert(Pos + Len <= Length(FText) + 1);
|
|||
|
Result := Copy(FText, Pos, Len);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.GetChar: Char;
|
|||
|
begin
|
|||
|
Assert((FPos >= 0) and (FPos <= Length(FText) + 1));
|
|||
|
|
|||
|
if FPos = Length(FText) + 1 then
|
|||
|
Result := #0
|
|||
|
else
|
|||
|
Result := FText[FPos];
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.GetLexem(i: Integer): TBiffFormulaLexem;
|
|||
|
begin
|
|||
|
Assert((i >= 0) and (i < Count));
|
|||
|
Result := FLexems[i];
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaLexer.Analyse(const Formula: string);
|
|||
|
begin
|
|||
|
SetLength(FLexems, 0);
|
|||
|
FText := Formula;
|
|||
|
FPos := 1;
|
|||
|
|
|||
|
while FPos <= Length(FText) do
|
|||
|
if not AddSpace then
|
|||
|
if not AddNumber then
|
|||
|
if not AddName then
|
|||
|
if not AddString('"') then
|
|||
|
if not AddString('''') then
|
|||
|
if not AddOp then
|
|||
|
if not AddSymbol then
|
|||
|
Inc(FPos);
|
|||
|
|
|||
|
Assert(FPos = Length(FText) + 1);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddName: Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FPos;
|
|||
|
|
|||
|
if IsAlpha(GetChar) then
|
|||
|
while IsAlpha(GetChar) do
|
|||
|
SkipChar;
|
|||
|
|
|||
|
Result := i < FPos;
|
|||
|
|
|||
|
if Result then
|
|||
|
Add(flkName, SubStr(i, FPos - i));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddNumber: Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FPos;
|
|||
|
|
|||
|
if IsDigit(GetChar) then
|
|||
|
while IsDigit(GetChar) do
|
|||
|
SkipChar;
|
|||
|
|
|||
|
Result := i < FPos;
|
|||
|
|
|||
|
if Result then
|
|||
|
Add(flkInt, SubStr(i, FPos - i));
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddOp: Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FPos;
|
|||
|
|
|||
|
case GetChar of
|
|||
|
'+', '-', '*', '/', '%', '^', '&', ':', '!', '=':
|
|||
|
Add(flkOp, Read(1));
|
|||
|
'<':
|
|||
|
if AnsiChar(NextChar) in ['>', '='] then
|
|||
|
Add(flkOp, Read(2))
|
|||
|
else
|
|||
|
Add(flkOp, Read(1));
|
|||
|
'>':
|
|||
|
if NextChar = '=' then
|
|||
|
Add(flkOp, Read(2))
|
|||
|
else
|
|||
|
Add(flkOp, Read(1));
|
|||
|
end;
|
|||
|
|
|||
|
Result := i < FPos;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddSpace: Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FPos;
|
|||
|
|
|||
|
while AnsiChar(GetChar) in [#1..#32] do
|
|||
|
SkipChar;
|
|||
|
|
|||
|
Result := i < FPos;
|
|||
|
|
|||
|
if Result then
|
|||
|
Add(flkSpace, '');
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddString(Quote: Char): Boolean;
|
|||
|
|
|||
|
function Clean(const s: string): string;
|
|||
|
begin
|
|||
|
Result := StringReplace(s, '""', '"', [rfReplaceAll])
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
i := FPos;
|
|||
|
|
|||
|
if GetChar = Quote then
|
|||
|
begin
|
|||
|
SkipChar;
|
|||
|
|
|||
|
while GetChar <> #0 do
|
|||
|
if GetChar <> Quote then
|
|||
|
SkipChar
|
|||
|
else if NextChar <> Quote then
|
|||
|
Break
|
|||
|
else
|
|||
|
begin
|
|||
|
SkipChar;
|
|||
|
SkipChar;
|
|||
|
end;
|
|||
|
|
|||
|
if GetChar = Quote then
|
|||
|
SkipChar
|
|||
|
else
|
|||
|
FPos := i;
|
|||
|
end;
|
|||
|
|
|||
|
Result := i < FPos;
|
|||
|
|
|||
|
if Result then
|
|||
|
Add(flkString, Clean(SubStr(i + 1, FPos - i - 2)))
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaLexer.AddSymbol: Boolean;
|
|||
|
begin
|
|||
|
Add(flkSymbol, Read(1));
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaRPNStack }
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.Top(const a: TBiffFormulaTokenArray): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Assert(Length(a) > 0);
|
|||
|
Result := a[High(a)];
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.Pop(var a: TBiffFormulaTokenArray): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Result := Top(a);
|
|||
|
SetLength(a, Length(a) - 1);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.Push(var Res: TBiffFormulaTokenArray; const t: TBiffFormulaToken);
|
|||
|
begin
|
|||
|
SetLength(Res, Length(Res) + 1);
|
|||
|
Res[High(Res)] := t;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PushFrame(const Func: string);
|
|||
|
var
|
|||
|
t: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
t.Kind := ftkOp;
|
|||
|
t.Op := fokFunc;
|
|||
|
t.Text := Func;
|
|||
|
t.Flags := Length(FStack) shl 8 or Byte(FNumArgs);
|
|||
|
|
|||
|
Push(FFrame, t);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PopFrame;
|
|||
|
var
|
|||
|
Func: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Unroll;
|
|||
|
Func := Top(FFrame);
|
|||
|
|
|||
|
if Func.Text = '' then
|
|||
|
Pop(FFrame) // Func is the identity function
|
|||
|
else
|
|||
|
begin
|
|||
|
Func.Flags := GetFrameArgs;
|
|||
|
Dec(FNumArgs, GetFrameArgs - 1);
|
|||
|
Pop(FFrame);
|
|||
|
Push(FCode, Func);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PushOp(Op: TBiffFormulaOperatorKind; const Text: string; NumArgs: Integer);
|
|||
|
var
|
|||
|
t: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
t.Kind := ftkOp;
|
|||
|
t.Op := Op;
|
|||
|
t.Text := Text;
|
|||
|
t.Flags := NumArgs;
|
|||
|
|
|||
|
PushOp(t);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PushOp(Op: TBiffFormulaToken);
|
|||
|
begin
|
|||
|
if Joinable(Op.Op) then
|
|||
|
Push(FStack, Op)
|
|||
|
else
|
|||
|
begin
|
|||
|
PopOp;
|
|||
|
PushOp(Op); // recusrion
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PopOp;
|
|||
|
var
|
|||
|
Op: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Op := Pop(FStack);
|
|||
|
Push(FCode, Op);
|
|||
|
Dec(FNumArgs, Op.Flags - 1);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.PushArg(Op: TBiffFormulaOperatorKind; const Text: string; Flags: Integer);
|
|||
|
var
|
|||
|
t: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
t.Kind := ftkArg;
|
|||
|
t.Op := Op;
|
|||
|
t.Text := Text;
|
|||
|
t.Flags := Flags;
|
|||
|
|
|||
|
Push(FCode, t);
|
|||
|
Inc(FNumArgs);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.Ensure(b: Boolean; const Msg: string);
|
|||
|
begin
|
|||
|
if not b then
|
|||
|
Error(Msg)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.Error(const Msg: string);
|
|||
|
begin
|
|||
|
raise EBiffFormulaRPNStackError.Create(Msg)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.GetFrameArgs: Integer;
|
|||
|
begin
|
|||
|
if Length(FFrame) = 0 then
|
|||
|
Result := FNumArgs
|
|||
|
else
|
|||
|
Result := FNumArgs - (Top(FFrame).Flags and $ff);
|
|||
|
|
|||
|
Assert(Result >= 0);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.GetFrameOps: Integer;
|
|||
|
begin
|
|||
|
if Length(FFrame) = 0 then
|
|||
|
Result := Length(FStack)
|
|||
|
else
|
|||
|
Result := Length(FStack) - (Top(FFrame).Flags shr 8);
|
|||
|
|
|||
|
Assert(Result >= 0);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.OpPriority(Kind: TBiffFormulaOperatorKind): Integer;
|
|||
|
begin
|
|||
|
case Kind of
|
|||
|
fokPush:
|
|||
|
Result := 8;
|
|||
|
|
|||
|
fokColon, fokArea:
|
|||
|
Result := 7;
|
|||
|
|
|||
|
fokExt:
|
|||
|
Result := 6;
|
|||
|
|
|||
|
fokNeg, fokDiv100:
|
|||
|
Result := 5;
|
|||
|
|
|||
|
fokPow:
|
|||
|
Result := 4;
|
|||
|
|
|||
|
fokDiv, fokMul, fokIsect:
|
|||
|
Result := 3;
|
|||
|
|
|||
|
fokAdd, fokSub:
|
|||
|
Result := 2;
|
|||
|
|
|||
|
fokL, fokG, fokE, fokNE, fokLE, fokGE:
|
|||
|
Result := 1;
|
|||
|
|
|||
|
else
|
|||
|
Result := 0; // unrecognized operators have the lowest priority
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.GetCount: Integer;
|
|||
|
begin
|
|||
|
Result := Length(FCode)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.GetInstruction(i: Integer): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Assert((i >= 0) and (i < Count));
|
|||
|
Result := FCode[i];
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaRPNStack.Unroll;
|
|||
|
var
|
|||
|
Op: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
while GetFrameOps > 0 do
|
|||
|
begin
|
|||
|
Op := Pop(FStack);
|
|||
|
Ensure(GetFrameArgs >= Op.Flags);
|
|||
|
Push(FCode, Op);
|
|||
|
Dec(FNumArgs, Op.Flags - 1);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaRPNStack.Joinable(Op: TBiffFormulaOperatorKind): Boolean;
|
|||
|
var
|
|||
|
a, b: TBiffFormulaOperatorKind;
|
|||
|
begin
|
|||
|
if GetFrameOps = 0 then
|
|||
|
Result := True // this is the first op in the current frame
|
|||
|
else
|
|||
|
begin
|
|||
|
a := Top(FStack).Op;
|
|||
|
b := Op;
|
|||
|
|
|||
|
{ Given an expression (X a Y b Z) where a and b are operators,
|
|||
|
this function must return True if (Y b Z) must be calculated first;
|
|||
|
and return False if (X a Y) must be calculated first. }
|
|||
|
|
|||
|
if OpPriority(a) < OpPriority(b) then
|
|||
|
Result := True
|
|||
|
|
|||
|
else if OpPriority(a) > OpPriority(b) then
|
|||
|
Result := False
|
|||
|
|
|||
|
else if (a = fokPow) and (b = fokPow) then
|
|||
|
Result := True
|
|||
|
|
|||
|
else
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaParser }
|
|||
|
|
|||
|
procedure TBiffFormulaParser.BuildLexems(const s: string);
|
|||
|
var
|
|||
|
Lex: TBiffFormulaLexer;
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
Lex := TBiffFormulaLexer.Create;
|
|||
|
|
|||
|
try
|
|||
|
Lex.Formula := s;
|
|||
|
SetLength(FLexems, Lex.Count);
|
|||
|
|
|||
|
for i := 0 to Lex.Count - 1 do
|
|||
|
FLexems[i] := Lex[i];
|
|||
|
finally
|
|||
|
Lex.Free;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.CleanLexems;
|
|||
|
var
|
|||
|
n: Integer;
|
|||
|
|
|||
|
procedure Move(i: Integer);
|
|||
|
begin
|
|||
|
Assert(i >= n);
|
|||
|
|
|||
|
if i > n then
|
|||
|
FLexems[n] := FLexems[i];
|
|||
|
|
|||
|
Inc(n);
|
|||
|
end;
|
|||
|
|
|||
|
function Lex(i: Integer): TBiffFormulaLexem;
|
|||
|
begin
|
|||
|
if (i < 0) or (i > High(FLexems)) then
|
|||
|
Result.Kind := flkVoid
|
|||
|
else
|
|||
|
Result := FLexems[i]
|
|||
|
end;
|
|||
|
|
|||
|
function CanIgnoreSpace(i: Integer): Boolean;
|
|||
|
var
|
|||
|
a, b: TBiffFormulaLexem;
|
|||
|
begin
|
|||
|
a := Lex(i - 1);
|
|||
|
b := Lex(i + 1);
|
|||
|
|
|||
|
Result :=
|
|||
|
(a.Kind = flkVoid) or (b.Kind = flkVoid) or
|
|||
|
(a.Kind = flkOp) or (b.Kind = flkOp) or
|
|||
|
(a.Text = '(') or (b.Text = ')') or
|
|||
|
IsArgSep(a) or IsArgSep(b)
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
n := 0;
|
|||
|
|
|||
|
for i := 0 to High(FLexems) do
|
|||
|
with FLexems[i] do
|
|||
|
case Kind of
|
|||
|
flkSpace:
|
|||
|
if not CanIgnoreSpace(i) then
|
|||
|
Move(i);
|
|||
|
|
|||
|
else
|
|||
|
Move(i);
|
|||
|
end;
|
|||
|
|
|||
|
SetLength(FLexems, n);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.IsArgSep(const Lex: TBiffFormulaLexem): Boolean;
|
|||
|
begin
|
|||
|
Result := (Lex.Kind = flkSymbol) and ((Lex.Text = ',') or (Lex.Text = ';'))
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.IsOp(const Lex: TBiffFormulaLexem; const Text: string): Boolean;
|
|||
|
begin
|
|||
|
Result := (Lex.Kind = flkOp) and (Lex.Text = Text)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Ensure(b: Boolean; const ErrorMsg: string);
|
|||
|
begin
|
|||
|
if not b then
|
|||
|
Error(ErrorMsg)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Error(const ErrorMsg: string);
|
|||
|
begin
|
|||
|
raise EBiffFormulaParserError.Create(ErrorMsg)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.GetToken(i: Integer): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Assert((i >= 0) and (i < Length(FCode)));
|
|||
|
Result := FCode[i];
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.GetTokensCount: Integer;
|
|||
|
begin
|
|||
|
Result := Length(FCode)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.CreateArgToken(Kind: TBiffFormulaOperatorKind;
|
|||
|
const Text: string; Flags: Integer): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Result.Kind := ftkArg;
|
|||
|
Result.Op := Kind;
|
|||
|
Result.Text := Text;
|
|||
|
Result.Flags := Flags;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.Lexem(i: Integer): TBiffFormulaLexem;
|
|||
|
|
|||
|
function GetLexKind(i: Integer): TBiffFormulaLexemKind;
|
|||
|
begin
|
|||
|
if (i >= 0) and (i < Length(FLexems)) then
|
|||
|
Result := FLexems[i].Kind
|
|||
|
else
|
|||
|
Result := flkVoid
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
case GetLexKind(FPos + i) of
|
|||
|
flkVoid:
|
|||
|
Result.Kind := flkVoid;
|
|||
|
|
|||
|
flkSpace:
|
|||
|
begin
|
|||
|
Result.Kind := flkOp;
|
|||
|
Result.Text := ' ';
|
|||
|
end;
|
|||
|
|
|||
|
else
|
|||
|
Result := FLexems[FPos + i];
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.SkipLexem;
|
|||
|
begin
|
|||
|
Assert(FPos < Length(FLexems));
|
|||
|
Inc(FPos);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.SkipLexemIf(Kind: TBiffFormulaLexemKind; out Text: string): Boolean;
|
|||
|
begin
|
|||
|
Result := Lexem.Kind = Kind;
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
Text := Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.SkipLexemIf(const Text: string): Boolean;
|
|||
|
begin
|
|||
|
Result := Lexem.Text = Text;
|
|||
|
|
|||
|
if Result then
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Save;
|
|||
|
begin
|
|||
|
SetLength(FSavedPos, Length(FSavedPos) + 1);
|
|||
|
FSavedPos[High(FSavedPos)] := FPos;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Load;
|
|||
|
begin
|
|||
|
Assert(Length(FSavedPos) > 0);
|
|||
|
FPos := FSavedPos[High(FSavedPos)];
|
|||
|
SetLength(FSavedPos, Length(FSavedPos) - 1);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Discard;
|
|||
|
begin
|
|||
|
Assert(Length(FSavedPos) > 0);
|
|||
|
SetLength(FSavedPos, Length(FSavedPos) - 1);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.OpKind(const s: string): TBiffFormulaOperatorKind;
|
|||
|
begin
|
|||
|
Assert(s <> '');
|
|||
|
Result := fokVoid;
|
|||
|
|
|||
|
case Length(s) of
|
|||
|
1:
|
|||
|
case s[1] of
|
|||
|
'+': Result := fokAdd;
|
|||
|
'-': Result := fokSub;
|
|||
|
'*': Result := fokMul;
|
|||
|
'/': Result := fokDiv;
|
|||
|
'^': Result := fokPow;
|
|||
|
'<': Result := fokL;
|
|||
|
'>': Result := fokG;
|
|||
|
'=': Result := fokE;
|
|||
|
':': Result := fokColon;
|
|||
|
' ': Result := fokIsect;
|
|||
|
'!': Result := fokExt;
|
|||
|
'&': Result := fokJoin;
|
|||
|
end;
|
|||
|
|
|||
|
2:
|
|||
|
if s = '<=' then
|
|||
|
Result := fokLE
|
|||
|
|
|||
|
else if s = '>=' then
|
|||
|
Result := fokGE
|
|||
|
|
|||
|
else if s = '<>' then
|
|||
|
Result := fokNE;
|
|||
|
end;
|
|||
|
|
|||
|
Ensure(Result <> fokVoid, Format('Operator "%s" is unknown', [s]));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Push(Arg: TBiffFormulaOperatorKind; const Text: string; Flags: Integer);
|
|||
|
begin
|
|||
|
FRPN.PushArg(Arg, Text, Flags);
|
|||
|
FRPN.PushOp(fokPush, 'push', 1);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.CopyCodeFromRPNStack;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
SetLength(FCode, FRPN.Count);
|
|||
|
|
|||
|
for i := 0 to FRPN.Count - 1 do
|
|||
|
FCode[i] := FRPN[i];
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaParser.Parse(const s: string);
|
|||
|
begin
|
|||
|
BuildLexems(s);
|
|||
|
CleanLexems;
|
|||
|
FPos := 0;
|
|||
|
|
|||
|
try
|
|||
|
FRPN := TBiffFormulaRPNStack.Create;
|
|||
|
Ensure(ParseFormula);
|
|||
|
CopyCodeFromRPNStack;
|
|||
|
finally
|
|||
|
FRPN.Free;
|
|||
|
FRPN := nil;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadCell(out t: TBiffFormulaToken): Boolean;
|
|||
|
var
|
|||
|
AbsCol, AbsRow: Boolean;
|
|||
|
ColName, RowName: string;
|
|||
|
Flags: Integer;
|
|||
|
begin
|
|||
|
Result := False;
|
|||
|
Save;
|
|||
|
|
|||
|
AbsCol := SkipLexemIf('$');
|
|||
|
|
|||
|
if not SkipLexemIf(flkName, ColName) then
|
|||
|
begin
|
|||
|
Load;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
AbsRow := SkipLexemIf('$');
|
|||
|
|
|||
|
if not SkipLexemIf(flkInt, RowName) then
|
|||
|
begin
|
|||
|
Load;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
Flags := 0;
|
|||
|
|
|||
|
if AbsRow then
|
|||
|
Flags := Flags or 1;
|
|||
|
|
|||
|
if AbsCol then
|
|||
|
Flags := Flags or 2;
|
|||
|
|
|||
|
Discard;
|
|||
|
t := CreateArgToken(fokCell, ColName + RowName, Flags);
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadName(out s: string): Boolean;
|
|||
|
var
|
|||
|
Name: string;
|
|||
|
begin
|
|||
|
if Lexem.Kind <> flkName then
|
|||
|
Result := False
|
|||
|
else
|
|||
|
begin
|
|||
|
Name := Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
|
|||
|
if Lexem.Kind = flkInt then
|
|||
|
begin
|
|||
|
Name := Name + Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
|
|||
|
s := Name;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadNumber(out s: string): Boolean;
|
|||
|
var
|
|||
|
Num: string;
|
|||
|
begin
|
|||
|
if Lexem.Kind <> flkInt then
|
|||
|
Result := False
|
|||
|
else
|
|||
|
begin
|
|||
|
Num := Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
|
|||
|
if Lexem.Text = '.' then
|
|||
|
begin
|
|||
|
Num := Num + '.';
|
|||
|
SkipLexem;
|
|||
|
|
|||
|
if Lexem.Kind = flkInt then
|
|||
|
begin
|
|||
|
Num := Num + Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
end
|
|||
|
else
|
|||
|
Num := Num + '0';
|
|||
|
end;
|
|||
|
|
|||
|
s := Num;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadOp(out s: string): Boolean;
|
|||
|
begin
|
|||
|
Result := Lexem.Kind = flkOp;
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
s := Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadSheet(out s: string): Boolean;
|
|||
|
begin
|
|||
|
Result := ReadString(s) or ReadName(s)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadString(out s: string): Boolean;
|
|||
|
begin
|
|||
|
Result := Lexem.Kind = flkString;
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
s := Lexem.Text;
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ReadSym(const s: string): Boolean;
|
|||
|
begin
|
|||
|
Result := (Lexem.Kind <> flkVoid) and (Lexem.Text = s);
|
|||
|
|
|||
|
if Result then
|
|||
|
SkipLexem;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseTerm: Boolean;
|
|||
|
begin
|
|||
|
Result :=
|
|||
|
ParseExtArea or
|
|||
|
ParseExtCell or
|
|||
|
ParseFuncCall or
|
|||
|
ParseString or
|
|||
|
ParseNumber or
|
|||
|
ParseNameConst or
|
|||
|
ParseArea or
|
|||
|
ParseCell
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseFormula: Boolean;
|
|||
|
begin
|
|||
|
FRPN.PushFrame;
|
|||
|
Ensure(ParsePPTerm);
|
|||
|
|
|||
|
while ParseBinOp do
|
|||
|
Ensure(ParsePPTerm, 'Expression cannot end with an operator');
|
|||
|
|
|||
|
FRPN.PopFrame;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseNameConst: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
begin
|
|||
|
Save;
|
|||
|
Result := ReadName(s);
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
s := AnsiUpperCase(s);
|
|||
|
|
|||
|
if s = 'TRUE' then
|
|||
|
Push(fokBool, s, 1)
|
|||
|
|
|||
|
else if s = 'FALSE' then
|
|||
|
Push(fokBool, s, 0)
|
|||
|
|
|||
|
else
|
|||
|
Result := False;
|
|||
|
end;
|
|||
|
|
|||
|
if Result then
|
|||
|
Discard
|
|||
|
else
|
|||
|
Load
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseNumber: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
begin
|
|||
|
Result := ReadNumber(s);
|
|||
|
|
|||
|
if Result then
|
|||
|
Push(fokNumber, s);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParsePostOp: Boolean;
|
|||
|
begin
|
|||
|
if IsOp(Lexem, '%') then
|
|||
|
begin
|
|||
|
FRPN.PushOp(fokDiv100, '%', 1);
|
|||
|
SkipLexem;
|
|||
|
Result := True;
|
|||
|
end
|
|||
|
else
|
|||
|
Result := False
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParsePPTerm: Boolean;
|
|||
|
begin
|
|||
|
ParsePrefOp;
|
|||
|
Ensure(ParseTerm);
|
|||
|
ParsePostOp;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParsePrefOp: Boolean;
|
|||
|
begin
|
|||
|
if IsOp(Lexem, '-') then
|
|||
|
begin
|
|||
|
FRPN.PushOp(fokNeg, '-', 1);
|
|||
|
SkipLexem;
|
|||
|
Result := True;
|
|||
|
end else if IsOp(Lexem, '+') then
|
|||
|
begin
|
|||
|
SkipLexem;
|
|||
|
Result := True;
|
|||
|
end
|
|||
|
else
|
|||
|
Result := False
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseCell: Boolean;
|
|||
|
var
|
|||
|
t: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Result := ReadCell(t);
|
|||
|
|
|||
|
if Result then
|
|||
|
Push(fokCell, t.Text, t.Flags);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseArea: Boolean;
|
|||
|
var
|
|||
|
c1, c2: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Save;
|
|||
|
Result := ReadCell(c1) and ReadSym(':') and ReadCell(c2);
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
Discard;
|
|||
|
|
|||
|
FRPN.PushArg(fokCell, c1.Text, c1.Flags);
|
|||
|
FRPN.PushArg(fokCell, c2.Text, c2.Flags);
|
|||
|
FRPN.PushOp(fokArea, ':', 2);
|
|||
|
end
|
|||
|
else
|
|||
|
Load;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseExtArea: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
c1, c2: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Save;
|
|||
|
Result := ReadSheet(s) and ReadSym('!') and ReadCell(c1) and ReadSym(':') and ReadCell(c2);
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
Discard;
|
|||
|
|
|||
|
FRPN.PushArg(fokString, s);
|
|||
|
FRPN.PushArg(fokCell, c1.Text, c1.Flags);
|
|||
|
FRPN.PushArg(fokCell, c2.Text, c2.Flags);
|
|||
|
FRPN.PushOp(fokExt, '!', 3);
|
|||
|
end
|
|||
|
else
|
|||
|
Load;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseExtCell: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
c: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
Save;
|
|||
|
Result := ReadSheet(s) and ReadSym('!') and ReadCell(c);
|
|||
|
|
|||
|
if Result then
|
|||
|
begin
|
|||
|
Discard;
|
|||
|
|
|||
|
FRPN.PushArg(fokString, s);
|
|||
|
FRPN.PushArg(fokCell, c.Text, c.Flags);
|
|||
|
FRPN.PushOp(fokExt, '!', 2);
|
|||
|
end
|
|||
|
else
|
|||
|
Load;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseString: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
begin
|
|||
|
Result := ReadString(s);
|
|||
|
|
|||
|
if Result then
|
|||
|
Push(fokString, s);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseBinOp: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
begin
|
|||
|
Result := ReadOp(s);
|
|||
|
|
|||
|
if Result then
|
|||
|
FRPN.PushOp(OpKind(s), s, 2);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaParser.ParseFuncCall: Boolean;
|
|||
|
var
|
|||
|
s: string;
|
|||
|
begin
|
|||
|
Save;
|
|||
|
|
|||
|
if not ReadName(s) then
|
|||
|
s := '';
|
|||
|
|
|||
|
Result := ReadSym('(');
|
|||
|
|
|||
|
if not Result then
|
|||
|
begin
|
|||
|
Load;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
FRPN.PushFrame(s);
|
|||
|
|
|||
|
if Lexem.Text <> ')' then
|
|||
|
begin
|
|||
|
Ensure(ParseFormula, 'Argument expected after "("');
|
|||
|
|
|||
|
while IsArgSep(Lexem) do
|
|||
|
begin
|
|||
|
SkipLexem;
|
|||
|
|
|||
|
if IsArgSep(Lexem) then
|
|||
|
Push(fokNull, 'void')
|
|||
|
else
|
|||
|
Ensure(ParseFormula, 'Argument expected after ","');
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
Ensure(ReadSym(')'));
|
|||
|
|
|||
|
if s = '' then
|
|||
|
FRPN.PushOp(fokId, 'id', 1);
|
|||
|
|
|||
|
FRPN.PopFrame;
|
|||
|
Discard;
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaCompiler }
|
|||
|
|
|||
|
constructor TBiffFormulaCompiler.Create;
|
|||
|
begin
|
|||
|
FParser := TBiffFormulaParser.Create;
|
|||
|
FEmitter := TBiffFormulaCodeEmitter.Create;
|
|||
|
FCode := TMemoryStream.Create;
|
|||
|
|
|||
|
FEmitter.Output := FCode;
|
|||
|
end;
|
|||
|
|
|||
|
destructor TBiffFormulaCompiler.Destroy;
|
|||
|
begin
|
|||
|
FParser.Free;
|
|||
|
FEmitter.Free;
|
|||
|
FCode.Free;
|
|||
|
inherited;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.Ensure(b: Boolean; const Fmt: string; const Args: array of const);
|
|||
|
begin
|
|||
|
Ensure(b, Format(Fmt, Args))
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.Ensure(b: Boolean; const ErrorMsg: string);
|
|||
|
begin
|
|||
|
if not b then
|
|||
|
Error('%s', [ErrorMsg])
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.Error(const Fmt: string; const Args: array of const);
|
|||
|
begin
|
|||
|
raise EBiffFormulaCompilerError.CreateFmt(Fmt, Args)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitArea(const Cell1, Cell2: TBiffFormulaToken);
|
|||
|
begin
|
|||
|
Ensure(IsCell(Cell1) and IsCell(Cell2));
|
|||
|
FEmitter.PushArea(GetCellPos(Cell1), GetCellPos(Cell2));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitExtArea(const Sheet, Cell1, Cell2: TBiffFormulaToken);
|
|||
|
begin
|
|||
|
Ensure(IsStr(Sheet) and IsCell(Cell1) and IsCell(Cell2));
|
|||
|
FEmitter.PushExtArea(LinkTable.GetInternalSheetRef(Sheet.Text), GetCellPos(Cell1), GetCellPos(Cell2));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitExtCell(const Sheet, Cell: TBiffFormulaToken);
|
|||
|
begin
|
|||
|
Ensure(IsStr(Sheet) and IsCell(Cell));
|
|||
|
FEmitter.PushExtCell(LinkTable.GetInternalSheetRef(Sheet.Text), GetCellPos(Cell));
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitFunc(Name: string; NumArgs: Integer);
|
|||
|
var
|
|||
|
Func: TBiffFormulaFunc;
|
|||
|
begin
|
|||
|
Assert(Name <> '');
|
|||
|
Name := AnsiUpperCase(Name);
|
|||
|
Ensure(TBiffFormulaFuncList.Exists(Name), 'Function %s is not supported', [Name]);
|
|||
|
|
|||
|
Func := TBiffFormulaFuncList.Get(Name);
|
|||
|
Assert(Func.Name = Name);
|
|||
|
|
|||
|
Ensure((Func.MinArgs <= NumArgs) and (NumArgs <= Func.MaxArgs),
|
|||
|
'Function %s cannot be called with %d arguments', [Name, NumArgs]);
|
|||
|
|
|||
|
if Func.MinArgs = Func.MaxArgs then
|
|||
|
FEmitter.Call(Func.Id)
|
|||
|
else
|
|||
|
FEmitter.Call(Func.Id, NumArgs);
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitIdFunc(NumArgs: Integer);
|
|||
|
begin
|
|||
|
if NumArgs = 1 then
|
|||
|
FEmitter.CallId
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitNum(Num: Double);
|
|||
|
begin
|
|||
|
|
|||
|
{ A number can be compiled in three ways:
|
|||
|
|
|||
|
double(Num) [9 bytes]
|
|||
|
int(Num) [5 bytes]
|
|||
|
int(Num) neg [6 bytes]
|
|||
|
|
|||
|
This method chooses a way that yields the smallest code. }
|
|||
|
|
|||
|
if (Num <> Ceil(Num)) or (Num > $ffff) or (Num < -$ffff) then
|
|||
|
FEmitter.Push(Num)
|
|||
|
else if Num >= 0 then
|
|||
|
FEmitter.Push(Ceil(Num))
|
|||
|
else
|
|||
|
begin
|
|||
|
FEmitter.Push(Ceil(-Num));
|
|||
|
FEmitter.Neg;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitOp(Kind: TBiffFormulaOperatorKind; NumArgs: Integer);
|
|||
|
begin
|
|||
|
case NumArgs of
|
|||
|
1:
|
|||
|
case Kind of
|
|||
|
fokNeg: FEmitter.Neg;
|
|||
|
fokDiv100: FEmitter.Div100;
|
|||
|
|
|||
|
else Error('Unsupported unary operator %d', [Integer(Kind)]);
|
|||
|
end;
|
|||
|
|
|||
|
2:
|
|||
|
case Kind of
|
|||
|
fokAdd: FEmitter.Add;
|
|||
|
fokSub: FEmitter.Sub;
|
|||
|
fokDiv: FEmitter.Divide;
|
|||
|
fokMul: FEmitter.Mul;
|
|||
|
fokPow: FEmitter.Pow;
|
|||
|
fokL: FEmitter.CmpL;
|
|||
|
fokG: FEmitter.CmpG;
|
|||
|
fokE: FEmitter.CmpE;
|
|||
|
fokNE: FEmitter.CmpNE;
|
|||
|
fokLE: FEmitter.CmpLE;
|
|||
|
fokGE: FEmitter.CmpGE;
|
|||
|
fokIsect: FEmitter.Intersect;
|
|||
|
fokJoin: FEmitter.Join;
|
|||
|
fokColon: FEmitter.Range;
|
|||
|
|
|||
|
else Error('Unsupported binary operator %d', [Integer(Kind)]);
|
|||
|
end;
|
|||
|
|
|||
|
else Error('Unsupported %d-ary operator %d', [NumArgs, Integer(Kind)]);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.EmitPush(const t: TBiffFormulaToken);
|
|||
|
|
|||
|
{ StrToFloat gets decimal separator from locale settings.
|
|||
|
Here this separator is always the dot. }
|
|||
|
|
|||
|
function StrToDouble(const s: string): Double;
|
|||
|
var
|
|||
|
p, n: Integer;
|
|||
|
begin
|
|||
|
Assert(s <> '');
|
|||
|
|
|||
|
n := Length(s);
|
|||
|
p := n;
|
|||
|
|
|||
|
while (p > 0) and (s[p] <> '.') do
|
|||
|
Dec(p);
|
|||
|
|
|||
|
if p = 0 then
|
|||
|
Result := StrToInt(s)
|
|||
|
else if p = n then
|
|||
|
Result := StrToInt(Copy(s, 1, n - 1))
|
|||
|
else if p = 1 then
|
|||
|
Result := StrToDouble('0' + s)
|
|||
|
else
|
|||
|
Result := StrToInt(Copy(s, 1, p - 1)) + StrToInt(Copy(s, p + 1, n - p))/Power(10, n - p)
|
|||
|
end;
|
|||
|
|
|||
|
begin
|
|||
|
case t.Op of
|
|||
|
fokNumber:
|
|||
|
EmitNum(StrToDouble(t.Text));
|
|||
|
|
|||
|
fokNull:
|
|||
|
FEmitter.PushNull;
|
|||
|
|
|||
|
fokCell:
|
|||
|
FEmitter.PushCell(GetCellPos(t));
|
|||
|
|
|||
|
fokString:
|
|||
|
FEmitter.Push(t.Text);
|
|||
|
|
|||
|
fokBool:
|
|||
|
FEmitter.Push(t.Flags <> 0);
|
|||
|
|
|||
|
else
|
|||
|
Error('Cannot push value (%s) of type %d', [t.Text, Integer(t.Op)]);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCompiler.GetCellPos(const t: TBiffFormulaToken): TBiffFormulaCellRef;
|
|||
|
|
|||
|
{ Converts a column name like GH into a zero based column index }
|
|||
|
|
|||
|
function GetColIndex(const s: string): Integer;
|
|||
|
var
|
|||
|
i, n: Integer;
|
|||
|
begin
|
|||
|
Assert(s <> '');
|
|||
|
|
|||
|
n := 1;
|
|||
|
Result := 0;
|
|||
|
|
|||
|
for i := Length(s) downto 1 do
|
|||
|
begin
|
|||
|
Assert(AnsiChar(s[i]) in ['A'..'Z']);
|
|||
|
Inc(Result, n * (Ord(s[i]) - Ord('A') + 1));
|
|||
|
n := n * 26;
|
|||
|
end;
|
|||
|
|
|||
|
Dec(Result);
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
Ref, ColName, RowName: string;
|
|||
|
begin
|
|||
|
Assert(IsCell(t));
|
|||
|
|
|||
|
Ref := AnsiUpperCase(t.Text);
|
|||
|
i := 1;
|
|||
|
|
|||
|
while AnsiChar(Ref[i]) in ['A'..'Z'] do
|
|||
|
Inc(i);
|
|||
|
|
|||
|
Assert((i > 1) and (i <= Length(Ref)));
|
|||
|
|
|||
|
ColName := Copy(Ref, 1, i - 1);
|
|||
|
RowName := Copy(Ref, i, Length(Ref) - i + 1);
|
|||
|
|
|||
|
with Result do
|
|||
|
begin
|
|||
|
Row := StrToInt(RowName) - 1;
|
|||
|
Col := GetColIndex(ColName);
|
|||
|
AbsRow := t.Flags and 1 > 0;
|
|||
|
AbsCol := t.Flags and 2 > 0;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCompiler.GetLinkTable: TBiffLinkTable;
|
|||
|
begin
|
|||
|
Result := FLinkTable;
|
|||
|
Ensure(Result <> nil, 'Link table is not assigned');
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCompiler.IsCell(const t: TBiffFormulaToken): Boolean;
|
|||
|
begin
|
|||
|
Result := (t.Kind = ftkArg) and (t.Op = fokCell) and (Length(t.Text) > 1)
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCompiler.IsStr(const t: TBiffFormulaToken): Boolean;
|
|||
|
begin
|
|||
|
Result := (t.Kind = ftkArg) and (t.Op = fokString)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.SaveToStream(Stream: TStream);
|
|||
|
begin
|
|||
|
Stream.CopyFrom(FCode, 0)
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.SelectToken(i: Integer);
|
|||
|
begin
|
|||
|
Assert((i >= 0) and (i <= FParser.Count));
|
|||
|
|
|||
|
if i < FParser.Count then
|
|||
|
FEmitter.RetMode := FRetTypes[i];
|
|||
|
|
|||
|
FPos := i;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.SkipToken;
|
|||
|
begin
|
|||
|
SelectToken(FPos + 1);
|
|||
|
end;
|
|||
|
|
|||
|
function TBiffFormulaCompiler.Token(i: Integer): TBiffFormulaToken;
|
|||
|
begin
|
|||
|
if (FPos + i >= 0) and (FPos + i < FParser.Count) then
|
|||
|
Result := FParser[FPos + i]
|
|||
|
else
|
|||
|
Result.Kind := ftkVoid
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.CalcRetTypes;
|
|||
|
var
|
|||
|
Stack: array of Integer;
|
|||
|
StackTop: Integer;
|
|||
|
|
|||
|
procedure Push(Id: Integer; RepNum: Integer = 1);
|
|||
|
begin
|
|||
|
Assert(RepNum >= 1);
|
|||
|
|
|||
|
for RepNum := RepNum downto 1 do
|
|||
|
begin
|
|||
|
Assert(StackTop < Length(Stack));
|
|||
|
Stack[StackTop] := Id;
|
|||
|
Inc(StackTop);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure Pop(n: Integer);
|
|||
|
begin
|
|||
|
Assert(StackTop >= n);
|
|||
|
Dec(StackTop, n);
|
|||
|
end;
|
|||
|
|
|||
|
function Top(i: Integer): Integer;
|
|||
|
begin
|
|||
|
Assert(StackTop - i - 1 >= 0);
|
|||
|
Result := Stack[StackTop - i - 1];
|
|||
|
end;
|
|||
|
|
|||
|
procedure SetRetType(i: Integer; t: TBiffFormulaRetType);
|
|||
|
begin
|
|||
|
Assert(FRetTypes[i] = frtVoid, 'Ret type is already set');
|
|||
|
FRetTypes[i] := t;
|
|||
|
end;
|
|||
|
|
|||
|
function GetArgType(const t: TBiffFormulaToken; i: Integer): TBiffFormulaRetType;
|
|||
|
begin
|
|||
|
Assert(t.Kind = ftkOp);
|
|||
|
Result := frtVoid;
|
|||
|
|
|||
|
case t.Op of
|
|||
|
fokColon: Result := frtRef;
|
|||
|
fokIsect: Result := frtRef;
|
|||
|
fokFunc:
|
|||
|
case TBiffFormulaFuncList.GetArgType(t.Text, i) of
|
|||
|
'R': Result := frtRef;
|
|||
|
'V': Result := frtVal;
|
|||
|
'A': Result := frtArray;
|
|||
|
end;
|
|||
|
|
|||
|
else Result := frtVal;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
procedure AssignArgTypes(const t: TBiffFormulaToken);
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
Assert(StackTop >= t.Flags);
|
|||
|
|
|||
|
for i := 1 to t.Flags do
|
|||
|
SetRetType(Top(t.Flags - i), GetArgType(t, i - 1))
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
i, n: Integer;
|
|||
|
t: TBiffFormulaToken;
|
|||
|
begin
|
|||
|
n := FParser.Count;
|
|||
|
|
|||
|
SetLength(FRetTypes, n);
|
|||
|
SetLength(Stack, n);
|
|||
|
StackTop := 0;
|
|||
|
|
|||
|
for i := 0 to n - 1 do
|
|||
|
begin
|
|||
|
t := FParser[i];
|
|||
|
|
|||
|
case t.Kind of
|
|||
|
ftkArg:
|
|||
|
Push(i);
|
|||
|
|
|||
|
ftkOp:
|
|||
|
begin
|
|||
|
AssignArgTypes(t);
|
|||
|
Pop(t.Flags);
|
|||
|
Push(i);
|
|||
|
end;
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
SetRetType(Top(0), frtVal);
|
|||
|
Pop(1);
|
|||
|
Ensure(StackTop = 0, 'Not all tokens produced by parser are supported by compiler');
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.Compile(const s: string);
|
|||
|
begin
|
|||
|
FParser.Formula := s;
|
|||
|
CalcRetTypes;
|
|||
|
SelectToken(0);
|
|||
|
|
|||
|
while FPos < FParser.Count do
|
|||
|
CompileToken
|
|||
|
end;
|
|||
|
|
|||
|
procedure TBiffFormulaCompiler.CompileToken;
|
|||
|
begin
|
|||
|
if Token.Kind = ftkOp then
|
|||
|
case Token.Op of
|
|||
|
fokPush:
|
|||
|
EmitPush(Token(-1));
|
|||
|
|
|||
|
fokId:
|
|||
|
EmitIdFunc(Token.Flags);
|
|||
|
|
|||
|
fokFunc:
|
|||
|
EmitFunc(Token.Text, Token.Flags);
|
|||
|
|
|||
|
fokArea:
|
|||
|
EmitArea(Token(-2), Token(-1));
|
|||
|
|
|||
|
fokExt:
|
|||
|
case Token.Flags of
|
|||
|
2: EmitExtCell(Token(-2), Token(-1));
|
|||
|
3: EmitExtArea(Token(-3), Token(-2), Token(-1));
|
|||
|
|
|||
|
else Error('Operator "!" does not support %d arguments', [Token.Flags]);
|
|||
|
end;
|
|||
|
|
|||
|
fokAdd, fokSub, fokMul, fokDiv, fokPow,
|
|||
|
fokL, fokG, fokLE, fokGE, fokNE, fokE,
|
|||
|
fokIsect, fokJoin, fokColon, fokDiv100, fokNeg:
|
|||
|
EmitOp(Token.Op, Token.Flags);
|
|||
|
|
|||
|
else
|
|||
|
Error('Operator (%s) %d not supported', [Token.Text, Integer(Token.Op)]);
|
|||
|
end;
|
|||
|
|
|||
|
SkipToken;
|
|||
|
end;
|
|||
|
|
|||
|
{ TBiffFormulaFuncList }
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.GetCount: Integer;
|
|||
|
begin
|
|||
|
Result := Length(FuncArray)
|
|||
|
end;
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.GetFunc(i: Integer): TBiffFormulaFunc;
|
|||
|
begin
|
|||
|
Result := FuncArray[i]
|
|||
|
end;
|
|||
|
|
|||
|
class procedure TBiffFormulaFuncList.SetCount(n: Integer);
|
|||
|
begin
|
|||
|
SetLength(FuncArray, n)
|
|||
|
end;
|
|||
|
|
|||
|
class procedure TBiffFormulaFuncList.SetFunc(i: Integer; const f: TBiffFormulaFunc);
|
|||
|
begin
|
|||
|
FuncArray[i] := f
|
|||
|
end;
|
|||
|
|
|||
|
class procedure TBiffFormulaFuncList.Add(Id: Integer; Name: string; MinArgs,
|
|||
|
MaxArgs: Integer; RetType: Char; ArgTypes: string; Volatile: Boolean);
|
|||
|
|
|||
|
function IsValid(const s: string): Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
for i := 1 to Length(s) do
|
|||
|
if not (AnsiChar(s[i]) in ['r', 'v', 'a']) then
|
|||
|
begin
|
|||
|
Result := False;
|
|||
|
Exit;
|
|||
|
end;
|
|||
|
|
|||
|
Result := True;
|
|||
|
end;
|
|||
|
|
|||
|
var
|
|||
|
f: TBiffFormulaFunc;
|
|||
|
begin
|
|||
|
Assert(Name <> '');
|
|||
|
Assert((MinArgs >= 0) and (MaxArgs >= MinArgs));
|
|||
|
Assert(IsValid(RetType));
|
|||
|
Assert(IsValid(ArgTypes));
|
|||
|
|
|||
|
if MaxArgs > 0 then
|
|||
|
Assert(ArgTypes <> '');
|
|||
|
|
|||
|
f.Name := AnsiUpperCase(Name);
|
|||
|
f.Id := Id;
|
|||
|
f.MinArgs := MinArgs;
|
|||
|
f.MaxArgs := MaxArgs;
|
|||
|
f.Volatile := Volatile;
|
|||
|
f.RetType := RetType;
|
|||
|
f.ArgTypes := ArgTypes;
|
|||
|
|
|||
|
Add(f);
|
|||
|
end;
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.Find(Name: string): Integer;
|
|||
|
begin
|
|||
|
Name := AnsiUpperCase(Name);
|
|||
|
|
|||
|
for Result := 0 to GetCount - 1 do
|
|||
|
if GetFunc(Result).Name >= Name then
|
|||
|
Exit;
|
|||
|
|
|||
|
Result := GetCount;
|
|||
|
end;
|
|||
|
|
|||
|
class procedure TBiffFormulaFuncList.Add(const f: TBiffFormulaFunc);
|
|||
|
var
|
|||
|
i, j: Integer;
|
|||
|
begin
|
|||
|
i := Find(f.Name);
|
|||
|
Assert((i = GetCount) or (GetFunc(i).Name > f.Name), f.Name + ' already added');
|
|||
|
SetCount(GetCount + 1);
|
|||
|
|
|||
|
for j := GetCount - 1 downto i + 1 do
|
|||
|
SetFunc(j, GetFunc(j - 1));
|
|||
|
|
|||
|
SetFunc(i, f);
|
|||
|
end;
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.Get(const Name: string): TBiffFormulaFunc;
|
|||
|
begin
|
|||
|
Init;
|
|||
|
Result := GetFunc(Find(Name))
|
|||
|
end;
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.GetArgType(const Name: string; i: Integer): Char;
|
|||
|
begin
|
|||
|
with Get(Name) do
|
|||
|
begin
|
|||
|
Assert((i >= 0) and (i < MaxArgs));
|
|||
|
|
|||
|
if i + 1 <= Length(ArgTypes) then
|
|||
|
Result := ArgTypes[i + 1]
|
|||
|
else
|
|||
|
Result := ArgTypes[Length(ArgTypes)];
|
|||
|
|
|||
|
Result := UpperCase(Result)[1];
|
|||
|
Assert(AnsiChar(Result) in ['R', 'V', 'A']);
|
|||
|
end;
|
|||
|
end;
|
|||
|
|
|||
|
class function TBiffFormulaFuncList.Exists(const Name: string): Boolean;
|
|||
|
var
|
|||
|
i: Integer;
|
|||
|
begin
|
|||
|
Init;
|
|||
|
i := Find(Name);
|
|||
|
|
|||
|
Result := (i < GetCount) and (GetFunc(i).Name = Name)
|
|||
|
end;
|
|||
|
|
|||
|
class procedure TBiffFormulaFuncList.Init;
|
|||
|
begin
|
|||
|
if GetCount > 0 then
|
|||
|
Exit;
|
|||
|
|
|||
|
{ http://sc.openoffice.org/excelfileformat.pdf
|
|||
|
http://msdn.microsoft.com/en-us/library/dd904817.aspx }
|
|||
|
|
|||
|
Add(0, 'count', 1, 30, 'v', 'r');
|
|||
|
Add(1, 'if', 2, 3, 'r', 'vr');
|
|||
|
Add(2, 'isna', 1, 1, 'v', 'v');
|
|||
|
Add(3, 'iserror', 1, 1, 'v', 'v');
|
|||
|
Add(4, 'sum', 1, 30, 'v', 'r');
|
|||
|
Add(5, 'average', 1, 30, 'v', 'r');
|
|||
|
Add(6, 'min', 1, 30, 'v', 'r');
|
|||
|
Add(7, 'max', 1, 30, 'v', 'r');
|
|||
|
Add(8, 'row', 0, 1, 'v', 'r');
|
|||
|
Add(9, 'column', 0, 1, 'v', 'r');
|
|||
|
Add(10, 'na', 0, 0, 'v', '');
|
|||
|
|
|||
|
Add(13, 'dollar', 1, 2, 'v', 'v');
|
|||
|
Add(14, 'fixed', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(20, 'sqrt', 1, 1, 'v', 'v');
|
|||
|
Add(21, 'exp', 1, 1, 'v', 'v');
|
|||
|
Add(22, 'ln', 1, 1, 'v', 'v');
|
|||
|
Add(23, 'log10', 1, 1, 'v', 'v');
|
|||
|
Add(24, 'abs', 1, 1, 'v', 'v');
|
|||
|
Add(25, 'int', 1, 1, 'v', 'v');
|
|||
|
Add(26, 'sign', 1, 1, 'v', 'v');
|
|||
|
Add(27, 'round', 2, 2, 'v', 'v');
|
|||
|
Add(28, 'lookup', 2, 3, 'v', 'vr');
|
|||
|
Add(29, 'index', 2, 4, 'r', 'rv');
|
|||
|
Add(30, 'rept', 2, 2, 'v', 'v');
|
|||
|
Add(31, 'mid', 3, 3, 'v', 'v');
|
|||
|
Add(32, 'len', 1, 1, 'v', 'v');
|
|||
|
Add(33, 'value', 1, 1, 'v', 'v');
|
|||
|
Add(34, 'true', 0, 0, 'v', '');
|
|||
|
Add(35, 'false', 0, 0, 'v', '');
|
|||
|
Add(36, 'and', 1, 30, 'v', 'r');
|
|||
|
Add(37, 'or', 1, 30, 'v', 'r');
|
|||
|
Add(38, 'not', 1, 1, 'v', 'v');
|
|||
|
Add(39, 'mod', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(48, 'text', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(63, 'rand', 0, 0, 'v', '');
|
|||
|
Add(64, 'match', 2, 3, 'v', 'vr');
|
|||
|
Add(65, 'date', 3, 3, 'v', 'v');
|
|||
|
Add(66, 'time', 3, 3, 'v', 'v');
|
|||
|
Add(67, 'day', 1, 1, 'v', 'v');
|
|||
|
Add(68, 'month', 1, 1, 'v', 'v');
|
|||
|
Add(69, 'year', 1, 1, 'v', 'v');
|
|||
|
Add(70, 'weekday', 1, 2, 'v', 'v');
|
|||
|
Add(71, 'hour', 1, 1, 'v', 'v');
|
|||
|
Add(72, 'minute', 1, 1, 'v', 'v');
|
|||
|
Add(73, 'second', 1, 1, 'v', 'v');
|
|||
|
Add(74, 'now', 0, 0, 'v', '');
|
|||
|
Add(75, 'areas', 1, 1, 'v', 'r');
|
|||
|
Add(76, 'rows', 1, 1, 'v', 'r');
|
|||
|
Add(77, 'columns', 1, 1, 'v', 'r');
|
|||
|
Add(78, 'offset', 3, 5, 'r', 'rv');
|
|||
|
Add(79, 'absref', 2, 2, 'r', 'vr');
|
|||
|
Add(80, 'relref', 2, 2, 'r', 'rr');
|
|||
|
|
|||
|
Add(82, 'search', 2, 3, 'v', 'v');
|
|||
|
Add(83, 'transpose', 1, 1, 'a', 'a');
|
|||
|
Add(84, 'error', 0, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(86, 'type', 1, 1, 'v', 'v');
|
|||
|
Add($5a, 'deref', 1, 1, 'v', 'r');
|
|||
|
|
|||
|
Add(100, 'choose', 2, 30, 'r', 'vr');
|
|||
|
Add(101, 'hlookup', 3, 4, 'v', 'vrrv');
|
|||
|
Add(102, 'vlookup', 3, 4, 'v', 'vrrv');
|
|||
|
|
|||
|
Add(105, 'isref', 1, 1, 'v', 'r');
|
|||
|
Add(109, 'log', 1, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(111, 'char', 1, 1, 'v', 'v');
|
|||
|
Add(112, 'lower', 1, 1, 'v', 'v');
|
|||
|
Add(113, 'upper', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(115, 'left', 1, 2, 'v', 'v');
|
|||
|
Add(116, 'right', 1, 2, 'v', 'v');
|
|||
|
Add(117, 'exact', 2, 2, 'v', 'v');
|
|||
|
Add(118, 'trim', 1, 2, 'v', 'v');
|
|||
|
Add(119, 'replace', 4, 4, 'v', 'v');
|
|||
|
Add(120, 'substitute', 3, 4, 'v', 'v');
|
|||
|
Add(121, 'code', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(124, 'find', 2, 3, 'v', 'v');
|
|||
|
Add(125, 'cell', 1, 2, 'v', 'vr', True);
|
|||
|
Add(126, 'iserr', 1, 1, 'v', 'v');
|
|||
|
Add(127, 'istext', 1, 1, 'v', 'v');
|
|||
|
Add(128, 'isnumber', 1, 1, 'v', 'v');
|
|||
|
Add(129, 'isblank', 1, 1, 'v', 'v');
|
|||
|
Add(130, 't', 1, 1, 'v', 'r');
|
|||
|
Add(131, 'n', 1, 1, 'v', 'r');
|
|||
|
|
|||
|
Add(140, 'datevalue', 1, 1, 'v', 'v');
|
|||
|
Add(141, 'timevalue', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(147, 'textref', 1, 2, 'r', 'v');
|
|||
|
Add(148, 'indirect', 1, 2, 'r', 'v');
|
|||
|
|
|||
|
Add(162, 'clean', 1, 1, 'v', 'v');
|
|||
|
Add(169, 'counta', 0, 30, 'v', 'r');
|
|||
|
Add(190, 'isnontext', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(197, 'trunc', 1, 2, 'v', 'v');
|
|||
|
Add(198, 'islogical', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(205, 'findb', 2, 3, 'v', 'v');
|
|||
|
Add(206, 'searchb', 2, 3, 'v', 'v');
|
|||
|
Add(207, 'replaceb', 4, 4, 'v', 'v');
|
|||
|
Add(208, 'leftb', 1, 2, 'v', 'v');
|
|||
|
Add(209, 'rightb', 1, 2, 'v', 'v');
|
|||
|
Add(210, 'midb', 3, 3, 'v', 'v');
|
|||
|
Add(211, 'lenb', 1, 1, 'v', 'v');
|
|||
|
Add(212, 'roundup', 2, 2, 'v', 'v');
|
|||
|
Add(213, 'rounddown', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(219, 'address', 2, 5, 'v', 'v');
|
|||
|
Add(221, 'today', 0, 0, 'v', '', True);
|
|||
|
Add(257, 'evaluate', 1, 1, 'v', 'v');
|
|||
|
Add(261, 'error.type', 1, 1, 'v', 'v');
|
|||
|
Add(277, 'confidence', 3, 3, 'v', 'v');
|
|||
|
Add(279, 'even', 1, 1, 'v', 'v');
|
|||
|
Add(285, 'floor', 2, 2, 'v', 'v');
|
|||
|
Add(288, 'ceiling', 2, 2, 'v', 'v');
|
|||
|
Add(298, 'odd', 1, 1, 'v', 'v');
|
|||
|
|
|||
|
Add(336, 'concatenate', 0, 30, 'v', 'v');
|
|||
|
Add(337, 'power', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(344, 'subtotal', 2, 30, 'v', 'vr');
|
|||
|
Add(345, 'sumif', 2, 3, 'v', 'rvr');
|
|||
|
Add(346, 'countif', 2, 2, 'v', 'rv');
|
|||
|
Add(347, 'countblank', 1, 1, 'v', 'r');
|
|||
|
|
|||
|
Add(350, 'ispmt', 4, 4, 'v', 'v');
|
|||
|
Add(351, 'dateif', 3, 3, 'v', 'v');
|
|||
|
Add(352, 'datestring', 1, 1, 'v', 'v');
|
|||
|
Add(353, 'numberstring', 2, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(359, 'hyperlink', 1, 2, 'v', 'v');
|
|||
|
|
|||
|
Add(362, 'maxa', 1, 30, 'v', 'r');
|
|||
|
Add(363, 'mina', 1, 30, 'v', 'r');
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetBottom: Integer;
|
|||
|
begin
|
|||
|
Result := FRect.Bottom;
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetBottomRight: TPoint;
|
|||
|
begin
|
|||
|
Result := FRect.BottomRight;
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetLeft: Integer;
|
|||
|
begin
|
|||
|
Result := FRect.Left;
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetRight: Integer;
|
|||
|
begin
|
|||
|
Result := FRect.Right;
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetTop: Integer;
|
|||
|
begin
|
|||
|
Result := FRect.Top;
|
|||
|
end;
|
|||
|
|
|||
|
function TRectangle.GetTopLeft: TPoint;
|
|||
|
begin
|
|||
|
Result := FRect.TopLeft;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetBottom(const Value: Integer);
|
|||
|
begin
|
|||
|
FRect.Bottom := Value;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetBottomRight(const Value: TPoint);
|
|||
|
begin
|
|||
|
FRect.BottomRight := Value;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetLeft(const Value: Integer);
|
|||
|
begin
|
|||
|
FRect.Left := Value;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetRight(const Value: Integer);
|
|||
|
begin
|
|||
|
FRect.Right := Value;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetTop(const Value: Integer);
|
|||
|
begin
|
|||
|
FRect.Top := Value;
|
|||
|
end;
|
|||
|
|
|||
|
procedure TRectangle.SetTopLeft(const Value: TPoint);
|
|||
|
begin
|
|||
|
FRect.TopLeft := Value;
|
|||
|
end;
|
|||
|
|
|||
|
end.
|