2023-03-23 23:57:37 +00:00
|
|
|
unit xlsbiff34;
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
|
|
|
|
|
|
interface
|
|
|
|
|
|
|
|
uses
|
|
|
|
Classes, SysUtils,
|
|
|
|
fpsTypes, fpsUtils, xlsCommon;
|
|
|
|
|
|
|
|
type
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
{ TsSpreadBIFF34Reader }
|
2023-03-22 23:08:00 +00:00
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
TsSpreadBIFF34Reader = class(TsSpreadBIFFReader)
|
|
|
|
private
|
|
|
|
type TBIFFFormat = (BIFF3, BIFF4);
|
|
|
|
var FFormat: TBIFFFormat;
|
2023-03-22 23:08:00 +00:00
|
|
|
protected
|
|
|
|
procedure AddBuiltInNumFormats; override;
|
|
|
|
procedure PopulatePalette; override;
|
|
|
|
procedure ReadDEFINEDNAME(AStream: TStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure ReadDIMENSION(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
procedure ReadFONT(const AStream: TStream);
|
|
|
|
procedure ReadFORMAT(AStream: TStream); override;
|
|
|
|
procedure ReadFORMULA(AStream: TStream); override;
|
|
|
|
procedure ReadLABEL(AStream: TStream); override;
|
|
|
|
procedure ReadStandardWidth(AStream: TStream; ASheet: TsBasicWorksheet);
|
|
|
|
procedure ReadStringRecord(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;
|
2023-03-23 23:57:37 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
TsSpreadBIFF3Reader = class(TsSpreadBIFF34Reader)
|
|
|
|
protected
|
|
|
|
function ReadRPNFunc(AStream: TStream): Word; override;
|
|
|
|
public
|
|
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
|
|
|
{ File format detection }
|
|
|
|
class function CheckfileFormat(AStream: TStream): Boolean; override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
TsSpreadBIFF4Reader = class(TsSpreadBIFF34Reader)
|
|
|
|
public
|
|
|
|
constructor Create(AWorkbook: TsBasicWorkbook); override;
|
2023-03-22 23:08:00 +00:00
|
|
|
{ File format detection }
|
|
|
|
class function CheckfileFormat(AStream: TStream): Boolean; override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
var
|
2023-03-23 23:57:37 +00:00
|
|
|
sfidExcel3: TsSpreadFormatID;
|
2023-03-22 23:08:00 +00:00
|
|
|
sfidExcel4: TsSpreadFormatID;
|
|
|
|
|
|
|
|
const
|
2023-03-23 23:57:37 +00:00
|
|
|
{@@ palette of the default BIFF3/BIFF4 colors as "big-endian color" values }
|
|
|
|
PALETTE_BIFF34: array[$00..$17] of TsColor = (
|
2023-03-22 23:08:00 +00:00
|
|
|
$000000, // $00: black
|
|
|
|
$FFFFFF, // $01: white
|
|
|
|
$FF0000, // $02: red
|
|
|
|
$00FF00, // $03: green
|
|
|
|
$0000FF, // $04: blue
|
|
|
|
$FFFF00, // $05: yellow
|
|
|
|
$FF00FF, // $06: magenta
|
|
|
|
$00FFFF, // $07: cyan
|
|
|
|
|
|
|
|
$000000, // $08: EGA black
|
|
|
|
$FFFFFF, // $09: EGA white
|
|
|
|
$FF0000, // $0A: EGA red
|
|
|
|
$00FF00, // $0B: EGA green
|
|
|
|
$0000FF, // $0C: EGA blue
|
|
|
|
$FFFF00, // $0D: EGA yellow
|
|
|
|
$FF00FF, // $0E: EGA magenta
|
|
|
|
$00FFFF, // $0F: EGA cyan
|
|
|
|
|
|
|
|
$800000, // $10: EGA dark red
|
|
|
|
$008000, // $11: EGA dark green
|
|
|
|
$000080, // $12: EGA dark blue
|
|
|
|
$808000, // $13: EGA olive
|
|
|
|
$800080, // $14: EGA purple
|
|
|
|
$008080, // $15: EGA teal
|
|
|
|
$C0C0C0, // $16: EGA silver
|
|
|
|
$808080 // $17: EGA gray
|
|
|
|
);
|
|
|
|
|
|
|
|
implementation
|
|
|
|
|
|
|
|
uses
|
|
|
|
LConvEncoding, Math,
|
|
|
|
fpSpreadsheet, fpsStrings, fpsReaderWriter, fpsPalette, fpsNumFormat;
|
|
|
|
|
|
|
|
const
|
2023-03-23 23:57:37 +00:00
|
|
|
BIFF34_MAX_PALETTE_SIZE = 8 + 16;
|
2023-03-22 23:08:00 +00:00
|
|
|
SYS_DEFAULT_FOREGROUND_COLOR = $18;
|
|
|
|
SYS_DEFAULT_BACKGROUND_COLOR = $19;
|
|
|
|
|
|
|
|
{ Excel record IDs }
|
|
|
|
INT_EXCEL_ID_BLANK = $0201;
|
|
|
|
INT_EXCEL_ID_NUMBER = $0203;
|
|
|
|
INT_EXCEL_ID_LABEL = $0204;
|
|
|
|
INT_EXCEL_ID_BOOLERROR = $0205;
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_BOF_3 = $0209;
|
|
|
|
INT_EXCEL_ID_BOF_4 = $0409;
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_FONT = $0231;
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_FORMULA_3 = $0206;
|
|
|
|
INT_EXCEL_ID_FORMULA_4 = $0406;
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_STANDARDWIDTH = $0099;
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_XF_3 = $0243;
|
|
|
|
INT_EXCEL_ID_XF_4 = $0443;
|
|
|
|
INT_EXCEL_ID_FORMAT_3 = $001E;
|
|
|
|
INT_EXCEL_ID_FORMAT_4 = $041E;
|
|
|
|
INT_EXCEL_ID_DIMENSION = $0200;
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
// XF Text orientation
|
|
|
|
MASK_XF_ORIENTATION = $C0;
|
|
|
|
XF_ROTATION_HORIZONTAL = 0;
|
|
|
|
XF_ROTATION_STACKED = 1;
|
|
|
|
XF_ROTATION_90DEG_CCW = 2;
|
|
|
|
XF_ROTATION_90DEG_CW = 3;
|
|
|
|
|
|
|
|
// XF cell background
|
|
|
|
MASK_XF_BKGR_FILLPATTERN = $003F;
|
|
|
|
MASK_XF_BKGR_PATTERN_COLOR = $07C0;
|
|
|
|
MASK_XF_BKGR_BACKGROUND_COLOR = $F800;
|
|
|
|
|
|
|
|
// XF cell border
|
|
|
|
MASK_XF_BORDER_TOP_STYLE = $00000007;
|
|
|
|
MASK_XF_BORDER_TOP_COLOR = $000000F8; // shr 3
|
|
|
|
MASK_XF_BORDER_LEFT_STYLE = $00000700; // shr 8
|
|
|
|
MASK_XF_BORDER_LEFT_COLOR = $0000F800; // shr 11
|
|
|
|
MASK_XF_BORDER_BOTTOM_STYLE = $00070000; // shr 16
|
|
|
|
MASK_XF_BORDER_BOTTOM_COLOR = $00F80000; // shr 19
|
|
|
|
MASK_XF_BORDER_RIGHT_STYLE = $07000000; // shr 24
|
|
|
|
MASK_XF_BORDER_RIGHT_COLOR = $F8000000; // shr 27
|
|
|
|
|
|
|
|
type
|
2023-03-23 23:57:37 +00:00
|
|
|
TBIFF34_LabelRecord = packed record
|
2023-03-22 23:08:00 +00:00
|
|
|
Row: Word;
|
|
|
|
Col: Word;
|
|
|
|
XFIndex: Word;
|
|
|
|
TextLen: Word;
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
TBIFF34_XFRecord = packed record
|
2023-03-22 23:08:00 +00:00
|
|
|
FontIndex: byte;
|
|
|
|
NumFormatIndex: byte;
|
2023-03-23 23:57:37 +00:00
|
|
|
case integer of
|
|
|
|
3: (XFType_Prot_3: byte;
|
|
|
|
UsedAttribs_3: byte;
|
|
|
|
Align_TextBreak_ParentXF_3: Word;
|
|
|
|
BackGround_3: Word;
|
|
|
|
Border_3: DWord);
|
|
|
|
4: (XFType_Prot_ParentXF_4: Word;
|
|
|
|
Align_TextBreak_Orientation_4: Byte;
|
|
|
|
UsedAttribs_4: byte;
|
|
|
|
BackGround_4: Word;
|
|
|
|
Border_4: DWord);
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure InitBiff34Limitations(out ALimitations: TsSpreadsheetFormatLimitations);
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
|
|
|
InitBiffLimitations(ALimitations);
|
2023-03-23 23:57:37 +00:00
|
|
|
ALimitations.MaxPaletteSize := BIFF34_MAX_PALETTE_SIZE;
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{ ------------------------------------------------------------------------------
|
2023-03-23 23:57:37 +00:00
|
|
|
TsSpreadBIFF34Reader
|
2023-03-22 23:08:00 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
2023-03-23 23:57:37 +00:00
|
|
|
constructor TsSpreadBIFF34Reader.Create(AWorkbook: TsBasicWorkbook);
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
|
|
|
inherited Create(AWorkbook);
|
2023-03-23 23:57:37 +00:00
|
|
|
InitBiff34Limitations(FLimitations);
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.AddBuiltInNumFormats;
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
|
|
|
FFirstNumFormatIndexInFile := 0;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
2023-03-23 23:57:37 +00:00
|
|
|
Populates the reader's default palette using the BIFF3/BIFF4 default colors.
|
2023-03-22 23:08:00 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.PopulatePalette;
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
|
|
|
FPalette.Clear;
|
2023-03-23 23:57:37 +00:00
|
|
|
FPalette.UseColors(PALETTE_BIFF34);
|
|
|
|
// The palette has been defined in big-endian but had been converted to
|
|
|
|
// little-endian in the initialization section.
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Reads a DEFINEDNAME record. Currently only extracts print ranges and titles.
|
|
|
|
-------------------------------------------------------------------------------}
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadDEFINEDNAME(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
{
|
|
|
|
var
|
|
|
|
options: Word;
|
|
|
|
len: byte;
|
|
|
|
formulaSize: Word;
|
|
|
|
ansistr: ansiString = '';
|
|
|
|
defName: String;
|
|
|
|
rpnformula: TsRPNFormula;
|
|
|
|
{%H-}extsheetIndex: Integer;
|
|
|
|
sheetIndex: Integer;
|
|
|
|
}
|
|
|
|
begin
|
|
|
|
(* FIX ME -- this is the code of BIFF5. Must be adapted.
|
|
|
|
|
|
|
|
// Options
|
|
|
|
options := WordLEToN(AStream.ReadWord);
|
|
|
|
if options and $0020 = 0 then // only support built-in names at the moment!
|
|
|
|
exit;
|
|
|
|
|
|
|
|
// Keyboard shortcut --> ignore
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
|
|
|
// Length of name (character count)
|
|
|
|
len := AStream.ReadByte;
|
|
|
|
|
|
|
|
// Size of formula data
|
|
|
|
formulasize := WordLEToN(AStream.ReadWord);
|
|
|
|
|
|
|
|
// EXTERNSHEET index (1-base), or 0 if global name
|
|
|
|
extsheetIndex := SmallInt(WordLEToN(AStream.ReadWord)) - 1; // now 0-based!
|
|
|
|
|
|
|
|
// Sheet index (1-based) on which the name is valid (0 = global)
|
|
|
|
sheetIndex := SmallInt(WordLEToN(AStream.ReadWord)) - 1; // now 0-based!
|
|
|
|
|
|
|
|
// Length of Menu text (ignore)
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
|
|
|
// Length of description text(ignore)
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
|
|
|
// Length of help topic text (ignore)
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
|
|
|
// Length of status bar text (ignore)
|
|
|
|
AStream.ReadByte;
|
|
|
|
|
|
|
|
// Name
|
|
|
|
SetLength(ansistr, len);
|
|
|
|
AStream.ReadBuffer(ansistr[1], len);
|
|
|
|
defName := ConvertEncoding(ansistr, FCodepage, encodingUTF8);
|
|
|
|
|
|
|
|
// Formula
|
|
|
|
if not ReadRPNTokenArray(AStream, formulaSize, rpnFormula) then
|
|
|
|
exit;
|
|
|
|
// Store defined name in internal list
|
|
|
|
FDefinedNames.Add(TsBIFFDefinedName.Create(defName, rpnFormula, sheetIndex));
|
|
|
|
|
|
|
|
// Skip rest...
|
|
|
|
*)
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadDIMENSION(AStream: TStream);
|
|
|
|
begin
|
|
|
|
{ ATM, we do not need the data found in this record. - This code should go here... }
|
|
|
|
|
|
|
|
{ BUT: We had stored palette indices in the colors of the XF records.
|
|
|
|
When, in the following records, cells are read we need the true rgb colors
|
|
|
|
because fps does not support paletted colors. This is prepared by
|
|
|
|
calling FixColors.
|
|
|
|
The ReadDIMENSION record is chosen here it is mantatory and called after
|
|
|
|
reading fonts, XF and palette, and before reading cells.
|
|
|
|
}
|
|
|
|
FixColors;
|
|
|
|
end;
|
|
|
|
|
|
|
|
procedure TsSpreadBIFF34Reader.ReadFont(const AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
|
|
|
{%H-}lCodePage: Word;
|
|
|
|
lHeight: Word;
|
|
|
|
lOptions: Word;
|
|
|
|
lColor: Word;
|
|
|
|
lWeight: Word;
|
|
|
|
lEsc: Word;
|
|
|
|
Len: Byte;
|
|
|
|
fontname: ansistring = '';
|
|
|
|
font: TsFont;
|
|
|
|
isDefaultFont: Boolean;
|
|
|
|
begin
|
|
|
|
font := TsFont.Create;
|
|
|
|
|
|
|
|
{ Height of the font in twips = 1/20 of a point }
|
|
|
|
lHeight := WordLEToN(AStream.ReadWord); // WordToLE(200)
|
|
|
|
font.Size := lHeight/20;
|
|
|
|
|
|
|
|
{ Option flags }
|
|
|
|
lOptions := WordLEToN(AStream.ReadWord);
|
|
|
|
font.Style := [];
|
|
|
|
if lOptions and $0001 <> 0 then Include(font.Style, fssBold);
|
|
|
|
if lOptions and $0002 <> 0 then Include(font.Style, fssItalic);
|
|
|
|
if lOptions and $0004 <> 0 then Include(font.Style, fssUnderline);
|
|
|
|
if lOptions and $0008 <> 0 then Include(font.Style, fssStrikeout);
|
|
|
|
|
|
|
|
{ Color index }
|
|
|
|
// The problem is that the palette is loaded after the font list; therefore
|
|
|
|
// we do not know the rgb color of the font here. We store the palette index
|
|
|
|
// ("SetAsPaletteIndex") and replace it by the rgb color after reading of the
|
|
|
|
// palette and after reading the workbook globals records. As an indicator
|
|
|
|
// that the font does not yet contain an rgb color a control bit is set in
|
|
|
|
// the high-byte of the TsColor.
|
|
|
|
lColor := WordLEToN(AStream.ReadWord);
|
|
|
|
if lColor < 8 then
|
|
|
|
// Use built-in colors directly otherwise the Workbook's FindFont would not find the font in ReadXF
|
|
|
|
font.Color := FPalette[lColor]
|
|
|
|
else
|
|
|
|
if lColor = SYS_DEFAULT_WINDOW_TEXT_COLOR then
|
|
|
|
font.Color := scBlack
|
|
|
|
else
|
|
|
|
font.Color := SetAsPaletteIndex(lColor);
|
|
|
|
|
|
|
|
{ Font name: Ansistring, char count in 1 byte }
|
|
|
|
Len := AStream.ReadByte();
|
|
|
|
SetLength(fontname, Len);
|
|
|
|
AStream.ReadBuffer(fontname[1], Len);
|
|
|
|
font.FontName := ConvertEncoding(fontname, FCodePage, encodingUTF8);
|
|
|
|
|
|
|
|
isDefaultFont := FFontList.Count = 0;
|
|
|
|
|
|
|
|
{ Add font to internal font list. Will be copied to workbook's font list later
|
|
|
|
as the font index in the internal list may be different from the index in
|
|
|
|
the workbook's list. }
|
|
|
|
FFontList.Add(font);
|
|
|
|
|
|
|
|
{ Excel does not have zero-based font #4! }
|
|
|
|
if FFontList.Count = 4 then FFontList.Add(nil);
|
|
|
|
|
|
|
|
if isDefaultFont then
|
|
|
|
(FWorkbook as TsWorkbook).SetDefaultFont(font.FontName, font.Size);
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
{@@ Reads the FORMAT record for formatting numerical data.
|
|
|
|
|
|
|
|
Record FORMAT (Section 5.49 in the OpenOffice pdf):
|
|
|
|
BIFF3
|
|
|
|
Offset Size Contents
|
|
|
|
0 var. Number format string (byte string, 8-bit string length
|
|
|
|
|
|
|
|
BIFF4
|
|
|
|
Offset Size Contents
|
|
|
|
0 2 BIFF4 not used
|
|
|
|
2 var Number format string (byte string, 8-bit string length) }
|
|
|
|
procedure TsSpreadBIFF34Reader.ReadFormat(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
|
|
|
len: byte;
|
|
|
|
fmtString: AnsiString = '';
|
|
|
|
nfs: String;
|
|
|
|
begin
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
{ BIFF 4 only }
|
|
|
|
if FFormat = BIFF4 then
|
|
|
|
begin
|
|
|
|
// not used
|
|
|
|
AStream.ReadWord;
|
|
|
|
end;
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Reads a FORMULA record, retrieves the RPN formula and puts the result in the
|
|
|
|
corresponding field. The formula is not recalculated here!
|
2023-03-23 23:57:37 +00:00
|
|
|
Valid for BIFF3 and BIFF4.
|
2023-03-22 23:08:00 +00:00
|
|
|
-------------------------------------------------------------------------------}
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadFormula(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: WORD;
|
|
|
|
ResultFormula: Double = 0.0;
|
|
|
|
Data: array [0..7] of byte;
|
|
|
|
dt: TDateTime;
|
|
|
|
nf: TsNumberFormat;
|
|
|
|
nfs: String;
|
|
|
|
err: TsErrorValue;
|
|
|
|
ok: Boolean;
|
|
|
|
cell: PCell;
|
|
|
|
sheet: TsWorksheet;
|
|
|
|
msg: String;
|
|
|
|
begin
|
|
|
|
sheet := TsWorksheet(FWorksheet);
|
|
|
|
|
|
|
|
{ Index to XF Record }
|
|
|
|
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));
|
|
|
|
|
|
|
|
{ Options flags }
|
|
|
|
WordLEtoN(AStream.ReadWord);
|
|
|
|
|
|
|
|
{ Create cell }
|
|
|
|
if FIsVirtualMode then // "Virtual" cell
|
|
|
|
begin
|
|
|
|
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := sheet.GetCell(ARow, ACol); // "Real" cell
|
|
|
|
// Don't call "AddCell" because, if the cell belongs to a shared formula, it
|
|
|
|
// already has been created before, and then would exist in the tree twice.
|
|
|
|
|
|
|
|
// Prevent shared formulas (which already may have been written at this time)
|
|
|
|
// being erased when cell content is written
|
|
|
|
TsWorkbook(sheet.Workbook).LockFormulas;
|
|
|
|
try
|
|
|
|
// 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
|
|
|
|
err := ConvertFromExcelError(Data[2]);
|
|
|
|
sheet.WriteErrorValue(cell, err);
|
|
|
|
end;
|
|
|
|
|
|
|
|
3: sheet.WriteBlank(cell);
|
|
|
|
end
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
// Result is a number or a date/time
|
|
|
|
Move(Data[0], ResultFormula, SizeOf(Data));
|
|
|
|
|
|
|
|
{Find out what cell type, set content type and value}
|
|
|
|
ExtractNumberFormat(XF, nf, nfs);
|
|
|
|
if IsDateTime(ResultFormula, nf, nfs, dt) then
|
|
|
|
sheet.WriteDateTime(cell, dt) //, nf, nfs)
|
|
|
|
else
|
|
|
|
sheet.WriteNumber(cell, ResultFormula);
|
|
|
|
end;
|
|
|
|
finally
|
|
|
|
TsWorkbook(sheet.Workbook).UnlockFormulas;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Formula token array }
|
|
|
|
if boReadFormulas in FWorkbook.Options then
|
|
|
|
begin
|
|
|
|
ok := ReadRPNTokenArray(AStream, cell);
|
|
|
|
if not ok then
|
|
|
|
begin
|
|
|
|
msg := Format(rsFormulaNotSupported, [
|
|
|
|
GetCellString(ARow, ACol), '.xls'
|
|
|
|
]);
|
|
|
|
if (boAbortReadOnFormulaError in Workbook.Options) then
|
|
|
|
raise Exception.Create(msg)
|
|
|
|
else begin
|
|
|
|
sheet.WriteErrorValue(cell, errFormulaNotSupported);
|
|
|
|
FWorkbook.AddErrorMsg(msg);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{Add attributes}
|
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode and (cell <> FIncompleteCell) then
|
|
|
|
(FWorkbook as TsWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadFromStream(AStream: TStream;
|
2023-03-22 23:08:00 +00:00
|
|
|
APassword: String = ''; AParams: TsStreamParams = []);
|
|
|
|
var
|
2023-03-23 23:57:37 +00:00
|
|
|
BIFF34EOF: Boolean;
|
2023-03-22 23:08:00 +00:00
|
|
|
RecordType: Word;
|
|
|
|
CurStreamPos: Int64;
|
|
|
|
BOFFound: Boolean;
|
|
|
|
begin
|
|
|
|
Unused(APassword, AParams);
|
2023-03-23 23:57:37 +00:00
|
|
|
BIFF34EOF := False;
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
{ 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;
|
2023-03-23 23:57:37 +00:00
|
|
|
while not BIFF34EOF do
|
2023-03-22 23:08:00 +00:00
|
|
|
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);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_BOF_3 : if FFormat = BIFF3 then BOFFound := true else BIFF34EOF := true;
|
|
|
|
INT_EXCEL_ID_BOF_4 : if FFormat = BIFF4 then BOFFound := true else BIFF34EOF := true;
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
|
|
|
|
INT_EXCEL_ID_BOTTOMMARGIN : ReadMargin(AStream, 3);
|
|
|
|
INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream);
|
|
|
|
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
|
|
|
|
INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream);
|
|
|
|
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
|
|
|
|
INT_EXCEL_ID_DEFINEDNAME : ReadDefinedName(AStream);
|
|
|
|
INT_EXCEL_ID_DEFROWHEIGHT : ReadDefRowHeight(AStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_DIMENSION : ReadDimension(AStream);
|
|
|
|
INT_EXCEL_ID_EOF : BIFF34EOF := True;
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_EXTERNCOUNT : ReadEXTERNCOUNT(AStream, FWorksheet);
|
|
|
|
INT_EXCEL_ID_EXTERNSHEET : ReadEXTERNSHEET(AStream, FWorksheet);
|
|
|
|
INT_EXCEL_ID_FONT : ReadFont(AStream);
|
|
|
|
INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_FORMAT_3 : if FFormat = BIFF3 then ReadFormat(AStream);
|
|
|
|
INT_EXCEL_ID_FORMAT_4 : if FFormat = BIFF4 then ReadFormat(AStream);
|
|
|
|
INT_EXCEL_ID_FORMULA_3 : if FFormat = BIFF3 then ReadFormula(AStream);
|
|
|
|
INT_EXCEL_ID_FORMULA_4 : if FFormat = BIFF4 then ReadFormula(AStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_HEADER : ReadHeaderFooter(AStream, true);
|
|
|
|
INT_EXCEL_ID_HCENTER : ReadHCENTER(AStream);
|
|
|
|
INT_EXCEL_ID_HORZPAGEBREAK : ReadHorizontalPageBreaks(AStream, FWorksheet);
|
|
|
|
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_OBJECTPROTECT : ReadObjectProtect(AStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_PAGESETUP : if FFormat = BIFF4 then ReadPageSetup(AStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_PALETTE : ReadPALETTE(AStream);
|
|
|
|
INT_EXCEL_ID_PANE : ReadPane(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);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_RK : ReadRKValue(AStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_ROW : ReadRowInfo(AStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_SCL : if FFormat = BIFF4 then ReadSCLRecord(AStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_SELECTION : ReadSELECTION(AStream);
|
|
|
|
INT_EXCEL_ID_SHEETPR : ReadSHEETPR(AStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_STANDARDWIDTH : if FFormat = BIFF4 then ReadStandardWidth(AStream, FWorksheet);
|
2023-03-22 23:08:00 +00:00
|
|
|
INT_EXCEL_ID_STRING : ReadStringRecord(AStream);
|
|
|
|
INT_EXCEL_ID_TOPMARGIN : ReadMargin(AStream, 2);
|
|
|
|
INT_EXCEL_ID_VCENTER : ReadVCENTER(AStream);
|
|
|
|
INT_EXCEL_ID_VERTPAGEBREAK : ReadVerticalPageBreaks(AStream, FWorksheet);
|
|
|
|
INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream);
|
|
|
|
INT_EXCEL_ID_WINDOWPROTECT : ReadWindowProtect(AStream);
|
2023-03-23 23:57:37 +00:00
|
|
|
INT_EXCEL_ID_XF_3 : if FFormat = BIFF3 then ReadXF(AStream);
|
|
|
|
INT_EXCEL_ID_XF_4 : if FFormat = BIFF4 then ReadXF(AStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
else
|
|
|
|
// nothing
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Make sure we are in the right position for the next record
|
|
|
|
AStream.Seek(CurStreamPos + RecordSize, soFromBeginning);
|
|
|
|
|
|
|
|
if AStream.Position >= AStream.Size then
|
2023-03-23 23:57:37 +00:00
|
|
|
BIFF34EOF := True;
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
if not BOFFound then
|
|
|
|
raise EFPSpreadsheetReader.Create('BOF record not found.');
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Convert palette indexes to rgb colors
|
2023-03-23 23:57:37 +00:00
|
|
|
//FixColors;
|
|
|
|
// Remove unnecessary column and row records
|
2023-03-22 23:08:00 +00:00
|
|
|
FixCols(FWorksheet);
|
|
|
|
FixRows(FWorksheet);
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadLabel(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
2023-03-23 23:57:37 +00:00
|
|
|
rec: TBIFF34_LabelRecord;
|
2023-03-22 23:08:00 +00:00
|
|
|
L: Word;
|
|
|
|
ARow, ACol: Cardinal;
|
|
|
|
XF: WORD;
|
|
|
|
cell: PCell;
|
|
|
|
ansistr: ansistring = '';
|
|
|
|
valuestr: String;
|
|
|
|
begin
|
|
|
|
rec.Row := 0; // to silence the compiler...
|
|
|
|
|
|
|
|
{ Read entire record, starting at Row, except for string data }
|
2023-03-23 23:57:37 +00:00
|
|
|
AStream.ReadBuffer(rec, SizeOf(TBIFF34_LabelRecord));
|
2023-03-22 23:08:00 +00:00
|
|
|
ARow := WordLEToN(rec.Row);
|
|
|
|
ACol := WordLEToN(rec.Col);
|
|
|
|
XF := WordLEToN(rec.XFIndex);
|
|
|
|
|
|
|
|
{ Byte String with 16-bit size }
|
|
|
|
L := WordLEToN(rec.TextLen);
|
|
|
|
SetLength(ansistr, L);
|
|
|
|
AStream.ReadBuffer(ansistr[1], L);
|
|
|
|
|
|
|
|
{ Create cell }
|
|
|
|
if FIsVirtualMode then begin
|
|
|
|
InitCell(FWorksheet, ARow, ACol, FVirtualCell);
|
|
|
|
cell := @FVirtualCell;
|
|
|
|
end else
|
|
|
|
cell := (FWorksheet as TsWorksheet).AddCell(ARow, ACol);
|
|
|
|
|
|
|
|
{ Save the data }
|
|
|
|
valueStr := ConvertEncoding(ansistr, FCodePage, encodingUTF8);
|
|
|
|
(FWorksheet as TsWorksheet).WriteText(cell, valueStr); //ISO_8859_1ToUTF8(ansistr));
|
|
|
|
|
|
|
|
{ Add attributes }
|
|
|
|
ApplyCellFormatting(cell, XF);
|
|
|
|
|
|
|
|
if FIsVirtualMode then
|
|
|
|
(Workbook as TsWorkbook).OnReadCellData(Workbook, ARow, ACol, cell);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Reads the default column width that is used when a bit in the GCW bit structure
|
|
|
|
is set for the corresponding column. The GCW is ignored here. The column
|
|
|
|
width read from the STANDARDWIDTH record overrides the one from the
|
|
|
|
DEFCOLWIDTH record. }
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadStandardWidth(AStream: TStream;
|
2023-03-22 23:08:00 +00:00
|
|
|
ASheet: TsBasicWorksheet);
|
|
|
|
var
|
|
|
|
w: Word;
|
|
|
|
begin
|
|
|
|
// read width in 1/256 of the width of "0" character
|
|
|
|
w := WordLEToN(AStream.ReadWord);
|
|
|
|
(ASheet as TsWorksheet).WriteDefaultRowHeight(w / 256, suChars);
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ Reads a STRING record which contains the result of string formula. }
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadStringRecord(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
|
|
|
len: Word;
|
|
|
|
s: ansistring = '';
|
|
|
|
begin
|
|
|
|
// The string is a byte-string with 16 bit length
|
|
|
|
len := WordLEToN(AStream.ReadWord);
|
|
|
|
if len > 0 then begin
|
|
|
|
SetLength(s, Len);
|
|
|
|
AStream.ReadBuffer(s[1], len);
|
|
|
|
if (FIncompleteCell <> nil) and (s <> '') then begin
|
|
|
|
FIncompletecell^.UTF8StringValue := ConvertEncoding(s, FCodePage, encodingUTF8);
|
|
|
|
FIncompleteCell^.ContentType := cctUTF8String;
|
|
|
|
if FIsVirtualMode then
|
|
|
|
(Workbook as TsWorkbook).OnReadCellData(
|
|
|
|
Workbook, FIncompleteCell^.Row, FIncompleteCell^.Col, FIncompleteCell
|
|
|
|
);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
FIncompleteCell := nil;
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
procedure TsSpreadBIFF34Reader.ReadXF(AStream: TStream);
|
2023-03-22 23:08:00 +00:00
|
|
|
var
|
2023-03-23 23:57:37 +00:00
|
|
|
rec: TBIFF34_XFRecord;
|
2023-03-22 23:08:00 +00:00
|
|
|
fmt: TsCellFormat;
|
|
|
|
cidx: Integer;
|
|
|
|
nfparams: TsNumFormatParams;
|
|
|
|
nfs: String;
|
2023-03-23 23:57:37 +00:00
|
|
|
nfIndex: Integer;
|
|
|
|
border: DWord;
|
|
|
|
backgr: Word;
|
2023-03-22 23:08:00 +00:00
|
|
|
b: Byte;
|
2023-03-23 23:57:37 +00:00
|
|
|
w: Word;
|
2023-03-22 23:08:00 +00:00
|
|
|
dw: DWord;
|
|
|
|
fill: Word;
|
|
|
|
fs: TsFillStyle;
|
|
|
|
book: TsWorkbook;
|
|
|
|
begin
|
|
|
|
book := FWorkbook as TsWorkbook;
|
|
|
|
|
|
|
|
InitFormatRecord(fmt);
|
|
|
|
fmt.ID := FCellFormatList.Count;
|
|
|
|
|
|
|
|
// Read the complete XF record into a buffer
|
2023-03-23 23:57:37 +00:00
|
|
|
rec := Default(TBIFF34_XFRecord);
|
|
|
|
AStream.ReadBuffer(rec, SizeOf(TBIFF34_XFRecord));
|
2023-03-22 23:08:00 +00:00
|
|
|
|
|
|
|
// Font index
|
|
|
|
fmt.FontIndex := FixFontIndex(rec.FontIndex);
|
|
|
|
if fmt.FontIndex > 1 then
|
|
|
|
Include(fmt.UsedFormattingFields, uffFont);
|
|
|
|
|
|
|
|
// Number format index
|
2023-03-23 23:57:37 +00:00
|
|
|
nfIndex := rec.NumFormatIndex;
|
|
|
|
if nfIndex <> 0 then begin
|
|
|
|
nfs := NumFormatList[nfIndex];
|
2023-03-22 23:08:00 +00:00
|
|
|
// "General" (NumFormatIndex = 0) not stored in workbook's NumFormatList
|
2023-03-23 23:57:37 +00:00
|
|
|
if (nfIndex > 0) and not SameText(nfs, 'General') then
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
|
|
|
fmt.NumberFormatIndex := book.AddNumberFormat(nfs);
|
|
|
|
nfParams := book.GetNumberFormat(fmt.NumberFormatIndex);
|
|
|
|
fmt.NumberFormat := nfParams.NumFormat;
|
|
|
|
fmt.NumberFormatStr := nfs;
|
|
|
|
Include(fmt.UsedFormattingFields, uffNumberFormat);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Horizontal text alignment
|
2023-03-23 23:57:37 +00:00
|
|
|
case FFormat of
|
|
|
|
BIFF3: w := WordLEToN(rec.Align_TextBreak_ParentXF_3) and MASK_XF_HOR_ALIGN;
|
|
|
|
BIFF4: w := rec.Align_TextBreak_Orientation_4 AND MASK_XF_HOR_ALIGN;
|
|
|
|
end;
|
|
|
|
if (w <= ord(High(TsHorAlignment))) then
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
2023-03-23 23:57:37 +00:00
|
|
|
fmt.HorAlignment := TsHorAlignment(w);
|
2023-03-22 23:08:00 +00:00
|
|
|
if fmt.HorAlignment <> haDefault then
|
|
|
|
Include(fmt.UsedFormattingFields, uffHorAlign);
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Vertical text alignment
|
2023-03-23 23:57:37 +00:00
|
|
|
if FFormat = BIFF4 then
|
2023-03-22 23:08:00 +00:00
|
|
|
begin
|
2023-03-23 23:57:37 +00:00
|
|
|
b := (rec.Align_TextBreak_Orientation_4 AND MASK_XF_VERT_ALIGN) shr 4;
|
|
|
|
if (b + 1 <= ord(high(TsVertAlignment))) then
|
|
|
|
begin
|
|
|
|
fmt.VertAlignment := TsVertAlignment(b + 1); // + 1 due to vaDefault
|
|
|
|
// Unfortunately BIFF does not provide a "default" vertical alignment code.
|
|
|
|
// Without the following correction "non-formatted" cells would always have
|
|
|
|
// the uffVertAlign FormattingField set which contradicts the statement of
|
|
|
|
// not being formatted.
|
|
|
|
if fmt.VertAlignment = vaBottom then
|
|
|
|
fmt.VertAlignment := vaDefault;
|
|
|
|
if fmt.VertAlignment <> vaDefault then
|
|
|
|
Include(fmt.UsedFormattingFields, uffVertAlign);
|
|
|
|
end;
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// Word wrap
|
2023-03-23 23:57:37 +00:00
|
|
|
case FFormat of
|
|
|
|
BIFF3: b := rec.Align_TextBreak_ParentXF_3;
|
|
|
|
BIFF4: b := rec.Align_TextBreak_Orientation_4;
|
|
|
|
end;
|
|
|
|
if (b and MASK_XF_TEXTWRAP) <> 0 then
|
2023-03-22 23:08:00 +00:00
|
|
|
Include(fmt.UsedFormattingFields, uffWordwrap);
|
|
|
|
|
|
|
|
// Text rotation
|
2023-03-23 23:57:37 +00:00
|
|
|
if FFormat = BIFF4 then
|
|
|
|
begin
|
|
|
|
case (rec.Align_TextBreak_Orientation_4 and MASK_XF_ORIENTATION) shr 6 of
|
|
|
|
XF_ROTATION_HORIZONTAL : fmt.TextRotation := trHorizontal;
|
|
|
|
XF_ROTATION_90DEG_CCW : fmt.TextRotation := rt90DegreeCounterClockwiseRotation;
|
|
|
|
XF_ROTATION_90DEG_CW : fmt.TextRotation := rt90DegreeClockwiseRotation;
|
|
|
|
XF_ROTATION_STACKED : fmt.TextRotation := rtStacked;
|
|
|
|
end;
|
|
|
|
if fmt.TextRotation <> trHorizontal then
|
|
|
|
Include(fmt.UsedFormattingFields, uffTextRotation);
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Cell borders
|
|
|
|
case FFormat of
|
|
|
|
BIFF3: border := DWordLEToN(rec.Border_3);
|
|
|
|
BIFF4: border := DWordLEToN(rec.Border_4);
|
2023-03-22 23:08:00 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
// The 4 masked bits encode the line style of the border line. 0 = no line.
|
|
|
|
// The case of "no line" is not included in the TsLineStyle enumeration.
|
|
|
|
// --> correct by subtracting 1!
|
2023-03-23 23:57:37 +00:00
|
|
|
dw := border and MASK_XF_BORDER_BOTTOM_STYLE;
|
2023-03-22 23:08:00 +00:00
|
|
|
if dw <> 0 then
|
|
|
|
begin
|
|
|
|
Include(fmt.Border, cbSouth);
|
|
|
|
fmt.BorderStyles[cbSouth].LineStyle := TsLineStyle(dw shr 16 - 1);
|
|
|
|
Include(fmt.UsedFormattingFields, uffBorder);
|
|
|
|
end;
|
2023-03-23 23:57:37 +00:00
|
|
|
dw := border and MASK_XF_BORDER_LEFT_STYLE;
|
2023-03-22 23:08:00 +00:00
|
|
|
if dw <> 0 then
|
|
|
|
begin
|
|
|
|
Include(fmt.Border, cbWest);
|
|
|
|
fmt.BorderStyles[cbWest].LineStyle := TsLineStyle(dw shr 8 - 1);
|
|
|
|
Include(fmt.UsedFormattingFields, uffBorder);
|
|
|
|
end;
|
2023-03-23 23:57:37 +00:00
|
|
|
dw := border and MASK_XF_BORDER_RIGHT_STYLE;
|
2023-03-22 23:08:00 +00:00
|
|
|
if dw <> 0 then
|
|
|
|
begin
|
|
|
|
Include(fmt.Border, cbEast);
|
|
|
|
fmt.BorderStyles[cbEast].LineStyle := TsLineStyle(dw shr 24 - 1);
|
|
|
|
Include(fmt.UsedFormattingFields, uffBorder);
|
|
|
|
end;
|
2023-03-23 23:57:37 +00:00
|
|
|
dw := border and MASK_XF_BORDER_TOP_STYLE;
|
2023-03-22 23:08:00 +00:00
|
|
|
if dw <> 0 then
|
|
|
|
begin
|
|
|
|
Include(fmt.Border, cbNorth);
|
|
|
|
fmt.BorderStyles[cbNorth].LineStyle := TsLineStyle(dw - 1);
|
|
|
|
Include(fmt.UsedFormattingFields, uffBorder);
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Border line colors
|
|
|
|
// NOTE: It is possible that the palette is not yet known at this moment.
|
|
|
|
// Therefore we store the palette index encoded into the colors.
|
|
|
|
// They will be converted to rgb in "FixColors".
|
2023-03-23 23:57:37 +00:00
|
|
|
cidx := (border and MASK_XF_BORDER_LEFT_COLOR) shr 11;
|
|
|
|
fmt.BorderStyles[cbWest].Color := IfThen(cidx >= BIFF34_MAX_PALETTE_SIZE, scBlack, SetAsPaletteIndex(cidx));
|
|
|
|
cidx := (border and MASK_XF_BORDER_RIGHT_COLOR) shr 27;
|
|
|
|
fmt.BorderStyles[cbEast].Color := IfThen(cidx >= BIFF34_MAX_PALETTE_SIZE, scBlack, SetAsPaletteIndex(cidx));
|
|
|
|
cidx := (border and MASK_XF_BORDER_TOP_COLOR) shr 3;
|
|
|
|
fmt.BorderStyles[cbNorth].Color := IfThen(cidx >= BIFF34_MAX_PALETTE_SIZE, scBlack, SetAsPaletteIndex(cidx));
|
|
|
|
cidx := (border and MASK_XF_BORDER_BOTTOM_COLOR) shr 19;
|
|
|
|
fmt.BorderStyles[cbSouth].Color := IfThen(cidx >= BIFF34_MAX_PALETTE_SIZE, scBlack, SetAsPaletteIndex(cidx));
|
|
|
|
|
|
|
|
// Cell Background
|
|
|
|
case FFormat of
|
|
|
|
BIFF3: backgr := WordLEToN(rec.Background_3);
|
|
|
|
BIFF4: backgr := WordLEToN(rec.Background_4);
|
|
|
|
end;
|
|
|
|
fill := backgr and MASK_XF_BKGR_FILLPATTERN;
|
2023-03-22 23:08:00 +00:00
|
|
|
for fs in TsFillStyle do
|
|
|
|
begin
|
|
|
|
if fs = fsNoFill then
|
|
|
|
Continue;
|
|
|
|
if fill = MASK_XF_FILL_PATT[fs] then
|
|
|
|
begin
|
|
|
|
// Fill style
|
|
|
|
fmt.Background.Style := fs;
|
|
|
|
// Pattern color
|
2023-03-23 23:57:37 +00:00
|
|
|
cidx := (backgr and MASK_XF_BKGR_PATTERN_COLOR) shr 6; // Palette index
|
2023-03-22 23:08:00 +00:00
|
|
|
fmt.Background.FgColor := IfThen(cidx = SYS_DEFAULT_FOREGROUND_COLOR,
|
|
|
|
scBlack, SetAsPaletteIndex(cidx));
|
2023-03-23 23:57:37 +00:00
|
|
|
cidx := (backgr and MASK_XF_BKGR_BACKGROUND_COLOR) shr 11;
|
2023-03-22 23:08:00 +00:00
|
|
|
fmt.Background.BgColor := IfThen(cidx = SYS_DEFAULT_BACKGROUND_COLOR,
|
|
|
|
scTransparent, SetAsPaletteIndex(cidx));
|
|
|
|
Include(fmt.UsedFormattingFields, uffBackground);
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
// Protection
|
2023-03-23 23:57:37 +00:00
|
|
|
case FFormat of
|
|
|
|
BIFF3: w := rec.XFType_Prot_3 and MASK_XF_TYPE_PROTECTION;
|
|
|
|
BIFF4: w := WordLEToN(rec.XFType_Prot_ParentXF_4) and MASK_XF_TYPE_PROTECTION;
|
|
|
|
end;
|
|
|
|
case w of
|
2023-03-22 23:08:00 +00:00
|
|
|
0:
|
|
|
|
fmt.Protection := [];
|
|
|
|
MASK_XF_TYPE_PROT_LOCKED:
|
|
|
|
fmt.Protection := [cpLockCell];
|
|
|
|
MASK_XF_TYPE_PROT_FORMULA_HIDDEN:
|
|
|
|
fmt.Protection := [cpHideFormulas];
|
|
|
|
MASK_XF_TYPE_PROT_LOCKED + MASK_XF_TYPE_PROT_FORMULA_HIDDEN:
|
|
|
|
fmt.Protection := [cpLockCell, cpHideFormulas];
|
|
|
|
end;
|
|
|
|
if fmt.Protection <> DEFAULT_CELL_PROTECTION then
|
|
|
|
Include(fmt.UsedFormattingFields, uffProtection);
|
|
|
|
|
|
|
|
// Add the XF to the list
|
|
|
|
FCellFormatList.Add(fmt);
|
|
|
|
end;
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
{ ------------------------------------------------------------------------------
|
|
|
|
TsSpreadBIFF3Reader
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
constructor TsSpreadBIFF3Reader.Create(AWorkbook: TsBasicWorkbook);
|
|
|
|
begin
|
|
|
|
inherited;
|
|
|
|
FFormat := BIFF3;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Checks the header of the stream for the signature of BIFF3 files
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
class function TsSpreadBIFF3Reader.CheckFileFormat(AStream: TStream): Boolean;
|
|
|
|
var
|
|
|
|
P: Int64;
|
|
|
|
buf: packed array[0..1] of byte = (0, 0);
|
|
|
|
n: Integer;
|
|
|
|
begin
|
|
|
|
Result := false;
|
|
|
|
P := AStream.Position;
|
|
|
|
try
|
|
|
|
AStream.Position := 0;
|
|
|
|
n := AStream.Read(buf, SizeOf(buf));
|
|
|
|
if n = SizeOf(buf) then
|
|
|
|
Result := (buf[0] = 9) and (buf[1] = 2);
|
|
|
|
finally
|
|
|
|
AStream.Position := P;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Reads the identifier for an RPN function with fixed argument count from the
|
|
|
|
stream.
|
|
|
|
Valid for BIFF2-BIFF3.
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
function TsSpreadBIFF3Reader.ReadRPNFunc(AStream: TStream): Word;
|
|
|
|
var
|
|
|
|
b: Byte;
|
|
|
|
begin
|
|
|
|
b := AStream.ReadByte;
|
|
|
|
Result := b;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{ ------------------------------------------------------------------------------
|
|
|
|
TsSpreadBIFF4Reader
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
constructor TsSpreadBIFF4Reader.Create(AWorkbook: TsBasicWorkbook);
|
|
|
|
begin
|
|
|
|
inherited;
|
|
|
|
FFormat := BIFF4;
|
|
|
|
end;
|
|
|
|
|
|
|
|
{@@ ----------------------------------------------------------------------------
|
|
|
|
Checks the header of the stream for the signature of BIFF4 files
|
|
|
|
-------------------------------------------------------------------------------}
|
|
|
|
class function TsSpreadBIFF4Reader.CheckFileFormat(AStream: TStream): Boolean;
|
|
|
|
var
|
|
|
|
P: Int64;
|
|
|
|
buf: packed array[0..1] of byte = (0, 0);
|
|
|
|
n: Integer;
|
|
|
|
begin
|
|
|
|
Result := false;
|
|
|
|
P := AStream.Position;
|
|
|
|
try
|
|
|
|
AStream.Position := 0;
|
|
|
|
n := AStream.Read(buf, SizeOf(buf));
|
|
|
|
if n = SizeOf(buf) then
|
|
|
|
Result := (buf[0] = 9) and (buf[1] = 4);
|
|
|
|
finally
|
|
|
|
AStream.Position := P;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2023-03-22 23:08:00 +00:00
|
|
|
initialization
|
2023-03-23 23:57:37 +00:00
|
|
|
sfidExcel3 := RegisterSpreadFormat(sfUser,
|
|
|
|
TsSpreadBIFF3Reader, nil, 'Excel 3', 'BIFF3', ['.xls']
|
|
|
|
);
|
2023-03-22 23:08:00 +00:00
|
|
|
sfidExcel4 := RegisterSpreadFormat(sfUser,
|
|
|
|
TsSpreadBIFF4Reader, nil, 'Excel 4', 'BIFF4', ['.xls']
|
|
|
|
);
|
|
|
|
|
2023-03-23 23:57:37 +00:00
|
|
|
// Converts the palette to litte-endian
|
|
|
|
MakeLEPalette(PALETTE_BIFF34);
|
|
|
|
|
2023-03-22 23:08:00 +00:00
|
|
|
end.
|
|
|
|
|