
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@7555 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2482 lines
78 KiB
ObjectPascal
2482 lines
78 KiB
ObjectPascal
{
|
||
xlsbiff2.pas
|
||
|
||
Writes an Excel 2.x file
|
||
|
||
Excel 2.x files support only one Worksheet per Workbook, so only the first
|
||
will be written.
|
||
|
||
An Excel file consists of a number of subsequent records.
|
||
To ensure a properly formed file, the following order must be respected:
|
||
|
||
1st record: BOF
|
||
2nd to Nth record: Any record
|
||
Last record: EOF
|
||
|
||
The row and column numbering in BIFF files is zero-based.
|
||
|
||
Excel file format specification obtained from:
|
||
|
||
http://sc.openoffice.org/excelfileformat.pdf
|
||
|
||
Encoding information: ISO_8859_1 is used, to have support to
|
||
other characters, please use a format which support unicode
|
||
|
||
AUTHORS: Felipe Monteiro de Carvalho
|
||
}
|
||
unit xlsbiff2;
|
||
|
||
{$ifdef fpc}
|
||
{$mode delphi}{$H+}
|
||
{$endif}
|
||
|
||
interface
|
||
|
||
uses
|
||
Classes, SysUtils, lconvencoding,
|
||
fpsTypes, fpsUtils, xlscommon;
|
||
|
||
const
|
||
BIFF2_MAX_PALETTE_SIZE = 8;
|
||
// There are more colors but they do not seem to be controlled by a palette.
|
||
|
||
type
|
||
|
||
{ TsSpreadBIFF2Reader }
|
||
|
||
TsSpreadBIFF2Reader = class(TsSpreadBIFFReader)
|
||
private
|
||
FFont: TsFont;
|
||
FPendingXFIndex: Word;
|
||
protected
|
||
procedure AddBuiltinNumFormats; override;
|
||
procedure ReadBlank(AStream: TStream); override;
|
||
procedure ReadBool(AStream: TStream); override;
|
||
procedure ReadColumnDefault(AStream: TStream);
|
||
procedure ReadColWidth(AStream: TStream);
|
||
procedure ReadDefRowHeight(AStream: TStream);
|
||
procedure ReadFONT(AStream: TStream);
|
||
procedure ReadFONTCOLOR(AStream: TStream);
|
||
procedure ReadFORMAT(AStream: TStream); override;
|
||
procedure ReadFormula(AStream: TStream); override;
|
||
procedure ReadInteger(AStream: TStream);
|
||
procedure ReadIXFE(AStream: TStream);
|
||
procedure ReadLabel(AStream: TStream); override;
|
||
procedure ReadNumber(AStream: TStream); override;
|
||
procedure ReadPASSWORD(AStream: TStream);
|
||
procedure ReadPROTECT(AStream: TStream);
|
||
procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override;
|
||
procedure ReadRowInfo(AStream: TStream); override;
|
||
procedure ReadRPNAttr(AStream: TStream; AIdentifier: Byte); override;
|
||
function ReadRPNFunc(AStream: TStream): Word; override;
|
||
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); override;
|
||
function ReadRPNTokenArraySize(AStream: TStream): Word; override;
|
||
procedure ReadStringRecord(AStream: TStream); override;
|
||
procedure ReadWindow2(AStream: TStream); override;
|
||
procedure ReadXF(AStream: TStream);
|
||
public
|
||
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
||
{ General reading methods }
|
||
procedure ReadFromStream(AStream: TStream; APassword: String = '';
|
||
AParams: TsStreamParams = []); override;
|
||
{ File format detection }
|
||
class function CheckfileFormat(AStream: TStream): Boolean; override;
|
||
end;
|
||
|
||
|
||
{ TsSpreadBIFF2Writer }
|
||
|
||
TsSpreadBIFF2Writer = class(TsSpreadBIFFWriter)
|
||
private
|
||
FSheetIndex: Integer; // Index of worksheet to be written
|
||
procedure GetAttributes(AFormatIndex: Integer; XFIndex: Word;
|
||
out Attrib1, Attrib2, Attrib3: Byte);
|
||
procedure GetFormatAndFontIndex(AFormatRecord: PsCellFormat;
|
||
out AFormatIndex, AFontIndex: Integer);
|
||
{ Record writing methods }
|
||
procedure WriteBOF(AStream: TStream);
|
||
procedure WriteCellAttributes(AStream: TStream; AFormatIndex: Integer; XFIndex: Word);
|
||
procedure WriteColWidth(AStream: TStream; ACol: PCol);
|
||
procedure WriteColWidths(AStream: TStream);
|
||
// procedure WriteColumnDefault(AStream: TStream; ACol: PCol);
|
||
procedure WriteColumnDefault(AStream: TStream;
|
||
AFirstColIndex, ALastColIndex: Word; AFormatIndex: Integer);
|
||
procedure WriteColumnDefaults(AStream: TStream);
|
||
procedure WriteDefaultRowHeight(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
||
procedure WriteDimensions(AStream: TStream; AWorksheet: TsBasicWorksheet);
|
||
procedure WriteEOF(AStream: TStream);
|
||
procedure WriteFont(AStream: TStream; AFontIndex: Integer);
|
||
procedure WriteFonts(AStream: TStream);
|
||
procedure WriteFORMATCOUNT(AStream: TStream);
|
||
procedure WriteIXFE(AStream: TStream; XFIndex: Word);
|
||
protected
|
||
procedure AddBuiltinNumFormats; override;
|
||
function FunctionSupported(AExcelCode: Integer;
|
||
const AFuncName: String): Boolean; override;
|
||
procedure PopulatePalette(AWorkbook: TsbasicWorkbook); override;
|
||
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
|
||
ACell: PCell); override;
|
||
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
||
const AValue: Boolean; ACell: PCell); override;
|
||
procedure WriteCodePage(AStream: TStream; ACodePage: String); override;
|
||
procedure WriteError(AStream: TStream; const ARow, ACol: Cardinal;
|
||
const AValue: TsErrorValue; ACell: PCell); override;
|
||
procedure WriteFORMAT(AStream: TStream; ANumFormatStr: String;
|
||
AFormatIndex: Integer); override;
|
||
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
|
||
const AValue: string; ACell: PCell); override;
|
||
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
|
||
const AValue: double; ACell: PCell); override;
|
||
procedure WritePASSWORD(AStream: TStream);
|
||
procedure WriteRow(AStream: TStream; ASheet: TsBasicWorksheet;
|
||
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow); override;
|
||
procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal;
|
||
AFormula: TsRPNFormula; ACell: PCell); override;
|
||
function WriteRPNFunc(AStream: TStream; AIdentifier: Word): Word; override;
|
||
procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); override;
|
||
procedure WriteStringRecord(AStream: TStream; AString: String); override;
|
||
procedure WriteWindow1(AStream: TStream); override;
|
||
procedure WriteWindow2(AStream: TStream; ASheet: TsBasicWorksheet);
|
||
procedure WriteXF(AStream: TStream; AFormatRecord: PsCellFormat;
|
||
XFType_Prot: Byte = 0); override;
|
||
public
|
||
constructor Create(AWorkbook: TsbasicWorkbook); override;
|
||
procedure WriteToStream(AStream: TStream; AParams: TsStreamParams = []); override;
|
||
end;
|
||
|
||
TExcel2Settings = record
|
||
// Settings used when writing to file
|
||
DateMode: TDateMode;
|
||
CodePage: String;
|
||
SheetIndex: Integer;
|
||
end;
|
||
|
||
var
|
||
Excel2Settings: TExcel2Settings = (
|
||
DateMode: dm1900;
|
||
CodePage: 'cp1252'; // on Windows, will be replaced --> see initalization
|
||
SheetIndex: 0;
|
||
);
|
||
|
||
{ the palette of the default BIFF2 colors as "big-endian color" values }
|
||
PALETTE_BIFF2: array[$0..$07] of TsColor = (
|
||
$000000, // $00: black
|
||
$FFFFFF, // $01: white
|
||
$FF0000, // $02: red
|
||
$00FF00, // $03: green
|
||
$0000FF, // $04: blue
|
||
$FFFF00, // $05: yellow
|
||
$FF00FF, // $06: magenta
|
||
$00FFFF // $07: cyan
|
||
);
|
||
|
||
sfidExcel2: TsSpreadFormatID;
|
||
|
||
procedure InitBiff2Limitations(out ALimitations: TsSpreadsheetFormatLimitations);
|
||
|
||
|
||
implementation
|
||
|
||
uses
|
||
{$IFDEF FPSpreadDebug}
|
||
LazLogger,
|
||
{$ENDIF}
|
||
Math,
|
||
fpsStrings, fpspreadsheet, fpsReaderWriter, fpsPalette, fpsNumFormat;
|
||
|
||
const
|
||
{ Excel record IDs }
|
||
INT_EXCEL_ID_DIMENSIONS = $0000;
|
||
INT_EXCEL_ID_BLANK = $0001;
|
||
INT_EXCEL_ID_INTEGER = $0002;
|
||
INT_EXCEL_ID_NUMBER = $0003;
|
||
INT_EXCEL_ID_LABEL = $0004;
|
||
INT_EXCEL_ID_BOOLERROR = $0005;
|
||
INT_EXCEL_ID_ROW = $0008;
|
||
INT_EXCEL_ID_BOF = $0009;
|
||
{%H-}INT_EXCEL_ID_INDEX = $000B;
|
||
INT_EXCEL_ID_FORMAT = $001E;
|
||
INT_EXCEL_ID_FORMATCOUNT = $001F;
|
||
INT_EXCEL_ID_COLUMNDEFAULT = $0020;
|
||
INT_EXCEL_ID_COLWIDTH = $0024;
|
||
INT_EXCEL_ID_DEFROWHEIGHT = $0025;
|
||
INT_EXCEL_ID_WINDOW2 = $003E;
|
||
INT_EXCEL_ID_XF = $0043;
|
||
INT_EXCEL_ID_IXFE = $0044;
|
||
INT_EXCEL_ID_FONTCOLOR = $0045;
|
||
|
||
{ BOF record constants }
|
||
INT_EXCEL_SHEET = $0010;
|
||
{%H-}INT_EXCEL_CHART = $0020;
|
||
{%H-}INT_EXCEL_MACRO_SHEET = $0040;
|
||
|
||
MASK_XF_TYPE_PROT_LOCKED_BIFF2 = $40;
|
||
MASK_XF_TYPE_PROT_FORMULA_HIDDEN_BIFF2 = $80;
|
||
|
||
type
|
||
TBIFF2_BoolErrRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
Row: Word;
|
||
Col: Word;
|
||
Attrib1: Byte;
|
||
Attrib2: Byte;
|
||
Attrib3: Byte;
|
||
BoolErrValue: Byte;
|
||
ValueType: Byte;
|
||
end;
|
||
|
||
TBIFF2_DimensionsRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
FirstRow: Word;
|
||
LastRowPlus1: Word;
|
||
FirstCol: Word;
|
||
LastColPlus1: Word;
|
||
end;
|
||
|
||
TBIFF2_LabelRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
Row: Word;
|
||
Col: Word;
|
||
Attrib1: Byte;
|
||
Attrib2: Byte;
|
||
Attrib3: Byte;
|
||
TextLen: Byte;
|
||
end;
|
||
|
||
TBIFF2_NumberRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
Row: Word;
|
||
Col: Word;
|
||
Attrib1: Byte;
|
||
Attrib2: Byte;
|
||
Attrib3: Byte;
|
||
Value: Double;
|
||
end;
|
||
|
||
TBIFF2_IntegerRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
Row: Word;
|
||
Col: Word;
|
||
Attrib1: Byte;
|
||
Attrib2: Byte;
|
||
Attrib3: Byte;
|
||
Value: Word;
|
||
end;
|
||
|
||
TBIFF2_XFRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
FontIndex: Byte;
|
||
NotUsed: Byte;
|
||
NumFormat_Prot: Byte;
|
||
HorAlign_Border_BkGr: Byte;
|
||
end;
|
||
|
||
|
||
procedure InitBiff2Limitations(out ALimitations: TsSpreadsheetFormatLimitations);
|
||
begin
|
||
InitBiffLimitations(ALimitations);
|
||
ALimitations.MaxPaletteSize := BIFF2_MAX_PALETTE_SIZE;
|
||
end;
|
||
|
||
procedure InternalAddBuiltinNumFormats(AList: TStringList; AFormatSettings: TFormatSettings);
|
||
var
|
||
fs: TFormatSettings absolute AFormatSettings;
|
||
cs: String;
|
||
begin
|
||
cs := fs.CurrencyString;
|
||
with AList do
|
||
begin
|
||
Clear;
|
||
Add(''); // 0
|
||
Add('0'); // 1
|
||
Add('0.00'); // 2
|
||
Add('#,##0'); // 3
|
||
Add('#,##0.00'); // 4
|
||
Add(BuildCurrencyFormatString(nfCurrency, fs, 0, fs.CurrencyFormat, fs.NegCurrFormat, cs)); // 5
|
||
Add(BuildCurrencyFormatString(nfCurrencyRed, fs, 0, fs.CurrencyFormat, fs.NegCurrFormat, cs)); // 6
|
||
Add(BuildCurrencyFormatString(nfCurrency, fs, 2, fs.CurrencyFormat, fs.NegCurrFormat, cs)); // 7
|
||
Add(BuildCurrencyFormatString(nfCurrencyRed, fs, 2, fs.CurrencyFormat, fs.NegCurrFormat, cs)); // 8
|
||
Add('0%'); // 9
|
||
Add('0.00%'); // 10
|
||
Add('0.00E+00'); // 11
|
||
Add(BuildDateTimeFormatString(nfShortDate, fs)); // 12
|
||
Add(BuildDateTimeFormatString(nfLongDate, fs)); // 13
|
||
Add(BuildDateTimeFormatString(nfDayMonth, fs)); // 14: 'd/mmm'
|
||
Add(BuildDateTimeFormatString(nfMonthYear, fs)); // 15: 'mmm/yy'
|
||
Add(BuildDateTimeFormatString(nfShortTimeAM, fs)); // 16;
|
||
Add(BuildDateTimeFormatString(nfLongTimeAM, fs)); // 17
|
||
Add(BuildDateTimeFormatString(nfShortTime, fs)); // 18
|
||
Add(BuildDateTimeFormatString(nfLongTime, fs)); // 19
|
||
Add(BuildDateTimeFormatString(nfShortDateTime, fs)); // 20
|
||
end;
|
||
end;
|
||
|
||
|
||
{------------------------------------------------------------------------------}
|
||
{ TsSpreadBIFF2Reader }
|
||
{------------------------------------------------------------------------------}
|
||
|
||
constructor TsSpreadBIFF2Reader.Create(AWorkbook: TsBasicWorkbook);
|
||
begin
|
||
inherited Create(AWorkbook);
|
||
InitBiff2Limitations(FLimitations);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.AddBuiltInNumFormats;
|
||
begin
|
||
FFirstNumFormatIndexInFile := 0;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Checks the header of the stream for the signature of BIFF2 files
|
||
-------------------------------------------------------------------------------}
|
||
class function TsSpreadBIFF2Reader.CheckFileFormat(AStream: TStream): Boolean;
|
||
const
|
||
BIFF2_HEADER: packed array[0..3] of byte = (
|
||
$09,$00, $04,$00); // they are common to all BIFF2 files that I've seen
|
||
var
|
||
P: Int64;
|
||
buf: packed array[0..3] of byte = (0, 0, 0, 0);
|
||
n: Integer;
|
||
begin
|
||
Result := false;
|
||
P := AStream.Position;
|
||
try
|
||
AStream.Position := 0;
|
||
n := AStream.Read(buf, SizeOf(buf));
|
||
if n < Length(BIFF2_HEADER) then
|
||
exit;
|
||
for n:=0 to High(buf) do
|
||
if buf[n] <> BIFF2_HEADER[n] then
|
||
exit;
|
||
Result := true;
|
||
finally
|
||
AStream.Position := P;
|
||
end;
|
||
end;
|
||
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadBlank(AStream: TStream);
|
||
var
|
||
ARow, ACol: Cardinal;
|
||
XF: Word;
|
||
cell: PCell;
|
||
begin
|
||
ReadRowColXF(AStream, ARow, ACol, XF);
|
||
if FIsVirtualMode then begin
|
||
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := TsWorksheet(FWorksheet).AddCell(ARow, ACol);
|
||
ApplyCellFormatting(cell, XF);
|
||
if FIsVirtualMode then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
The name of this method is misleading - it reads a BOOLEAN cell value,
|
||
but also an ERROR value; BIFF stores them in the same record.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadBool(AStream: TStream);
|
||
var
|
||
rec: TBIFF2_BoolErrRecord;
|
||
r, c: Cardinal;
|
||
xf: Word;
|
||
cell: PCell;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
{ Read entire record, starting at Row }
|
||
rec.Row := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2_BoolErrRecord) - 2*SizeOf(Word));
|
||
r := WordLEToN(rec.Row);
|
||
c := WordLEToN(rec.Col);
|
||
xf := rec.Attrib1 and $3F;
|
||
if xf = 63 then xf := FPendingXFIndex;
|
||
|
||
{ Create cell }
|
||
if FIsVirtualMode then begin
|
||
InitCell(FWorksheet, r, c, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := sheet.AddCell(r, c);
|
||
|
||
{ Retrieve boolean or error value depending on the "ValueType" }
|
||
case rec.ValueType of
|
||
0: sheet.WriteBoolValue(cell, boolean(rec.BoolErrValue));
|
||
1: sheet.WriteErrorValue(cell, ConvertFromExcelError(rec.BoolErrValue));
|
||
end;
|
||
|
||
{ Apply formatting }
|
||
ApplyCellFormatting(cell, xf);
|
||
|
||
if FIsVirtualMode then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, r, c, cell);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadColumnDefault(AStream: TStream);
|
||
var
|
||
c, col1, col2: Word;
|
||
attr2, attr3: Byte;
|
||
fmt: TsCellFormat;
|
||
fmtIndex: Integer;
|
||
fontIndex: Integer;
|
||
fnt: TsFont;
|
||
nf: TsNumFormatParams;
|
||
nfs: String;
|
||
b: Byte;
|
||
book: TsWorkbook;
|
||
begin
|
||
book := TsWorkbook(FWorkbook);
|
||
|
||
{ Index of first column }
|
||
col1 := WordLEToN(AStream.ReadWord);
|
||
|
||
{ Index of last column - note: the file value is incremented by 1 }
|
||
col2 := WordLEToN(AStream.ReadWord) - 1;
|
||
|
||
{ Attributes }
|
||
{attr1 := }AStream.ReadByte; // Avoid compiler warning of unused attr1
|
||
attr2 := AStream.ReadByte;
|
||
attr3 := AStream.ReadByte;
|
||
|
||
InitFormatRecord(fmt);
|
||
fmt.ID := FCellFormatList.Count;
|
||
|
||
// Font index
|
||
fontIndex := (attr2 and $C0) shr 6;
|
||
if fontIndex > 4 then dec(fontIndex); // Watch out for the nasty missing font #4...
|
||
fnt := TsFont(FFontList[fontIndex]);
|
||
fmt.FontIndex := book.FindFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color);
|
||
if fmt.FontIndex = -1 then
|
||
fmt.FontIndex := book.AddFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color);
|
||
if fmt.FontIndex > 0 then
|
||
Include(fmt.UsedFormattingFields, uffFont);
|
||
|
||
// Number format index
|
||
b := attr2 and $3F;
|
||
nfs := NumFormatList[b];
|
||
if nfs <> '' then begin
|
||
fmt.NumberFormatIndex := book.AddNumberFormat(nfs);
|
||
nf := book.GetNumberFormat(fmt.NumberFormatIndex);
|
||
fmt.NumberFormat := nf.NumFormat;
|
||
fmt.NumberFormatStr := nf.NumFormatStr;
|
||
if fmt.NumberFormat <> nfGeneral then
|
||
Include(fmt.UsedFormattingfields, uffNumberFormat);
|
||
end;
|
||
|
||
// Horizontal alignment
|
||
b := attr3 and MASK_XF_HOR_ALIGN;
|
||
if (b <= ord(High(TsHorAlignment))) then
|
||
begin
|
||
fmt.HorAlignment := TsHorAlignment(b);
|
||
if fmt.HorAlignment <> haDefault then
|
||
Include(fmt.UsedFormattingFields, uffHorAlign);
|
||
end;
|
||
|
||
// Vertical alignment - not used in BIFF2
|
||
fmt.VertAlignment := vaDefault;
|
||
|
||
// Word wrap - not used in BIFF2
|
||
// -- nothing to do here
|
||
|
||
// Text rotation - not used in BIFF2
|
||
// -- nothing to do here
|
||
|
||
// Borders
|
||
fmt.Border := [];
|
||
if attr3 and $08 <> 0 then
|
||
Include(fmt.Border, cbWest);
|
||
if attr3 and $10 <> 0 then
|
||
Include(fmt.Border, cbEast);
|
||
if attr3 and $20 <> 0 then
|
||
Include(fmt.Border, cbNorth);
|
||
if attr3 and $40 <> 0 then
|
||
Include(fmt.Border, cbSouth);
|
||
if fmt.Border <> [] then
|
||
Include(fmt.UsedFormattingFields, uffBorder);
|
||
|
||
// Background color not supported, only shaded background
|
||
if attr3 and $80 <> 0 then
|
||
begin
|
||
fmt.Background.Style := fsGray50;
|
||
fmt.Background.FgColor := scBlack;
|
||
fmt.Background.BgColor := scTransparent;
|
||
Include(fmt.UsedFormattingFields, uffBackground);
|
||
end;
|
||
|
||
// Add the decoded data to the format list
|
||
FCellFormatList.Add(fmt);
|
||
fmtIndex := book.AddCellFormat(fmt);
|
||
|
||
for c := col1 to col2 do
|
||
TsWorksheet(FWorksheet).WriteColFormatIndex(c, fmtIndex);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadColWidth(AStream: TStream);
|
||
const
|
||
EPS = 1E-3;
|
||
var
|
||
c, c1, c2: Cardinal;
|
||
w: Word;
|
||
colwidth: Single;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
sheet := TsWorksheet(FWorksheet);
|
||
|
||
// read column start and end index of column range
|
||
c1 := AStream.ReadByte;
|
||
c2 := AStream.ReadByte;
|
||
// read col width in 1/256 of the width of "0" character
|
||
w := WordLEToN(AStream.ReadWord);
|
||
// calculate width in units of "characters"
|
||
colwidth := (FWorkbook as TsWorkbook).ConvertUnits(w / 256, suChars, FWorkbook.Units);
|
||
// assign width to columns, but only if different from default column width.
|
||
if not SameValue(colwidth, sheet.ReadDefaultColWidth(FWorkbook.Units), EPS) then
|
||
for c := c1 to c2 do
|
||
sheet.WriteColWidth(c, colwidth, FWorkbook.Units);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadDefRowHeight(AStream: TStream);
|
||
var
|
||
hw: word;
|
||
h: Single;
|
||
begin
|
||
hw := WordLEToN(AStream.ReadWord);
|
||
h := TwipsToPts(hw and $7FFF);
|
||
(FWorksheet as TsWorksheet).WriteDefaultRowHeight(h, suPoints);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadFONT(AStream: TStream);
|
||
var
|
||
lHeight: Word;
|
||
lOptions: Word;
|
||
Len: Byte;
|
||
lFontName: UTF8String = '';
|
||
isDefaultFont: Boolean;
|
||
begin
|
||
FFont := TsFont.Create;
|
||
|
||
{ Height of the font in twips = 1/20 of a point }
|
||
lHeight := WordLEToN(AStream.ReadWord);
|
||
FFont.Size := lHeight/20;
|
||
|
||
{ Option flags }
|
||
lOptions := WordLEToN(AStream.ReadWord);
|
||
FFont.Style := [];
|
||
if lOptions and $0001 <> 0 then Include(FFont.Style, fssBold);
|
||
if lOptions and $0002 <> 0 then Include(FFont.Style, fssItalic);
|
||
if lOptions and $0004 <> 0 then Include(FFont.Style, fssUnderline);
|
||
if lOptions and $0008 <> 0 then Include(FFont.Style, fssStrikeout);
|
||
|
||
{ Font name: Unicodestring, char count in 1 byte }
|
||
Len := AStream.ReadByte();
|
||
SetLength(lFontName, Len);
|
||
AStream.ReadBuffer(lFontName[1], Len);
|
||
FFont.FontName := lFontName;
|
||
|
||
isDefaultFont := FFontList.Count = 0;
|
||
|
||
{ Add font to internal font list }
|
||
FFontList.Add(FFont);
|
||
|
||
if isDefaultFont then
|
||
TsWorkbook(FWorkbook).SetDefaultFont(FFont.FontName, FFont.Size);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadFONTCOLOR(AStream: TStream);
|
||
var
|
||
lColor: Word;
|
||
begin
|
||
lColor := WordLEToN(AStream.ReadWord); // Palette index
|
||
FFont.Color := IfThen(lColor = SYS_DEFAULT_WINDOW_TEXT_COLOR,
|
||
scBlack, FPalette[lColor]);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads the FORMAT record required for formatting numerical data
|
||
-------------------------------------------------------------------------------}
|
||
(*
|
||
procedure TsSpreadBIFF2Reader.ReadFORMAT(AStream: TStream);
|
||
begin
|
||
Unused(AStream);
|
||
// We ignore the formats in the file, everything is known
|
||
// (Using the formats in the file would require de-localizing them).
|
||
end;*)
|
||
procedure TsSpreadBIFF2Reader.ReadFormat(AStream: TStream);
|
||
var
|
||
len: byte;
|
||
fmtString: AnsiString = '';
|
||
nfs: String;
|
||
begin
|
||
// number format string
|
||
len := AStream.ReadByte;
|
||
SetLength(fmtString, len);
|
||
AStream.ReadBuffer(fmtString[1], len);
|
||
|
||
// We need the format string as utf8 and non-localized
|
||
nfs := ConvertEncoding(fmtString, FCodePage, encodingUTF8);
|
||
|
||
// Add to the end of the list.
|
||
NumFormatList.Add(nfs);
|
||
end;
|
||
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadFromStream(AStream: TStream;
|
||
APassword: String = ''; AParams: TsStreamParams = []);
|
||
var
|
||
BIFF2EOF: Boolean;
|
||
RecordType: Word;
|
||
CurStreamPos: Int64;
|
||
BOFFound: Boolean;
|
||
begin
|
||
Unused(APassword, AParams);
|
||
BIFF2EOF := False;
|
||
|
||
{ In BIFF2 files there is only one worksheet, let's create it }
|
||
FWorksheet := TsWorkbook(FWorkbook).AddWorksheet('Sheet', true);
|
||
|
||
{ Read all records in a loop }
|
||
BOFFound := false;
|
||
while not BIFF2EOF do
|
||
begin
|
||
{ Read the record header }
|
||
RecordType := WordLEToN(AStream.ReadWord);
|
||
RecordSize := WordLEToN(AStream.ReadWord);
|
||
|
||
CurStreamPos := AStream.Position;
|
||
|
||
case RecordType of
|
||
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
|
||
INT_EXCEL_ID_BOF : BOFFound := true;
|
||
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
|
||
INT_EXCEL_ID_BOTTOMMARGIN : ReadMargin(AStream, 3);
|
||
INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream);
|
||
INT_EXCEL_ID_COLUMNDEFAULT : ReadColumnDefault(AStream);
|
||
INT_EXCEL_ID_COLWIDTH : ReadColWidth(AStream);
|
||
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
|
||
INT_EXCEL_ID_EOF : BIFF2EOF := True;
|
||
INT_EXCEL_ID_FONT : ReadFont(AStream);
|
||
INT_EXCEL_ID_FONTCOLOR : ReadFontColor(AStream);
|
||
INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false);
|
||
INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
|
||
INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
|
||
INT_EXCEL_ID_HEADER : ReadHeaderFooter(AStream, true);
|
||
INT_EXCEL_ID_HORZPAGEBREAK : ReadHorizontalPageBreaks(AStream, FWorksheet);
|
||
INT_EXCEL_ID_INTEGER : ReadInteger(AStream);
|
||
INT_EXCEL_ID_IXFE : ReadIXFE(AStream);
|
||
INT_EXCEL_ID_LABEL : ReadLabel(AStream);
|
||
INT_EXCEL_ID_LEFTMARGIN : ReadMargin(AStream, 0);
|
||
INT_EXCEL_ID_NOTE : ReadComment(AStream);
|
||
INT_EXCEL_ID_NUMBER : ReadNumber(AStream);
|
||
INT_EXCEL_ID_PANE : ReadPane(AStream);
|
||
INT_EXCEL_ID_OBJECTPROTECT : ReadObjectProtect(AStream);
|
||
INT_EXCEL_ID_PASSWORD : ReadPASSWORD(AStream);
|
||
INT_EXCEL_ID_PRINTGRID : ReadPrintGridLines(AStream);
|
||
INT_EXCEL_ID_PRINTHEADERS : ReadPrintHeaders(AStream);
|
||
INT_EXCEL_ID_PROTECT : ReadPROTECT(AStream);
|
||
INT_EXCEL_ID_RIGHTMARGIN : ReadMargin(AStream, 1);
|
||
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
|
||
INT_EXCEL_ID_SELECTION : ReadSELECTION(AStream);
|
||
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
|
||
INT_EXCEL_ID_TOPMARGIN : ReadMargin(AStream, 2);
|
||
INT_EXCEL_ID_DEFROWHEIGHT : ReadDefRowHeight(AStream);
|
||
INT_EXCEL_ID_VERTPAGEBREAK : ReadVerticalPageBreaks(AStream, FWorksheet);
|
||
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
|
||
INT_EXCEL_ID_WINDOWPROTECT : ReadWindowProtect(AStream);
|
||
INT_EXCEL_ID_XF : ReadXF(AStream);
|
||
else
|
||
// nothing
|
||
end;
|
||
|
||
// Make sure we are in the right position for the next record
|
||
AStream.Seek(CurStreamPos + RecordSize, soFromBeginning);
|
||
|
||
if AStream.Position >= AStream.Size then
|
||
BIFF2EOF := True;
|
||
|
||
if not BOFFound then
|
||
raise EFPSpreadsheetReader.Create('BOF record not found.');
|
||
end;
|
||
|
||
FixCols(FWorksheet);
|
||
FixRows(FWorksheet);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadFormula(AStream: TStream);
|
||
var
|
||
ARow, ACol: Cardinal;
|
||
XF: Word;
|
||
ok: Boolean;
|
||
formulaResult: Double = 0.0;
|
||
Data: array [0..7] of byte;
|
||
dt: TDateTime;
|
||
nf: TsNumberFormat;
|
||
nfs: String;
|
||
err: TsErrorValue;
|
||
cell: PCell;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
sheet := TsWorksheet(FWorksheet);
|
||
|
||
{ BIFF Record row/column/style }
|
||
ReadRowColXF(AStream, ARow, ACol, XF);
|
||
|
||
{ Result of the formula result in IEEE 754 floating-point value }
|
||
Data[0] := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(Data, Sizeof(Data));
|
||
|
||
{ Recalculation byte - currently not used }
|
||
AStream.ReadByte;
|
||
|
||
{ Create cell }
|
||
if FIsVirtualMode then begin
|
||
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := sheet.AddCell(ARow, ACol);
|
||
|
||
// Now determine the type of the formula result
|
||
if (Data[6] = $FF) and (Data[7] = $FF) then
|
||
case Data[0] of
|
||
0: // String -> Value is found in next record (STRING)
|
||
FIncompleteCell := cell;
|
||
1: // Boolean value
|
||
sheet.WriteBoolValue(cell, Data[2] = 1);
|
||
2: begin // Error value
|
||
case Data[2] of
|
||
ERR_INTERSECTION_EMPTY : err := errEmptyIntersection;
|
||
ERR_DIVIDE_BY_ZERO : err := errDivideByZero;
|
||
ERR_WRONG_TYPE_OF_OPERAND: err := errWrongType;
|
||
ERR_ILLEGAL_REFERENCE : err := errIllegalRef;
|
||
ERR_WRONG_NAME : err := errWrongName;
|
||
ERR_OVERFLOW : err := errOverflow;
|
||
ERR_ARG_ERROR : err := errArgError;
|
||
end;
|
||
sheet.WriteErrorValue(cell, err);
|
||
end;
|
||
3: // Empty cell
|
||
sheet.WriteBlank(cell);
|
||
end
|
||
else
|
||
begin
|
||
// Result is a number or a date/time
|
||
Move(Data[0], formulaResult, SizeOf(Data));
|
||
|
||
{Find out what cell type, set content type and value}
|
||
ExtractNumberFormat(XF, nf, nfs);
|
||
if IsDateTime(formulaResult, nf, nfs, dt) then
|
||
sheet.WriteDateTime(cell, dt, nf, nfs)
|
||
else
|
||
sheet.WriteNumber(cell, formulaResult, nf, nfs);
|
||
end;
|
||
|
||
{ Formula token array }
|
||
if (boReadFormulas in FWorkbook.Options) then
|
||
begin
|
||
ok := ReadRPNTokenArray(AStream, cell);
|
||
if not ok then sheet.WriteErrorValue(cell, errFormulaNotSupported);
|
||
end;
|
||
|
||
{ Apply formatting to cell }
|
||
ApplyCellFormatting(cell, XF);
|
||
|
||
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadLabel(AStream: TStream);
|
||
var
|
||
rec: TBIFF2_LabelRecord;
|
||
L: Byte;
|
||
ARow, ACol: Cardinal;
|
||
XF: Word;
|
||
ansiStr: ansistring = '';
|
||
valueStr: UTF8String;
|
||
cell: PCell;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
{ Read entire record, starting at Row, except for string data }
|
||
rec.Row := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2_LabelRecord) - 2*SizeOf(Word));
|
||
ARow := WordLEToN(rec.Row);
|
||
ACol := WordLEToN(rec.Col);
|
||
XF := rec.Attrib1 and $3F;
|
||
if XF = 63 then XF := FPendingXFIndex;
|
||
|
||
{ String with 8-bit size }
|
||
L := rec.TextLen;
|
||
SetLength(ansiStr, L);
|
||
AStream.ReadBuffer(ansiStr[1], L);
|
||
|
||
{ Save the data }
|
||
valueStr := ConvertEncoding(ansiStr, FCodePage, encodingUTF8);
|
||
|
||
{ Create cell }
|
||
if FIsVirtualMode then begin
|
||
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := sheet.AddCell(ARow, ACol);
|
||
sheet.WriteText(cell, valueStr);
|
||
|
||
{ Apply formatting to cell }
|
||
ApplyCellFormatting(cell, XF);
|
||
|
||
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadNumber(AStream: TStream);
|
||
var
|
||
rec: TBIFF2_NumberRecord;
|
||
ARow, ACol: Cardinal;
|
||
XF: Word;
|
||
value: Double = 0.0;
|
||
dt: TDateTime;
|
||
nf: TsNumberFormat;
|
||
nfs: String;
|
||
cell: PCell;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
{ Read entire record, starting at Row }
|
||
rec.Row := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2_NumberRecord) - 2*SizeOf(Word));
|
||
ARow := WordLEToN(rec.Row);
|
||
ACol := WordLEToN(rec.Col);
|
||
XF := rec.Attrib1 and $3F;
|
||
if XF = 63 then XF := FPendingXFIndex;
|
||
value := rec.Value;
|
||
|
||
{Create cell}
|
||
if FIsVirtualMode then begin
|
||
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := sheet.AddCell(ARow, ACol);
|
||
|
||
{Find out what cell type, set content type and value}
|
||
ExtractNumberFormat(XF, nf, nfs);
|
||
if IsDateTime(value, nf, nfs, dt) then
|
||
sheet.WriteDateTime(cell, dt, nf, nfs)
|
||
else
|
||
sheet.WriteNumber(cell, value, nf, nfs);
|
||
|
||
{ Apply formatting to cell }
|
||
ApplyCellFormatting(cell, XF);
|
||
|
||
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadInteger(AStream: TStream);
|
||
var
|
||
sheet: TsWorksheet;
|
||
ARow, ACol: Cardinal;
|
||
XF: Word;
|
||
AWord : Word = 0;
|
||
cell: PCell;
|
||
rec: TBIFF2_IntegerRecord;
|
||
begin
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
{ Read record into buffer }
|
||
rec.Row := 0; // to silence the comiler...
|
||
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF2_NumberRecord) - 2*SizeOf(Word));
|
||
ARow := WordLEToN(rec.Row);
|
||
ACol := WordLEToN(rec.Col);
|
||
XF := rec.Attrib1 and $3F;
|
||
if XF = 63 then XF := FPendingXFIndex;
|
||
AWord := WordLEToN(rec.Value);
|
||
|
||
{ Create cell }
|
||
if FIsVirtualMode then
|
||
begin
|
||
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
||
cell := @FVirtualCell;
|
||
end else
|
||
cell := sheet.AddCell(ARow, ACol);
|
||
|
||
{ Save the data }
|
||
sheet.WriteNumber(cell, AWord);
|
||
|
||
{ Apply formatting to cell }
|
||
ApplyCellFormatting(cell, XF);
|
||
|
||
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
||
TsWorkbook(FWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads an IXFE record. This record contains the "true" XF index of a cell. It
|
||
is used if there are more than 62 XF records (XF field is only 6-bit). The
|
||
IXFE record is used in front of the cell record using it
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadIXFE(AStream: TStream);
|
||
begin
|
||
FPendingXFIndex := WordLEToN(AStream.ReadWord);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads a PASSWORD record. Since BIFF2 does not have multiple worksheets the
|
||
same password is stored in the workbook and worksheet cryptoinfo records.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadPASSWORD(AStream: TStream);
|
||
var
|
||
hash: Word;
|
||
cinfo: TsCryptoInfo;
|
||
begin
|
||
hash := WordLEToN(AStream.ReadWord);
|
||
if hash = 0 then
|
||
exit; // no password
|
||
|
||
InitCryptoInfo(cinfo);
|
||
cinfo.PasswordHash := Format('%.4x', [hash]);
|
||
cinfo.Algorithm := caExcel;
|
||
|
||
// Use the same password for workbook and worksheet protection because
|
||
// BIFF2 can have only a single sheet.
|
||
(FWorkbook as TsWorkbook).CryptoInfo := cinfo;
|
||
(FWorksheet as TsWorksheet).CryptoInfo := cinfo;
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadPROTECT(AStream: TStream);
|
||
begin
|
||
inherited ReadPROTECT(AStream);
|
||
(FWorksheet as TsWorksheet).Protect(Workbook.IsProtected);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads the row, column and xf index from the stream
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadRowColXF(AStream: TStream;
|
||
out ARow, ACol: Cardinal; out AXF: WORD);
|
||
begin
|
||
{ BIFF Record data for row and column}
|
||
ARow := WordLEToN(AStream.ReadWord);
|
||
ACol := WordLEToN(AStream.ReadWord);
|
||
|
||
{ Index to XF record }
|
||
AXF := AStream.ReadByte and $3F;
|
||
// If AXF = $3F = 63 then there is an IXFE record containing the true XF index!
|
||
if AXF = $3F then
|
||
AXF := FPendingXFIndex;
|
||
|
||
{ Index to format and font record, cell style - ignored because contained in XF
|
||
Must read to keep the record in sync. }
|
||
AStream.ReadWord;
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadRowInfo(AStream: TStream);
|
||
type
|
||
TRowRecord = packed record
|
||
RowIndex: Word;
|
||
Col1: Word;
|
||
Col2: Word;
|
||
Height: Word;
|
||
NotUsed: Word;
|
||
ContainsXF: Byte;
|
||
OffsetToCell: Word;
|
||
Attributes1: Byte;
|
||
Attributes2: Byte;
|
||
Attributes3: Byte;
|
||
XFIndex: Word;
|
||
end;
|
||
var
|
||
rowrec: TRowRecord;
|
||
lRow: PRow;
|
||
h: word;
|
||
auto: Boolean;
|
||
rowheight: Single;
|
||
defRowHeight: Single;
|
||
containsXF: Boolean;
|
||
xf: Word;
|
||
book: TsWorkbook;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
book := FWorkbook as TsWorkbook;
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
rowRec.RowIndex := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(rowrec, SizeOf(TRowRecord));
|
||
h := WordLEToN(rowrec.Height);
|
||
auto := h and $8000 <> 0;
|
||
rowheight := book.ConvertUnits(TwipsToPts(h and $7FFF), suPoints, FWorkbook.Units);
|
||
defRowHeight := sheet.ReadDefaultRowHeight(FWorkbook.Units);
|
||
containsXF := rowRec.ContainsXF = 1;
|
||
xf := WordLEToN(rowRec.XFIndex);
|
||
|
||
// No row record if rowheight in file is the same as the default rowheight and
|
||
// if there is no formatting record.
|
||
if SameValue(rowheight, defRowHeight, ROWHEIGHT_EPS) and (not containsXF) then
|
||
exit;
|
||
|
||
// Otherwise: create a row record
|
||
lRow := sheet.GetRow(WordLEToN(rowrec.RowIndex));
|
||
lRow^.Height := rowHeight;
|
||
if auto then
|
||
lRow^.RowHeightType := rhtAuto else
|
||
lRow^.RowHeightType := rhtCustom;
|
||
lRow^.FormatIndex := XFToFormatIndex(xf);
|
||
end;
|
||
|
||
{ ------------------------------------------------------------------------------
|
||
Reads the RPN attribute. Most attributes are not handled, but the
|
||
data associated with it must be read to keep the stream in sync with the
|
||
data structure.
|
||
This version of the method is valid for BIFF2 only.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadRPNAttr(AStream: TStream; AIdentifier: Byte);
|
||
var
|
||
nc: Byte;
|
||
begin
|
||
case AIdentifier of
|
||
$01: AStream.ReadByte; // tAttrVolatile, data not used
|
||
$02: AStream.ReadByte; // tAttrIF, not explicitely used
|
||
$04: begin // tAttrChoose, not supported
|
||
nc := AStream.ReadByte;
|
||
AStream.Position := AStream.Position + 2*Int64(nc) + 1;
|
||
end;
|
||
$08: AStream.ReadByte; // tAttrSkip, data not used
|
||
$10: AStream.ReadByte; // tAttrSum, to be processed by ReadRPNTokenArray, byte not used
|
||
$20: AStream.ReadByte; // tAttrAssign, not used
|
||
end;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads the identifier for an RPN function with fixed argument count from the
|
||
stream.
|
||
Valid for BIFF2-BIFF3.
|
||
-------------------------------------------------------------------------------}
|
||
function TsSpreadBIFF2Reader.ReadRPNFunc(AStream: TStream): Word;
|
||
var
|
||
b: Byte;
|
||
begin
|
||
b := AStream.ReadByte;
|
||
Result := b;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads the cell coordiantes of the top/left cell of a range using a
|
||
shared formula.
|
||
This cell contains the rpn token sequence of the formula.
|
||
Is overridden because BIFF2 has 1 byte for column.
|
||
Code is not called for shared formulas (which are not supported by BIFF2), but
|
||
maybe for array formulas.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadRPNSharedFormulaBase(AStream: TStream;
|
||
out ARow, ACol: Cardinal);
|
||
begin
|
||
// 2 bytes for row of first cell in shared formula
|
||
ARow := WordLEToN(AStream.ReadWord);
|
||
// 1 byte for column of first cell in shared formula
|
||
ACol := AStream.ReadByte;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Helper funtion for reading of the size of the token array of an RPN formula.
|
||
Is overridden because BIFF2 uses 1 byte only.
|
||
-------------------------------------------------------------------------------}
|
||
function TsSpreadBIFF2Reader.ReadRPNTokenArraySize(AStream: TStream): Word;
|
||
begin
|
||
Result := AStream.ReadByte;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads a STRING record which contains the result of string formula.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadStringRecord(AStream: TStream);
|
||
var
|
||
len: Byte;
|
||
s: ansistring = '';
|
||
begin
|
||
// The string is a byte-string with 8 bit length
|
||
len := AStream.ReadByte;
|
||
if len > 0 then
|
||
begin
|
||
SetLength(s, Len);
|
||
AStream.ReadBuffer(s[1], len);
|
||
if (FIncompleteCell <> nil) and (s <> '') then
|
||
begin
|
||
// The "IncompleteCell" has been identified in the sheet when reading
|
||
// the FORMULA record which precedes the String record.
|
||
// FIncompleteCell^.UTF8StringValue := AnsiToUTF8(s);
|
||
FIncompleteCell^.UTF8StringValue := ConvertEncoding(s, FCodePage, encodingUTF8);
|
||
FIncompleteCell^.ContentType := cctUTF8String;
|
||
if FIsVirtualMode then
|
||
TsWorkbook(FWorkbook).OnReadCellData(FWorkbook,
|
||
FIncompleteCell^.Row, FIncompleteCell^.Col, FIncompleteCell
|
||
);
|
||
end;
|
||
end;
|
||
FIncompleteCell := nil;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Reads the WINDOW2 record containing information like "show grid lines",
|
||
"show sheet headers", "panes are frozen", etc.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Reader.ReadWindow2(AStream: TStream);
|
||
begin
|
||
// Show formulas, not results
|
||
AStream.ReadByte;
|
||
|
||
// Show grid lines
|
||
if AStream.ReadByte <> 0 then
|
||
FWorksheet.Options := FWorksheet.Options + [soShowGridLines]
|
||
else
|
||
FWorksheet.Options := FWorksheet.Options - [soShowGridLines];
|
||
|
||
// Show sheet headers
|
||
if AStream.ReadByte <> 0 then
|
||
FWorksheet.Options := FWorksheet.Options + [soShowHeaders]
|
||
else
|
||
FWorksheet.Options := FWorksheet.Options - [soShowHeaders];
|
||
|
||
// Panes are frozen
|
||
if AStream.ReadByte <> 0 then
|
||
FWorksheet.Options := FWorksheet.Options + [soHasFrozenPanes]
|
||
else
|
||
FWorksheet.Options := FWorksheet.Options - [soHasFrozenPanes];
|
||
|
||
// Show zero values
|
||
AStream.ReadByte;
|
||
|
||
// Index to first visible row
|
||
WordLEToN(AStream.ReadWord);
|
||
|
||
// Indoex to first visible column
|
||
WordLEToN(AStream.ReadWord);
|
||
|
||
// Use automatic grid line color (0= manual)
|
||
AStream.ReadByte;
|
||
|
||
// Manual grid line line color (rgb)
|
||
DWordToLE(AStream.ReadDWord);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Reader.ReadXF(AStream: TStream);
|
||
var
|
||
rec: TBIFF2_XFRecord;
|
||
fmt: TsCellFormat;
|
||
b: Byte;
|
||
nf: TsNumFormatParams;
|
||
nfs: String;
|
||
i: Integer;
|
||
fnt: TsFont;
|
||
book: TsWorkbook;
|
||
begin
|
||
book := FWorkbook as TsWorkbook;
|
||
|
||
// Read entire xf record into buffer
|
||
InitFormatRecord(fmt);
|
||
fmt.ID := FCellFormatList.Count;
|
||
|
||
rec.FontIndex := 0; // to silence the compiler...
|
||
AStream.ReadBuffer(rec.FontIndex, SizeOf(rec) - 2*SizeOf(word));
|
||
|
||
// Font index
|
||
i := rec.FontIndex;
|
||
if i > 4 then dec(i); // Watch out for the nasty missing font #4...
|
||
fnt := TsFont(FFontList[i]);
|
||
fmt.FontIndex := book.FindFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color);
|
||
if fmt.FontIndex = -1 then
|
||
fmt.FontIndex := book.AddFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color);
|
||
if fmt.FontIndex > 0 then
|
||
Include(fmt.UsedFormattingFields, uffFont);
|
||
|
||
// Number format index
|
||
b := rec.NumFormat_Prot and $3F;
|
||
nfs := NumFormatList[b];
|
||
if nfs <> '' then
|
||
begin
|
||
fmt.NumberFormatIndex := book.AddNumberFormat(nfs);
|
||
nf := book.GetNumberFormat(fmt.NumberFormatIndex);
|
||
fmt.NumberFormat := nf.NumFormat;
|
||
fmt.NumberFormatStr := nf.NumFormatStr;
|
||
if fmt.NumberFormat <> nfGeneral then
|
||
Include(fmt.UsedFormattingFields, uffNumberFormat);
|
||
end;
|
||
|
||
// Horizontal alignment
|
||
b := rec.HorAlign_Border_BkGr and MASK_XF_HOR_ALIGN;
|
||
if (b <= ord(High(TsHorAlignment))) then
|
||
begin
|
||
fmt.HorAlignment := TsHorAlignment(b);
|
||
if fmt.HorAlignment <> haDefault then
|
||
Include(fmt.UsedFormattingFields, uffHorAlign);
|
||
end;
|
||
|
||
// Vertical alignment - not used in BIFF2
|
||
fmt.VertAlignment := vaDefault;
|
||
|
||
// Word wrap - not used in BIFF2
|
||
// -- nothing to do here
|
||
|
||
// Text rotation - not used in BIFF2
|
||
// -- nothing to do here
|
||
|
||
// Borders
|
||
fmt.Border := [];
|
||
if rec.HorAlign_Border_BkGr and $08 <> 0 then
|
||
Include(fmt.Border, cbWest);
|
||
if rec.HorAlign_Border_BkGr and $10 <> 0 then
|
||
Include(fmt.Border, cbEast);
|
||
if rec.HorAlign_Border_BkGr and $20 <> 0 then
|
||
Include(fmt.Border, cbNorth);
|
||
if rec.HorAlign_Border_BkGr and $40 <> 0 then
|
||
Include(fmt.Border, cbSouth);
|
||
if fmt.Border <> [] then
|
||
Include(fmt.UsedFormattingFields, uffBorder);
|
||
|
||
// Background color not supported, only shaded background
|
||
if rec.HorAlign_Border_BkGr and $80 <> 0 then
|
||
begin
|
||
fmt.Background.Style := fsGray50;
|
||
fmt.Background.FgColor := scBlack;
|
||
fmt.Background.BgColor := scTransparent;
|
||
Include(fmt.UsedFormattingFields, uffBackground);
|
||
end;
|
||
|
||
// Protection
|
||
b := rec.NumFormat_Prot and $C0;
|
||
case b of
|
||
$00: fmt.Protection := [];
|
||
$40: fmt.Protection := [cpLockCell];
|
||
$80: fmt.Protection := [cpHideFormulas];
|
||
$C0: fmt.Protection := [cpLockCell, cpHideFormulas];
|
||
end;
|
||
if fmt.Protection <> DEFAULT_CELL_PROTECTION then
|
||
Include(fmt.UsedFormattingFields, uffProtection);
|
||
|
||
// Add the decoded data to the format list
|
||
FCellFormatList.Add(fmt);
|
||
end;
|
||
|
||
|
||
{------------------------------------------------------------------------------}
|
||
{ TsSpreadBIFF2Writer }
|
||
{------------------------------------------------------------------------------}
|
||
|
||
constructor TsSpreadBIFF2Writer.Create(AWorkbook: TsBasicWorkbook);
|
||
begin
|
||
inherited Create(AWorkbook);
|
||
|
||
InitBiff2Limitations(FLimitations);
|
||
|
||
FDateMode := Excel2Settings.DateMode;
|
||
FCodePage := Excel2Settings.CodePage;
|
||
FSheetIndex := Excel2Settings.SheetIndex;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Adds the built-in number formats to the NumFormatList.
|
||
Inherited method overridden for BIFF2 specialties.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.AddBuiltInNumFormats;
|
||
begin
|
||
FFirstNumFormatIndexInFile := 0;
|
||
InternalAddBuiltInNumFormats(FNumFormatList, Workbook.FormatSettings);
|
||
end;
|
||
|
||
function TsSpreadBIFF2Writer.FunctionSupported(AExcelCode: Integer;
|
||
const AFuncName: String): Boolean;
|
||
begin
|
||
Result := inherited and (AExcelCode < 200);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Determines the formatting attributes of a cell, row or column. This is needed,
|
||
for example, for writing a cell content record, such as WriteLabel,
|
||
WriteNumber, etc.
|
||
|
||
The attributes contain, in bit masks, xf record index, font index,
|
||
borders, etc.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.GetAttributes(AFormatIndex: Integer;
|
||
XFIndex: Word; out Attrib1, Attrib2, Attrib3: Byte);
|
||
var
|
||
fmt: PsCellFormat;
|
||
fontIdx, formatIdx: Integer;
|
||
begin
|
||
fmt := (Workbook as TsWorkbook).GetPointerToCellFormat(AFormatIndex);
|
||
|
||
if fmt^.UsedFormattingFields = [] then begin
|
||
Attrib1 := 15 + MASK_XF_TYPE_PROT_LOCKED_BIFF2; // $40
|
||
Attrib2 := 0;
|
||
Attrib3 := 0;
|
||
exit;
|
||
end;
|
||
|
||
// 1st byte:
|
||
// Mask $3F: Index to XF record
|
||
// Mask $40: 1 = Cell is locked
|
||
// Mask $80: 1 = Formula is hidden
|
||
Attrib1 := Min(XFIndex, $3F) and $3F;
|
||
if cpLockCell in fmt^.Protection then
|
||
Attrib1 := Attrib1 or MASK_XF_TYPE_PROT_LOCKED_BIFF2;
|
||
if cpHideFormulas in fmt^.Protection then
|
||
Attrib1 := Attrib1 or MASK_XF_TYPE_PROT_FORMULA_HIDDEN_BIFF2;
|
||
|
||
// 2nd byte:
|
||
// Mask $3F: Index to FORMAT record ("FORMAT" = number format!)
|
||
// Mask $C0: Index to FONT record
|
||
GetFormatAndFontIndex(fmt, formatIdx, fontIdx);
|
||
Attrib2 := formatIdx + fontIdx shr 6;
|
||
// Attrib2 := fmt^.FontIndex shr 6;
|
||
|
||
// 3rd byte
|
||
// Mask $07: horizontal alignment
|
||
// Mask $08: Cell has left border
|
||
// Mask $10: Cell has right border
|
||
// Mask $20: Cell has top border
|
||
// Mask $40: Cell has bottom border
|
||
// Mask $80: Cell has shaded background
|
||
Attrib3 := 0;
|
||
if uffHorAlign in fmt^.UsedFormattingFields then
|
||
Attrib3 := ord (fmt^.HorAlignment);
|
||
if uffBorder in fmt^.UsedFormattingFields then begin
|
||
if cbNorth in fmt^.Border then Attrib3 := Attrib3 or $20;
|
||
if cbWest in fmt^.Border then Attrib3 := Attrib3 or $08;
|
||
if cbEast in fmt^.Border then Attrib3 := Attrib3 or $10;
|
||
if cbSouth in fmt^.Border then Attrib3 := Attrib3 or $40;
|
||
end;
|
||
if (uffBackground in fmt^.UsedFormattingFields) then
|
||
Attrib3 := Attrib3 or $80;
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.GetFormatAndFontIndex(AFormatRecord: PsCellFormat;
|
||
out AFormatIndex, AFontIndex: Integer);
|
||
var
|
||
nfparams: TsNumFormatParams;
|
||
nfs: String;
|
||
begin
|
||
{ Index to FORMAT record }
|
||
AFormatIndex := 0;
|
||
if (AFormatRecord <> nil) and (uffNumberFormat in AFormatRecord^.UsedFormattingFields) then
|
||
begin
|
||
nfParams := TsWorkbook(FWorkbook).GetNumberFormat(AFormatRecord^.NumberFormatIndex);
|
||
nfs := nfParams.NumFormatStr;
|
||
AFormatIndex := NumFormatList.IndexOf(nfs);
|
||
if AFormatIndex = -1 then AFormatIndex := 0;
|
||
end;
|
||
|
||
{ Index to FONT record }
|
||
AFontIndex := 0;
|
||
if (AFormatRecord <> nil) and (uffFont in AFormatRecord^.UsedFormattingFields) then
|
||
begin
|
||
AFontIndex := AFormatRecord^.FontIndex;
|
||
if AFontIndex >= 4 then inc(AFontIndex); // Font #4 does not exist in BIFF
|
||
end;
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.PopulatePalette(AWorkbook: TsBasicWorkbook);
|
||
begin
|
||
FPalette.Clear;
|
||
FPalette.AddBuiltinColors(false);
|
||
{ The next instruction creates an error log entry in CheckLimitations
|
||
if the workbook contains more colors than the default 8.
|
||
This is because BIFF2 can only have a palette with 8 colors. }
|
||
FPalette.CollectFromWorkbook(AWorkbook);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Attaches cell formatting data for the given cell to the current record.
|
||
Is called from all writing methods of cell contents and rows
|
||
|
||
@param AFormatIndex Index into the workbook's FCellFormatList
|
||
@param XFIndex Index of the XF record used here
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteCellAttributes(AStream: TStream;
|
||
AFormatIndex: Integer; XFIndex: Word);
|
||
type
|
||
TCellFmtRecord = packed record
|
||
XFIndex_Locked_Hidden: Byte;
|
||
Format_Font: Byte;
|
||
Align_Border_BkGr: Byte;
|
||
end;
|
||
var
|
||
rec: TCellFmtRecord;
|
||
fmt: PsCellFormat;
|
||
w: Word;
|
||
begin
|
||
fmt := TsWorkbook(FWorkbook).GetPointerToCellFormat(AFormatIndex);
|
||
rec.XFIndex_Locked_Hidden := 0; // to silence the compiler...
|
||
FillChar(rec, SizeOf(rec), 0);
|
||
|
||
if fmt^.UsedFormattingFields <> [] then
|
||
begin
|
||
// 1st byte:
|
||
// Mask $3F: Index to XF record
|
||
// Mask $40: 1 = Cell is locked
|
||
// Mask $80: 1 = Formula is hidden
|
||
rec.XFIndex_Locked_Hidden := Min(XFIndex, $3F) and $3F;
|
||
if cpLockCell in fmt^.Protection then
|
||
rec.XFIndex_Locked_Hidden := rec.XFIndex_Locked_Hidden or MASK_XF_TYPE_PROT_LOCKED_BIFF2;
|
||
if cpHideFormulas in fmt^.Protection then
|
||
rec.XFIndex_Locked_Hidden := rec.XFIndex_Locked_Hidden or MASK_XF_TYPE_PROT_FORMULA_HIDDEN_BIFF2;
|
||
|
||
// 2nd byte:
|
||
// Mask $3F: Index to FORMAT record
|
||
// Mask $C0: Index to FONT record
|
||
w := fmt^.FontIndex shr 6; // was shl --> MUST BE shr! // ??????????????????????
|
||
rec.Format_Font := Lo(w);
|
||
|
||
// 3rd byte
|
||
// Mask $07: horizontal alignment
|
||
// Mask $08: Cell has left border
|
||
// Mask $10: Cell has right border
|
||
// Mask $20: Cell has top border
|
||
// Mask $40: Cell has bottom border
|
||
// Mask $80: Cell has shaded background
|
||
if uffHorAlign in fmt^.UsedFormattingFields then
|
||
rec.Align_Border_BkGr := ord(fmt^.HorAlignment);
|
||
if uffBorder in fmt^.UsedFormattingFields then begin
|
||
if cbNorth in fmt^.Border then
|
||
rec.Align_Border_BkGr := rec.Align_Border_BkGr or $20;
|
||
if cbWest in fmt^.Border then
|
||
rec.Align_Border_BkGr := rec.Align_Border_BkGr or $08;
|
||
if cbEast in fmt^.Border then
|
||
rec.Align_Border_BkGr := rec.Align_Border_BkGr or $10;
|
||
if cbSouth in fmt^.Border then
|
||
rec.Align_Border_BkGr := rec.Align_Border_BkGr or $40;
|
||
end;
|
||
if uffBackground in fmt^.UsedFormattingFields then
|
||
rec.Align_Border_BkGr := rec.Align_Border_BkGr or $80;
|
||
end;
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 CODEPAGE record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteCodePage(AStream: TStream; ACodePage: String);
|
||
// AEncoding: TsEncoding);
|
||
begin
|
||
if ACodePage = 'cp1251' then begin
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_CODEPAGE));
|
||
AStream.WriteWord(WordToLE(2));
|
||
AStream.WriteWord(WordToLE(WORD_CP_1258_Latin1_BIFF2_3));
|
||
FCodePage := ACodePage;
|
||
end else
|
||
inherited;
|
||
(*
|
||
if AEncoding = seLatin1 then begin
|
||
cp := WORD_CP_1258_Latin1_BIFF2_3;
|
||
FCodePage := 'cp1252';
|
||
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_CODEPAGE));
|
||
AStream.WriteWord(WordToLE(2));
|
||
AStream.WriteWord(WordToLE(cp));
|
||
end else
|
||
inherited; *)
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 COLUMNDEFAULT record containing default column formatting of
|
||
specified columns
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteColumnDefault(AStream: TStream;
|
||
AFirstColIndex, ALastColIndex: Word; AFormatIndex: Integer);
|
||
//ACol: PCol);
|
||
var
|
||
attr1, attr2, attr3: Byte;
|
||
xf: Word;
|
||
begin
|
||
{ BIFF record header }
|
||
WriteBIFFHeader(AStream, INT_EXCEL_ID_COLUMNDEFAULT, 2+2+3+2);
|
||
|
||
{ Index to first column }
|
||
AStream.WriteWord(WordToLE(AFirstColIndex));
|
||
|
||
{ Index to last column }
|
||
AStream.WriteWord(WordToLE(ALastColIndex + 1));
|
||
// Unlike specified in the excelfileformat.pdf, Excel 2 wants to have the
|
||
// last column index incremented by 1!
|
||
|
||
{ Attributes }
|
||
xf := FindXFIndex(AFormatIndex);
|
||
GetAttributes(AFormatIndex, xf, attr1, attr2, attr3);
|
||
AStream.WriteByte(attr1);
|
||
AStream.WriteByte(attr2);
|
||
AStream.WriteByte(attr3);
|
||
|
||
{ Not used }
|
||
AStream.WriteWord(0);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.WriteColumnDefaults(AStream: TStream);
|
||
var
|
||
j, j1: Integer;
|
||
sheet: TsWorksheet;
|
||
lCol, lCol1: PCol;
|
||
lastcol: Integer;
|
||
begin
|
||
sheet := TsWorkbook(FWorkbook).GetFirstWorksheet;
|
||
j := 0;
|
||
while (j < sheet.Cols.Count) do begin
|
||
lCol := PCol(sheet.Cols[j]);
|
||
j1 := j;
|
||
lastcol := lCol^.Col;
|
||
while (j1 < sheet.Cols.Count) do begin
|
||
lCol1 := PCol(sheet.Cols[j1]);
|
||
if lCol1^.FormatIndex <> lCol^.FormatIndex then
|
||
break;
|
||
lastCol := lCol1^.Col;
|
||
inc(j1);
|
||
end;
|
||
WriteColumnDefault(AStream, lCol^.Col, lastCol, lCol^.FormatIndex);
|
||
j := j1;
|
||
end;
|
||
{
|
||
for j := 0 to sheet.Cols.Count-1 do begin
|
||
lCol := PCol(sheet.Cols[j]);
|
||
if lCol^.FormatIndex > 0 then
|
||
WriteColumnDefault(AStream, lCol);
|
||
end;
|
||
}
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 COLWIDTH record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteColWidth(AStream: TStream; ACol: PCol);
|
||
type
|
||
TColRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
StartCol: Byte;
|
||
EndCol: Byte;
|
||
ColWidth: Word;
|
||
end;
|
||
var
|
||
rec: TColRecord;
|
||
w: Single;
|
||
begin
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_COLWIDTH);
|
||
rec.RecordSize := WordToLE(SizeOf(TColRecord) - 4);
|
||
|
||
{ Start and end column }
|
||
rec.StartCol := ACol^.Col;
|
||
rec.EndCol := ACol^.Col;
|
||
|
||
{ Column width }
|
||
{ calculate width to be in units of 1/256 of pixel width of character "0" }
|
||
if ACol^.ColWidthType = cwtDefault then
|
||
w := TsWorksheet(FWorksheet).ReadDefaultColWidth(suChars)
|
||
else
|
||
w := tsWorkbook(FWorkbook).ConvertUnits(ACol^.Width, FWorkbook.Units, suChars);
|
||
rec.ColWidth := WordToLE(round(w*256));
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Write COLWIDTH records for all columns
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteColWidths(AStream: TStream);
|
||
var
|
||
j: Integer;
|
||
sheet: TsWorksheet;
|
||
col: PCol;
|
||
begin
|
||
sheet := TsWorkbook(FWorkbook).GetFirstWorksheet;
|
||
for j := 0 to sheet.Cols.Count-1 do begin
|
||
col := PCol(sheet.Cols[j]);
|
||
WriteColWidth(AStream, col);
|
||
end;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 DIMENSIONS record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteDimensions(AStream: TStream;
|
||
AWorksheet: TsBasicWorksheet);
|
||
var
|
||
firstRow, lastRow, firstCol, lastCol: Cardinal;
|
||
rec: TBIFF2_DimensionsRecord;
|
||
begin
|
||
{ Determine sheet size }
|
||
GetSheetDimensions(AWorksheet, firstRow, lastRow, firstCol, lastCol);
|
||
|
||
{ Populate BIFF record }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_DIMENSIONS);
|
||
rec.RecordSize := WordToLE(8);
|
||
rec.FirstRow := WordToLE(firstRow);
|
||
if lastRow < $FFFF then // avoid WORD overflow when adding 1
|
||
rec.LastRowPlus1 := WordToLE(lastRow+1)
|
||
else
|
||
rec.LastRowPlus1 := $FFFF;
|
||
rec.FirstCol := WordToLE(firstCol);
|
||
rec.LastColPlus1 := WordToLE(lastCol+1);
|
||
|
||
{ Write BIFF record to stream }
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{ ------------------------------------------------------------------------------
|
||
Writes an Excel 2 IXFE record
|
||
This record contains the "real" XF index if it is > 62.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteIXFE(AStream: TStream; XFIndex: Word);
|
||
begin
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_IXFE));
|
||
AStream.WriteWord(WordToLE(2));
|
||
AStream.WriteWord(WordToLE(XFIndex));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 file to a stream
|
||
|
||
Excel 2.x files support only one Worksheet per Workbook,
|
||
so only the first one will be written.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteToStream(AStream: TStream;
|
||
AParams: TsStreamParams = []);
|
||
var
|
||
pane: Byte;
|
||
begin
|
||
Unused(AParams);
|
||
|
||
FWorksheet := (FWorkbook as TsWorkbook).GetWorksheetByIndex(FSheetIndex);
|
||
if FWorksheet = nil then
|
||
raise EFPSpreadsheetWriter.Create(rsWorksheetNotFound1);
|
||
|
||
WriteBOF(AStream);
|
||
WriteCodePage(AStream, FCodePage);
|
||
WritePrintHeaders(AStream);
|
||
WritePrintGridLines(AStream);
|
||
WriteDefaultRowHeight(AStream, FWorksheet);
|
||
WriteHorizontalPageBreaks(AStream, FWorksheet);
|
||
WriteVerticalPageBreaks(AStream, FWorksheet);
|
||
WriteFonts(AStream);
|
||
|
||
// Page settings block
|
||
WriteHeaderFooter(AStream, true); // true = header
|
||
WriteHeaderFooter(AStream, false); // false = footer
|
||
WriteMargin(AStream, 0); // 0 = left margin
|
||
WriteMargin(AStream, 1); // 1 = right margin
|
||
WriteMargin(AStream, 2); // 2 = top margin
|
||
WriteMargin(AStream, 3); // 3 = bottom margin
|
||
|
||
WriteFormatCount(AStream);
|
||
WriteNumFormats(AStream);
|
||
|
||
if (bpLockStructure in Workbook.Protection) or FWorksheet.IsProtected then
|
||
WritePROTECT(AStream, true);
|
||
WriteWindowProtect(AStream, bpLockWindows in Workbook.Protection);
|
||
WriteObjectProtect(AStream, FWorksheet);
|
||
WritePASSWORD(AStream);
|
||
|
||
WriteXFRecords(AStream);
|
||
WriteDefaultColWidth(AStream, FWorksheet);
|
||
WriteColWidths(AStream);
|
||
WriteDimensions(AStream, FWorksheet);
|
||
WriteColumnDefaults(AStream);
|
||
WriteRows(AStream, FWorksheet);
|
||
|
||
if (boVirtualMode in Workbook.Options) then
|
||
WriteVirtualCells(AStream, FWorksheet)
|
||
else
|
||
WriteCellsToStream(AStream, TsWorksheet(FWorksheet).Cells);
|
||
|
||
WriteWindow1(AStream);
|
||
// { -- currently not working
|
||
WriteWindow2(AStream, FWorksheet);
|
||
WritePane(AStream, FWorksheet, false, pane); // false = "is not BIFF5 or BIFF8"
|
||
WriteSelections(AStream, FWorksheet);
|
||
//}
|
||
WriteEOF(AStream);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 WINDOW1 record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteWindow1(AStream: TStream);
|
||
begin
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW1));
|
||
AStream.WriteWord(WordToLE(9));
|
||
|
||
{ Horizontal position of the document window, in twips = 1 / 20 of a point }
|
||
AStream.WriteWord(WordToLE(0));
|
||
|
||
{ Vertical position of the document window, in twips = 1 / 20 of a point }
|
||
AStream.WriteWord(WordToLE($0069));
|
||
|
||
{ Width of the document window, in twips = 1 / 20 of a point }
|
||
AStream.WriteWord(WordToLE($339F));
|
||
|
||
{ Height of the document window, in twips = 1 / 20 of a point }
|
||
AStream.WriteWord(WordToLE($1B5D));
|
||
|
||
{ Window is visible (1) / hidden (0) }
|
||
AStream.WriteByte(WordToLE(0));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 WINDOW2 record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteWindow2(AStream: TStream;
|
||
ASheet: TsBasicWorksheet);
|
||
var
|
||
b: Byte;
|
||
sheet: TsWorksheet absolute ASheet;
|
||
begin
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_WINDOW2));
|
||
AStream.WriteWord(WordToLE(14));
|
||
|
||
{ Show formulas, not results }
|
||
AStream.WriteByte(0);
|
||
|
||
{ Show grid lines }
|
||
b := IfThen(soShowGridLines in sheet.Options, 1, 0);
|
||
AStream.WriteByte(b);
|
||
|
||
{ Show sheet headers }
|
||
b := IfThen(soShowHeaders in sheet.Options, 1, 0);
|
||
AStream.WriteByte(b);
|
||
|
||
{ Panes are frozen? }
|
||
b := 0;
|
||
if (soHasFrozenPanes in sheet.Options) and
|
||
((sheet.LeftPaneWidth > 0) or (sheet.TopPaneHeight > 0))
|
||
then
|
||
b := 1;
|
||
AStream.WriteByte(b);
|
||
|
||
{ Show zero values as zeros, not empty cells }
|
||
AStream.WriteByte(1);
|
||
|
||
{ Index to first visible row }
|
||
AStream.WriteWord(0);
|
||
|
||
{ Index to first visible column }
|
||
AStream.WriteWord(0);
|
||
|
||
{ Use automatic grid line color }
|
||
AStream.WriteByte(1);
|
||
|
||
{ RGB of manual grid line color }
|
||
AStream.WriteDWord(0);
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.WriteXF(AStream: TStream;
|
||
AFormatRecord: PsCellFormat; XFType_Prot: Byte = 0);
|
||
var
|
||
rec: TBIFF2_XFRecord;
|
||
b: Byte;
|
||
formatIdx, fontIdx: Integer;
|
||
fmtProt: byte;
|
||
begin
|
||
Unused(XFType_Prot);
|
||
GetFormatAndFontIndex(AFormatRecord, formatIdx, fontIdx);
|
||
|
||
{ BIFF Record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_XF);
|
||
rec.RecordSize := WordToLE(SizeOf(TBIFF2_XFRecord) - 2*SizeOf(word));
|
||
|
||
{ Index to FONT record }
|
||
rec.FontIndex := WordToLE(fontIdx);
|
||
|
||
{ Not used byte }
|
||
rec.NotUsed := 0;
|
||
|
||
{ Number format index and cell flags
|
||
Bit Mask Contents
|
||
----- ---- --------------------------------
|
||
5-0 $3F Index to (number) FORMAT record
|
||
6 $40 1 = Cell is locked
|
||
7 $80 1 = Formula is hidden }
|
||
fmtProt := formatIdx + MASK_XF_TYPE_PROT_LOCKED_BIFF2;
|
||
if AFormatRecord <> nil then
|
||
begin
|
||
if not (cpLockCell in AFormatRecord^.Protection) then
|
||
fmtProt := fmtProt and not MASK_XF_TYPE_PROT_LOCKED_BIFF2;
|
||
if (cpHideFormulas in AFormatRecord^.Protection) then
|
||
fmtProt := fmtProt or MASK_XF_TYPE_PROT_FORMULA_HIDDEN_BIFF2;
|
||
end;
|
||
rec.NumFormat_Prot := WordToLE(fmtProt);
|
||
|
||
{Horizontal alignment, border style, and background
|
||
Bit Mask Contents
|
||
--- ---- ------------------------------------------------
|
||
2-0 $07 XF_HOR_ALIGN – Horizontal alignment (0=General, 1=Left, 2=Centered, 3=Right)
|
||
3 $08 1 = Cell has left black border
|
||
4 $10 1 = Cell has right black border
|
||
5 $20 1 = Cell has top black border
|
||
6 $40 1 = Cell has bottom black border
|
||
7 $80 1 = Cell has shaded background }
|
||
b := 0;
|
||
if (AFormatRecord <> nil) then
|
||
begin
|
||
if (uffHorAlign in AFormatRecord^.UsedFormattingFields) then
|
||
b := b + byte(AFormatRecord^.HorAlignment);
|
||
if (uffBorder in AFormatRecord^.UsedFormattingFields) then
|
||
begin
|
||
if cbWest in AFormatRecord^.Border then b := b or $08;
|
||
if cbEast in AFormatRecord^.Border then b := b or $10;
|
||
if cbNorth in AFormatRecord^.Border then b := b or $20;
|
||
if cbSouth in AFormatRecord^.Border then b := b or $40;
|
||
end;
|
||
if (uffBackground in AFormatRecord^.UsedFormattingFields) then
|
||
b := b or $80;
|
||
end;
|
||
rec.HorAlign_Border_BkGr:= b;
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 BOF record
|
||
This must be the first record in an Excel 2 stream
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteBOF(AStream: TStream);
|
||
begin
|
||
{ BIFF Record header }
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_BOF, 4);
|
||
|
||
{ Unused }
|
||
AStream.WriteWord($0000);
|
||
|
||
{ Data type }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_SHEET));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 EOF record
|
||
This must be the last record in an Excel 2 stream
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteEOF(AStream: TStream);
|
||
begin
|
||
{ BIFF Record header }
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_EOF, 0);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 font record
|
||
The font data is passed as font index.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteFont(AStream: TStream; AFontIndex: Integer);
|
||
var
|
||
Len: Byte;
|
||
lFontName: AnsiString;
|
||
optn: Word;
|
||
font: TsFont;
|
||
begin
|
||
font := TsWorkbook(FWorkbook).GetFont(AFontIndex);
|
||
if font = nil then // this happens for FONT4 in case of BIFF
|
||
exit;
|
||
|
||
if font.FontName = '' then
|
||
raise EFPSpreadsheetWriter.Create('Font name not specified.');
|
||
if font.Size <= 0.0 then
|
||
raise EFPSpreadsheetWriter.Create('Font size not specified.');
|
||
|
||
lFontName := font.FontName;
|
||
Len := Length(lFontName);
|
||
|
||
{ BIFF Record header }
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_FONT, 4 + 1 + Len * SizeOf(AnsiChar));
|
||
|
||
{ Height of the font in twips = 1/20 of a point }
|
||
AStream.WriteWord(WordToLE(round(font.Size*20)));
|
||
|
||
{ Option flags }
|
||
optn := 0;
|
||
if fssBold in font.Style then optn := optn or $0001;
|
||
if fssItalic in font.Style then optn := optn or $0002;
|
||
if fssUnderline in font.Style then optn := optn or $0004;
|
||
if fssStrikeout in font.Style then optn := optn or $0008;
|
||
AStream.WriteWord(WordToLE(optn));
|
||
|
||
{ Font name: Unicodestring, char count in 1 byte }
|
||
AStream.WriteByte(Len);
|
||
AStream.WriteBuffer(lFontName[1], Len * Sizeof(AnsiChar));
|
||
|
||
{ Font color: goes into next record! }
|
||
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FONTCOLOR));
|
||
AStream.WriteWord(WordToLE(2));
|
||
|
||
{ Font color index, only first 8 palette entries allowed! }
|
||
AStream.WriteWord(WordToLE(PaletteIndex(font.Color)));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes all font records to the stream
|
||
@see WriteFont
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBiff2Writer.WriteFonts(AStream: TStream);
|
||
var
|
||
i: Integer;
|
||
begin
|
||
for i:=0 to TsWorkbook(FWorkbook).GetFontCount-1 do
|
||
WriteFont(AStream, i);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 FORMAT record which describes formatting of numerical data.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBiff2Writer.WriteFORMAT(AStream: TStream;
|
||
ANumFormatStr: String; AFormatIndex: Integer);
|
||
type
|
||
TNumFormatRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
FormatLen: Byte;
|
||
end;
|
||
var
|
||
len: Integer;
|
||
s: string; //ansistring;
|
||
rec: TNumFormatRecord;
|
||
buf: array of byte = nil;
|
||
begin
|
||
Unused(AFormatIndex);
|
||
|
||
{ Convert format string to code page used by the writer }
|
||
s := ConvertEncoding(ANumFormatStr, encodingUTF8, FCodePage);
|
||
len := Length(s);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_FORMAT);
|
||
rec.RecordSize := WordToLE(1 + len);
|
||
|
||
{ Length byte of format string }
|
||
rec.FormatLen := len;
|
||
|
||
{ Copy the format string characters into a buffer immediately after rec }
|
||
SetLength(buf, SizeOf(rec) + SizeOf(ansiChar)*len);
|
||
Move(rec, buf[0], SizeOf(rec));
|
||
Move(s[1], buf[SizeOf(rec)], len*SizeOf(ansiChar));
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(buf[0], SizeOf(rec) + SizeOf(ansiChar)*len);
|
||
|
||
{ Clean up }
|
||
SetLength(buf, 0);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes the number of FORMAT records contained in the file.
|
||
|
||
There are 21 built-in formats. The file may contain more, but Excel
|
||
expects a "21" here...
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteFORMATCOUNT(AStream: TStream);
|
||
begin
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_FORMATCOUNT, 2);
|
||
AStream.WriteWord(WordToLE(21));
|
||
// AStream.WriteWord(WordToLE(NumFormatList.Count));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 FORMULA record
|
||
The formula is an RPN formula that was converted from usual user-readable
|
||
string to an RPN array by the calling method.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteRPNFormula(AStream: TStream;
|
||
const ARow, ACol: Cardinal; AFormula: TsRPNFormula; ACell: PCell);
|
||
var
|
||
RPNLength: Word;
|
||
RecordSizePos, FinalPos: Cardinal;
|
||
xf: Word;
|
||
isSupported: Boolean;
|
||
unsupportedFormulas: String;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
{ Check if formula is supported by this file format. If not, write only
|
||
the result }
|
||
isSupported := FormulaSupported(AFormula, unsupportedFormulas);
|
||
if not IsSupported then
|
||
Workbook.AddErrorMsg(rsFormulaNotSupported, [
|
||
GetCellString(ARow, ACol), unsupportedformulas
|
||
]);
|
||
|
||
RPNLength := 0;
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF Record header }
|
||
AStream.WriteWord(WordToLE(INT_EXCEL_ID_FORMULA));
|
||
RecordSizePos := AStream.Position;
|
||
AStream.WriteWord(0); // We don't know the record size yet. It will be replaced at end.
|
||
|
||
{ Row and column }
|
||
AStream.WriteWord(WordToLE(ARow));
|
||
AStream.WriteWord(WordToLE(ACol));
|
||
|
||
{ BIFF2 Attributes }
|
||
WriteCellAttributes(AStream, ACell^.FormatIndex, xf);
|
||
|
||
{ Encoded result of RPN formula }
|
||
WriteRPNResult(AStream, ACell);
|
||
|
||
{ 0 = Do not recalculate
|
||
1 = Always recalculate }
|
||
AStream.WriteByte(1);
|
||
|
||
{ Formula data (RPN token array) }
|
||
WriteRPNTokenArray(AStream, ACell, AFormula, false, IsSupported, RPNLength);
|
||
|
||
{ Finally write sizes after we know them }
|
||
FinalPos := AStream.Position;
|
||
AStream.Position := RecordSizePos;
|
||
AStream.WriteWord(WordToLE(17 + RPNLength));
|
||
AStream.Position := FinalPos;
|
||
|
||
{ Write following STRING record if formula result is a non-empty string }
|
||
if (ACell^.ContentType = cctUTF8String) and (ACell^.UTF8StringValue <> '') then
|
||
WriteSTRINGRecord(AStream, ACell^.UTF8StringValue);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes the identifier for an RPN function with fixed argument count and
|
||
returns the number of bytes written.
|
||
-------------------------------------------------------------------------------}
|
||
function TsSpreadBIFF2Writer.WriteRPNFunc(AStream: TStream;
|
||
AIdentifier: Word): Word;
|
||
begin
|
||
AStream.WriteByte(Lo(AIdentifier));
|
||
Result := 1;
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes the size of the RPN token array. Called from WriteRPNFormula.
|
||
Overrides xlscommon.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteRPNTokenArraySize(AStream: TStream;
|
||
ASize: Word);
|
||
begin
|
||
AStream.WriteByte(ASize);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 STRING record which immediately follows a FORMULA record
|
||
when the formula result is a string.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteStringRecord(AStream: TStream;
|
||
AString: String);
|
||
var
|
||
s: ansistring;
|
||
len: Integer;
|
||
begin
|
||
s := ConvertEncoding(AString, encodingUTF8, FCodePage);
|
||
len := Length(s);
|
||
|
||
{ BIFF Record header }
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_STRING, 1 + len*SizeOf(ansichar));
|
||
|
||
{ Write string length }
|
||
AStream.WriteByte(len);
|
||
{ Write characters }
|
||
AStream.WriteBuffer(s[1], len * SizeOf(ansichar));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes a Excel 2 BOOLEAN cell record.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteBool(AStream: TStream;
|
||
const ARow, ACol: Cardinal; const AValue: Boolean; ACell: PCell);
|
||
var
|
||
rec: TBIFF2_BoolErrRecord;
|
||
xf: Integer;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_BOOLERROR);
|
||
rec.RecordSize := WordToLE(9);
|
||
|
||
{ Row and column index }
|
||
rec.Row := WordToLE(ARow);
|
||
rec.Col := WordToLE(ACol);
|
||
|
||
{ BIFF2 attributes }
|
||
GetAttributes(ACell^.FormatIndex, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
||
|
||
{ Cell value }
|
||
rec.BoolErrValue := ord(AValue);
|
||
rec.ValueType := 0; // 0 = boolean value, 1 = error value
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 DEFAULTROWHEIGHT record
|
||
Specifies the default height and default flags for rows that do not have a
|
||
corresponding ROW record
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteDefaultRowHeight(AStream: TStream;
|
||
AWorksheet: TsBasicWorksheet);
|
||
var
|
||
h: Single;
|
||
begin
|
||
{ BIFF record header }
|
||
WriteBIFFHeader(AStream, INT_EXCEL_ID_DEFROWHEIGHT, 2);
|
||
|
||
{ Default height for unused rows, in twips = 1/20 of a point
|
||
Bits 0-14: Default height for unused rows, in twips
|
||
Bit 15 = 1: Row height not changed manually }
|
||
h := TsWorksheet(AWorksheet).ReadDefaultRowHeight(suPoints); // h is in points
|
||
AStream.WriteWord(WordToLE(PtsToTwips(h))); // write as twips
|
||
end;
|
||
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 ERROR cell record.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteError(AStream: TStream;
|
||
const ARow, ACol: Cardinal; const AValue: TsErrorValue; ACell: PCell);
|
||
var
|
||
rec: TBIFF2_BoolErrRecord;
|
||
xf: Integer;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_BOOLERROR);
|
||
rec.RecordSize := WordToLE(9);
|
||
|
||
{ Row and column index }
|
||
rec.Row := WordToLE(ARow);
|
||
rec.Col := WordToLE(ACol);
|
||
|
||
{ BIFF2 attributes }
|
||
GetAttributes(ACell^.FormatIndex, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
||
|
||
{ Cell value }
|
||
rec.BoolErrValue := ConvertToExcelError(AValue);
|
||
rec.ValueType := 1; // 0 = boolean value, 1 = error value
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 record for an empty cell
|
||
Required if this cell should contain formatting, but no data.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteBlank(AStream: TStream;
|
||
const ARow, ACol: Cardinal; ACell: PCell);
|
||
type
|
||
TBlankRecord = packed record
|
||
RecordID: Word;
|
||
RecordSize: Word;
|
||
Row: Word;
|
||
Col: Word;
|
||
Attrib1, Attrib2, Attrib3: Byte;
|
||
end;
|
||
var
|
||
xf: Word;
|
||
rec: TBlankRecord;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_BLANK);
|
||
rec.RecordSize := WordToLE(7);
|
||
|
||
{ BIFF record data }
|
||
rec.Row := WordToLE(ARow);
|
||
rec.Col := WordToLE(ACol);
|
||
|
||
{ BIFF2 attributes }
|
||
GetAttributes(ACell^.FormatIndex, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, Sizeof(rec));
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 LABEL record
|
||
If the string length exceeds 255 bytes, the string will be truncated and an
|
||
error message will be logged.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteLabel(AStream: TStream; const ARow,
|
||
ACol: Cardinal; const AValue: string; ACell: PCell);
|
||
const
|
||
MAXBYTES = 255; //limit for this format
|
||
var
|
||
L: Byte;
|
||
AnsiText: ansistring;
|
||
rec: TBIFF2_LabelRecord;
|
||
buf: array of byte = nil;
|
||
var
|
||
xf: Word;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
if AValue = '' then Exit; // Writing an empty text doesn't work
|
||
|
||
AnsiText := UTF8ToISO_8859_1(FixLineEnding(AValue));
|
||
|
||
if Length(AnsiText) > MAXBYTES then begin
|
||
// BIFF 5 does not support labels/text bigger than 255 chars,
|
||
// so BIFF2 won't either
|
||
// Rather than lose data when reading it, let the application programmer deal
|
||
// with the problem or purposefully ignore it.
|
||
AnsiText := Copy(AnsiText, 1, MAXBYTES);
|
||
Workbook.AddErrorMsg(rsTruncateTooLongCellText, [
|
||
MAXBYTES, GetCellString(ARow, ACol)
|
||
]);
|
||
end;
|
||
L := Length(AnsiText);
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_LABEL);
|
||
rec.RecordSize := WordToLE(8 + L);
|
||
|
||
{ BIFF record data }
|
||
rec.Row := WordToLE(ARow);
|
||
rec.Col := WordToLE(ACol);
|
||
|
||
{ BIFF2 attributes }
|
||
GetAttributes(ACell^.FormatIndex, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
||
|
||
{ Text length: 8 bit }
|
||
rec.TextLen := L;
|
||
|
||
{ Copy the text characters into a buffer immediately after rec }
|
||
SetLength(buf, SizeOf(rec) + SizeOf(ansiChar)*L);
|
||
Move(rec, buf[0], SizeOf(rec));
|
||
Move(AnsiText[1], buf[SizeOf(rec)], L*SizeOf(ansiChar));
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(buf[0], SizeOf(Rec) + SizeOf(ansiChar)*L);
|
||
end;
|
||
|
||
{@@ ----------------------------------------------------------------------------
|
||
Writes an Excel 2 NUMBER record
|
||
A "number" is a 64-bit IEE 754 floating point.
|
||
-------------------------------------------------------------------------------}
|
||
procedure TsSpreadBIFF2Writer.WriteNumber(AStream: TStream; const ARow,
|
||
ACol: Cardinal; const AValue: double; ACell: PCell);
|
||
var
|
||
xf: Word;
|
||
rec: TBIFF2_NumberRecord;
|
||
begin
|
||
if (ARow >= FLimitations.MaxRowCount) or (ACol >= FLimitations.MaxColCount) then
|
||
exit;
|
||
|
||
xf := FindXFIndex(ACell^.FormatIndex);
|
||
if xf >= 63 then
|
||
WriteIXFE(AStream, xf);
|
||
|
||
{ BIFF record header }
|
||
rec.RecordID := WordToLE(INT_EXCEL_ID_NUMBER);
|
||
rec.RecordSize := WordToLE(15);
|
||
|
||
{ BIFF record data }
|
||
rec.Row := WordToLE(ARow);
|
||
rec.Col := WordToLE(ACol);
|
||
|
||
{ BIFF2 attributes }
|
||
GetAttributes(ACell^.FormatIndex, xf, rec.Attrib1, rec.Attrib2, rec.Attrib3);
|
||
|
||
{ Number value }
|
||
rec.Value := AValue;
|
||
|
||
{ Write out }
|
||
AStream.WriteBuffer(rec, SizeOf(Rec));
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.WritePassword(AStream: TStream);
|
||
var
|
||
hash: Word;
|
||
hb, hs: LongInt;
|
||
book: TsWorkbook;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
book := FWorkbook as TsWorkbook;
|
||
sheet := FWorksheet as TsWorksheet;
|
||
|
||
hb := 0;
|
||
if (book.CryptoInfo.PasswordHash <> '') and
|
||
not TryStrToInt('$' + book.CryptoInfo.PasswordHash, hb) then
|
||
begin
|
||
book.AddErrorMsg(rsPasswordRemoved_NotValid);
|
||
exit;
|
||
end;
|
||
|
||
hs := 0;
|
||
if (sheet.CryptoInfo.PasswordHash <> '') and
|
||
not TryStrToInt('$' + sheet.CryptoInfo.PasswordHash, hs) then
|
||
begin
|
||
book.AddErrorMsg(rsPasswordRemoved_NotValid);
|
||
exit;
|
||
end;
|
||
|
||
// Neither workbook nor worksheet password set
|
||
if (hb = 0) and (hs = 0) then
|
||
exit;
|
||
|
||
// Only workbook password set. Check for Excel algorithm.
|
||
if (hb <> 0) and (hs = 0) then begin
|
||
if book.CryptoInfo.Algorithm <> caExcel then begin
|
||
book.AddErrorMsg(rsPasswordRemoved_Excel);
|
||
exit;
|
||
end;
|
||
hash := hb;
|
||
end else
|
||
// Only worksheet password set, check for Excel algorithm
|
||
if (hs <> 0) and (hb = 0) then begin
|
||
if sheet.CryptoInfo.Algorithm <> caExcel then begin
|
||
book.AddErrorMsg(rsPasswordRemoved_Excel);
|
||
exit;
|
||
end;
|
||
hash := hs;
|
||
end else
|
||
if (hs <> hb) then begin
|
||
book.AddErrorMsg(rsPasswordRemoved_BIFF2);
|
||
exit;
|
||
end else
|
||
if (book.CryptoInfo.Algorithm <> caExcel) or
|
||
(sheet.CryptoInfo.Algorithm <> caExcel) then
|
||
begin
|
||
book.AddErrorMsg(rsPasswordRemoved_Excel);
|
||
exit;
|
||
end else
|
||
hash := hs; // or hb -- they are equal here.
|
||
|
||
// Write out record
|
||
WriteBIFFHeader(AStream, INT_EXCEL_ID_PASSWORD, 2);
|
||
AStream.WriteWord(WordToLE(hash));
|
||
end;
|
||
|
||
procedure TsSpreadBIFF2Writer.WriteRow(AStream: TStream; ASheet: TsBasicWorksheet;
|
||
ARowIndex, AFirstColIndex, ALastColIndex: Cardinal; ARow: PRow);
|
||
var
|
||
containsXF: Boolean;
|
||
rowheight: Word;
|
||
auto: Boolean;
|
||
w: Word;
|
||
xf: Word;
|
||
book: TsWorkbook;
|
||
sheet: TsWorksheet;
|
||
begin
|
||
if (ARowIndex >= FLimitations.MaxRowCount) or
|
||
(AFirstColIndex >= FLimitations.MaxColCount) or
|
||
(ALastColIndex >= FLimitations.MaxColCount)
|
||
then
|
||
exit;
|
||
|
||
book := FWorkbook as TsWorkbook;
|
||
sheet := ASheet as TsWorksheet;
|
||
|
||
containsXF := (ARow <> nil) and (ARow^.FormatIndex > 0);
|
||
|
||
{ BIFF record header }
|
||
WriteBiffHeader(AStream, INT_EXCEL_ID_ROW, IfThen(containsXF, 18, 13));
|
||
|
||
{ Index of row }
|
||
AStream.WriteWord(WordToLE(Word(ARowIndex)));
|
||
|
||
{ Index to column of the first cell which is described by a cell record }
|
||
AStream.WriteWord(WordToLE(Word(AFirstColIndex)));
|
||
|
||
{ Index to column of the last cell which is described by a cell record, increased by 1 }
|
||
AStream.WriteWord(WordToLE(Word(ALastColIndex) + 1));
|
||
|
||
auto := true;
|
||
{ Row height (in twips, 1/20 point) and info on custom row height }
|
||
if (ARow = nil) or (ARow^.RowHeightType = rhtDefault) then
|
||
rowheight := PtsToTwips(sheet.ReadDefaultRowHeight(suPoints))
|
||
else
|
||
if (ARow^.Height = 0) then
|
||
rowheight := 0
|
||
else begin
|
||
rowheight := PtsToTwips(book.ConvertUnits(ARow^.Height, book.Units, suPoints));
|
||
auto := ARow^.RowHeightType <> rhtCustom;
|
||
end;
|
||
w := rowheight and $7FFF;
|
||
if auto then
|
||
w := w or $8000;
|
||
AStream.WriteWord(WordToLE(w));
|
||
|
||
{ not used }
|
||
AStream.WriteWord(0);
|
||
|
||
{ Does the record contain row attribute field and XF index? }
|
||
AStream.WriteByte(ord(containsXF));
|
||
|
||
{ Relative offset to calculate stream position of the first cell record for this row }
|
||
AStream.WriteWord(0);
|
||
|
||
if containsXF then begin
|
||
xf := FindXFIndex(ARow^.FormatIndex);
|
||
|
||
{ Default row attributes }
|
||
WriteCellAttributes(AStream, ARow^.FormatIndex, xf);
|
||
|
||
{ Index to XF record }
|
||
AStream.WriteWord(WordToLE(xf));
|
||
end;
|
||
end;
|
||
|
||
{*******************************************************************
|
||
* Initialization section
|
||
*
|
||
* Registers this reader / writer to fpspreadsheet
|
||
* Converts the palette to litte-endian
|
||
*
|
||
*******************************************************************}
|
||
|
||
initialization
|
||
|
||
{$IFDEF MSWINDOWS}
|
||
Excel2Settings.CodePage := GetDefaultTextEncoding;
|
||
{$ENDIF}
|
||
|
||
sfidExcel2 := RegisterSpreadFormat(sfExcel2,
|
||
TsSpreadBIFF2Reader, TsSpreadBIFF2Writer,
|
||
STR_FILEFORMAT_EXCEL_2, 'BIFF2', [STR_EXCEL_EXTENSION]
|
||
);
|
||
|
||
MakeLEPalette(PALETTE_BIFF2);
|
||
|
||
end.
|