fpspreadsheet: Read print ranges and repeated print rows and columns from BIFF5 and BIFF8 files (xls - BIFF2 does not support them).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4512 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2016-02-19 19:53:10 +00:00
parent 0b9b04a541
commit d2b80cf143
7 changed files with 921 additions and 78 deletions

View File

@ -51,6 +51,10 @@ function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; overload; ANext: PRPNItem): PRPNItem; overload;
function RPNCellOffset(ARowOffset, AColOffset: Integer; AFlags: TsRelFlags; function RPNCellOffset(ARowOffset, AColOffset: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem; ANext: PRPNItem): PRPNItem;
function RPNCellRef3D(ASheet, ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem;
function RPNCellRange3D(ASheet1, ARow1, ACol1, ASheet2, ARow2, ACol2: Integer;
AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem;
function RPNErr(AErrCode: TsErrorValue; ANext: PRPNItem): PRPNItem; function RPNErr(AErrCode: TsErrorValue; ANext: PRPNItem): PRPNItem;
function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem;
function RPNMissingArg(ANext: PRPNItem): PRPNItem; function RPNMissingArg(ANext: PRPNItem): PRPNItem;
@ -82,6 +86,7 @@ begin
New(Result); New(Result);
FillChar(Result^.FE, SizeOf(Result^.FE), 0); FillChar(Result^.FE, SizeOf(Result^.FE), 0);
Result^.FE.StringValue := ''; Result^.FE.StringValue := '';
Result^.FE.SheetNames := '';
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -254,6 +259,30 @@ begin
Result^.Next := ANext; Result^.Next := ANext;
end; end;
function RPNCellRef3D(ASheet, ARow, ACol: Integer; AFlags: TsRelFlags;
ANext: PRPNItem): PRPNItem;
begin
Result := NewRPNItem;
Result^.FE.ElementKind := fekCellRef3d;
Result^.FE.Sheet := ASheet;
Result^.FE.Row := ARow;
Result^.FE.Col := ACol;
Result^.FE.RelFlags := AFlags;
Result^.Next := ANext;
end;
function RPNCellRange3D(ASheet1, ARow1, ACol1, ASheet2, ARow2, ACol2: Integer;
AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem;
begin
Result := RPNCellRef3d(ASheet1, ARow1, ACol1, AFlags, ANext);
Result^.FE.ElementKind := fekCellRange3D;
Result^.FE.Sheet2 := ASheet2;
Result^.FE.Row2 := ARow2;
Result^.FE.Col2 := ACol2;
Result^.FE.RelFlags := AFlags;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Creates an entry in the RPN array with an error value. Creates an entry in the RPN array with an error value.
@ -437,6 +466,7 @@ begin
nextitem := item^.Next; nextitem := item^.Next;
Result[n] := item^.FE; Result[n] := item^.FE;
Result[n].StringValue := item^.FE.StringValue; Result[n].StringValue := item^.FE.StringValue;
Result[n].Sheetnames := item^.FE.SheetNames;
if AReverse then dec(n) else inc(n); if AReverse then dec(n) else inc(n);
DisposeRPNItem(item); DisposeRPNItem(item);
item := nextitem; item := nextitem;

View File

@ -104,13 +104,15 @@ type
} }
TFEKind = ( TFEKind = (
{ Basic operands } { Basic operands }
fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger, fekCell, fekCellRef, fekCellRange, fekCellOffset,
fekString, fekBool, fekErr, fekMissingArg, fekCellRef3d, fekCellRange3d,
fekNum, fekInteger, fekString, fekBool, fekErr, fekMissingArg,
{ Basic operations } { Basic operations }
fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus,
fekConcat, // string concatenation fekConcat, // string concatenation
fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual,
fekParen, // show parenthesis around expression node fekList, // List operator
fekParen, // show parenthesis around expression node -- don't add anything after fekParen!
{ Functions - they are identified by their name } { Functions - they are identified by their name }
fekFunc fekFunc
); );
@ -139,16 +141,17 @@ const
type type
{@@ Elements of an expanded formula. {@@ Elements of an expanded formula.
Note: If ElementKind is fekCellOffset, "Row" and "Col" have to be cast Note: If ElementKind is fekCellOffset, "Row" and "Col" have to be cast to signed integers! }
to signed integers! }
TsFormulaElement = record TsFormulaElement = record
ElementKind: TFEKind; ElementKind: TFEKind;
Row, Row2: Cardinal; // zero-based Row, Row2: Cardinal; // zero-based
Col, Col2: Cardinal; // zero-based Col, Col2: Cardinal; // zero-based
Sheet, Sheet2: Integer; // zero-based
SheetNames: String; // both sheet names separated by a TAB character (intermediate use only)
DoubleValue: double; DoubleValue: double;
IntValue: Word; IntValue: Word;
StringValue: String; StringValue: String;
RelFlags: TsRelFlags; // store info on relative/absolute addresses RelFlags: TsRelFlags; // info on relative/absolute addresses
FuncName: String; FuncName: String;
ParamsNum: Byte; ParamsNum: Byte;
end; end;
@ -569,6 +572,15 @@ type
{@@ Array with cell ranges } {@@ Array with cell ranges }
TsCellRangeArray = array of TsCellRange; TsCellRangeArray = array of TsCellRange;
{@@ Record combining sheet index and row/column corner indexes of a cell range }
TsCellRange3d = record
Row1, Col1, Row2, Col2: Cardinal;
Sheet1, Sheet2: Integer;
end;
{@@ Array of 3d cell ranges }
TsCellRange3dArray = array of TsCellRange3d;
{@@ Record containing limiting indexes of column or row range } {@@ Record containing limiting indexes of column or row range }
TsRowColRange = record TsRowColRange = record
FirstIndex, LastIndex: Cardinal; FirstIndex, LastIndex: Cardinal;

View File

@ -115,6 +115,25 @@ type
procedure TestWriteRead_BIFF5_HeaderFooterFontColor_2sheets; procedure TestWriteRead_BIFF5_HeaderFooterFontColor_2sheets;
procedure TestWriteRead_BIFF5_HeaderFooterFontColor_3sheets; procedure TestWriteRead_BIFF5_HeaderFooterFontColor_3sheets;
procedure TestWriteRead_BIFF5_PrintRanges_1sheet_1Range_NoSpace;
procedure TestWriteRead_BIFF5_PrintRanges_1sheet_2Ranges_NoSpace;
procedure TestWriteRead_BIFF5_PrintRanges_2sheet_1Range_NoSpace;
procedure TestWriteRead_BIFF5_PrintRanges_2sheet_2Ranges_NoSpace;
procedure TestWriteRead_BIFF5_PrintRanges_1sheet_1Range_Space;
procedure TestWriteRead_BIFF5_PrintRanges_1sheet_2Ranges_Space;
procedure TestWriteRead_BIFF5_PrintRanges_2sheet_1Range_Space;
procedure TestWriteRead_BIFF5_PrintRanges_2sheet_2Ranges_Space;
procedure TestWriteRead_BIFF5_RepeatedRow_0;
procedure TestWriteRead_BIFF5_RepeatedRows_0_1;
procedure TestWriteRead_BIFF5_RepeatedRows_1_3;
procedure TestWriteRead_BIFF5_RepeatedCol_0;
procedure TestWriteRead_BIFF5_RepeatedCols_0_1;
procedure TestWriteRead_BIFF5_RepeatedCols_1_3;
procedure TestWriteRead_BIFF5_RepeatedCol_0_Row_0;
procedure TestWriteRead_BIFF5_RepeatedCols_0_1_Rows_0_1;
{ BIFF8 page layout tests } { BIFF8 page layout tests }
procedure TestWriteRead_BIFF8_PageMargins_1sheet_0; procedure TestWriteRead_BIFF8_PageMargins_1sheet_0;
procedure TestWriteRead_BIFF8_PageMargins_1sheet_1; procedure TestWriteRead_BIFF8_PageMargins_1sheet_1;
@ -169,6 +188,25 @@ type
procedure TestWriteRead_BIFF8_HeaderFooterFontColor_2sheets; procedure TestWriteRead_BIFF8_HeaderFooterFontColor_2sheets;
procedure TestWriteRead_BIFF8_HeaderFooterFontColor_3sheets; procedure TestWriteRead_BIFF8_HeaderFooterFontColor_3sheets;
procedure TestWriteRead_BIFF8_PrintRanges_1sheet_1Range_NoSpace;
procedure TestWriteRead_BIFF8_PrintRanges_1sheet_2Ranges_NoSpace;
procedure TestWriteRead_BIFF8_PrintRanges_2sheet_1Range_NoSpace;
procedure TestWriteRead_BIFF8_PrintRanges_2sheet_2Ranges_NoSpace;
procedure TestWriteRead_BIFF8_PrintRanges_1sheet_1Range_Space;
procedure TestWriteRead_BIFF8_PrintRanges_1sheet_2Ranges_Space;
procedure TestWriteRead_BIFF8_PrintRanges_2sheet_1Range_Space;
procedure TestWriteRead_BIFF8_PrintRanges_2sheet_2Ranges_Space;
procedure TestWriteRead_BIFF8_RepeatedRow_0;
procedure TestWriteRead_BIFF8_RepeatedRows_0_1;
procedure TestWriteRead_BIFF8_RepeatedRows_1_3;
procedure TestWriteRead_BIFF8_RepeatedCol_0;
procedure TestWriteRead_BIFF8_RepeatedCols_0_1;
procedure TestWriteRead_BIFF8_RepeatedCols_1_3;
procedure TestWriteRead_BIFF8_RepeatedCol_0_Row_0;
procedure TestWriteRead_BIFF8_RepeatedCols_0_1_Rows_0_1;
{ OOXML page layout tests } { OOXML page layout tests }
procedure TestWriteRead_OOXML_PageMargins_1sheet_0; procedure TestWriteRead_OOXML_PageMargins_1sheet_0;
procedure TestWriteRead_OOXML_PageMargins_1sheet_1; procedure TestWriteRead_OOXML_PageMargins_1sheet_1;
@ -1138,6 +1176,87 @@ begin
end; end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_1sheet_1Range_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel5, 1, 1, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_1sheet_2Ranges_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel5, 1, 2, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_2sheet_1Range_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel5, 2, 1, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_2sheet_2Ranges_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel5, 2, 2, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_1sheet_1Range_Space;
begin
TestWriteRead_PrintRanges(sfExcel5, 1, 1, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_1sheet_2Ranges_Space;
begin
TestWriteRead_PrintRanges(sfExcel5, 1, 2, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_2sheet_1Range_Space;
begin
TestWriteRead_PrintRanges(sfExcel5, 2, 1, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_PrintRanges_2sheet_2Ranges_Space;
begin
TestWriteRead_PrintRanges(sfExcel5, 2, 2, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedRow_0;
begin
TestWriteRead_RepeatedColRows(sfExcel5, -1, -1, 0, 0);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedRows_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel5, -1, -1, 0, 1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedRows_1_3;
begin
TestWriteRead_RepeatedColRows(sfExcel5, -1, -1, 1, 3);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedCol_0;
begin
TestWriteRead_RepeatedColRows(sfExcel5, 0, 0, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedCols_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel5, 0, 1, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedCols_1_3;
begin
TestWriteRead_RepeatedColRows(sfExcel5, 1, 3, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedCol_0_Row_0;
begin
TestWriteRead_RepeatedColRows(sfExcel5, 0, 0, 0, 0);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF5_RepeatedCols_0_1_Rows_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel5, 0, 1, 0, 1);
end;
{ Tests for BIFF8 file format } { Tests for BIFF8 file format }
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PageMargins_1sheet_0; procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PageMargins_1sheet_0;
@ -1363,6 +1482,87 @@ begin
end; end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_1sheet_1Range_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel8, 1, 1, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_1sheet_2Ranges_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel8, 1, 2, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_2sheet_1Range_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel8, 2, 1, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_2sheet_2Ranges_NoSpace;
begin
TestWriteRead_PrintRanges(sfExcel8, 2, 2, false);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_1sheet_1Range_Space;
begin
TestWriteRead_PrintRanges(sfExcel8, 1, 1, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_1sheet_2Ranges_Space;
begin
TestWriteRead_PrintRanges(sfExcel8, 1, 2, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_2sheet_1Range_Space;
begin
TestWriteRead_PrintRanges(sfExcel8, 2, 1, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PrintRanges_2sheet_2Ranges_Space;
begin
TestWriteRead_PrintRanges(sfExcel8, 2, 2, true);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedRow_0;
begin
TestWriteRead_RepeatedColRows(sfExcel8, -1, -1, 0, 0);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedRows_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel8, -1, -1, 0, 1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedRows_1_3;
begin
TestWriteRead_RepeatedColRows(sfExcel8, -1, -1, 1, 3);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedCol_0;
begin
TestWriteRead_RepeatedColRows(sfExcel8, 0, 0, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedCols_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel8, 0, 1, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedCols_1_3;
begin
TestWriteRead_RepeatedColRows(sfExcel8, 1, 3, -1, -1);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedCol_0_Row_0;
begin
TestWriteRead_RepeatedColRows(sfExcel8, 0, 0, 0, 0);
end;
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_RepeatedCols_0_1_Rows_0_1;
begin
TestWriteRead_RepeatedColRows(sfExcel8, 0, 1, 0, 1);
end;
{ Tests for OOXML file format } { Tests for OOXML file format }
procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_OOXML_PageMargins_1sheet_0; procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_OOXML_PageMargins_1sheet_0;

View File

@ -59,7 +59,7 @@ interface
uses uses
Classes, SysUtils, fpcanvas, lconvencoding, Classes, SysUtils, fpcanvas, lconvencoding,
fpsTypes, fpspreadsheet, fpsTypes, fpspreadsheet, fpsrpn,
xlscommon, xlscommon,
{$ifdef USE_NEW_OLE} {$ifdef USE_NEW_OLE}
fpolebasic, fpolebasic,
@ -69,7 +69,6 @@ uses
fpsUtils; fpsUtils;
type type
{ TsSpreadBIFF5Reader } { TsSpreadBIFF5Reader }
TsSpreadBIFF5Reader = class(TsSpreadBIFFReader) TsSpreadBIFF5Reader = class(TsSpreadBIFFReader)
@ -77,9 +76,11 @@ type
procedure PopulatePalette; override; procedure PopulatePalette; override;
{ Record writing methods } { Record writing methods }
procedure ReadBoundsheet(AStream: TStream); procedure ReadBoundsheet(AStream: TStream);
procedure ReadDEFINEDNAME(AStream: TStream);
procedure ReadFONT(const AStream: TStream); procedure ReadFONT(const AStream: TStream);
procedure ReadFORMAT(AStream: TStream); override; procedure ReadFORMAT(AStream: TStream); override;
procedure ReadLABEL(AStream: TStream); override; procedure ReadLABEL(AStream: TStream); override;
function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override;
procedure ReadRSTRING(AStream: TStream); procedure ReadRSTRING(AStream: TStream);
procedure ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet); procedure ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet);
procedure ReadStringRecord(AStream: TStream); override; procedure ReadStringRecord(AStream: TStream); override;
@ -88,7 +89,6 @@ type
procedure ReadXF(AStream: TStream); procedure ReadXF(AStream: TStream);
public public
{ General reading methods } { General reading methods }
// procedure ReadFromFile(AFileName: string); override;
procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override;
end; end;
@ -346,7 +346,9 @@ type
end; end;
{ TsSpreadBIFF5Reader } {------------------------------------------------------------------------------}
{ TsSpreadBIFF5Reader }
{------------------------------------------------------------------------------}
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Populates the reader's default palette using the BIFF5 default colors. Populates the reader's default palette using the BIFF5 default colors.
@ -357,6 +359,96 @@ begin
FPalette.UseColors(PALETTE_BIFF5); FPalette.UseColors(PALETTE_BIFF5);
end; end;
{@@ ----------------------------------------------------------------------------
Reads a BOUNDSHEET record containing a worksheet name
-------------------------------------------------------------------------------}
procedure TsSpreadBIFF5Reader.ReadBoundsheet(AStream: TStream);
var
Len: Byte;
s: AnsiString;
sheetName: String;
begin
{ Absolute stream position of the BOF record of the sheet represented
by this record }
// Just assume that they are in order
AStream.ReadDWord();
{ Visibility }
AStream.ReadByte();
{ Sheet type }
AStream.ReadByte();
{ Sheet name: Byte string, 8-bit length }
Len := AStream.ReadByte();
SetLength(s, Len);
AStream.ReadBuffer(s[1], Len*SizeOf(AnsiChar));
sheetName := ConvertEncoding(s, FCodePage, EncodingUTF8);
FWorksheetNames.Add(sheetName);
end;
{@@ ----------------------------------------------------------------------------
Reads a DEFINEDNAME record. Currently only extracts print ranges and titles.
-------------------------------------------------------------------------------}
procedure TsSpreadBIFF5Reader.ReadDEFINEDNAME(AStream: TStream);
var
options: Word;
len: byte;
formulaSize: Word;
ansistr: ansiString;
defName: String;
rpnformula: TsRPNFormula;
extsheetIndex: Integer;
sheetIndex: Integer;
begin
// 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;
procedure TsSpreadBIFF5Reader.ReadWorkbookGlobals(AStream: TStream); procedure TsSpreadBIFF5Reader.ReadWorkbookGlobals(AStream: TStream);
var var
SectionEOF: Boolean = False; SectionEOF: Boolean = False;
@ -372,14 +464,16 @@ begin
CurStreamPos := AStream.Position; CurStreamPos := AStream.Position;
case RecordType of case RecordType of
INT_EXCEL_ID_BOF : ; INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream);
INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream); INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream);
INT_EXCEL_ID_FONT : ReadFont(AStream); INT_EXCEL_ID_DEFINEDNAME : ReadDefinedName(AStream);
INT_EXCEL_ID_FORMAT : ReadFormat(AStream); INT_EXCEL_ID_EXTERNSHEET : ReadExternSheet(AStream);
INT_EXCEL_ID_XF : ReadXF(AStream); INT_EXCEL_ID_FONT : ReadFont(AStream);
INT_EXCEL_ID_PALETTE : ReadPalette(AStream); INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
INT_EXCEL_ID_EOF : SectionEOF := True; INT_EXCEL_ID_XF : ReadXF(AStream);
INT_EXCEL_ID_PALETTE : ReadPalette(AStream);
INT_EXCEL_ID_EOF : SectionEOF := True;
else else
// nothing // nothing
end; end;
@ -401,7 +495,7 @@ var
RecordType: Word; RecordType: Word;
CurStreamPos: Int64; CurStreamPos: Int64;
begin begin
FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurrentWorksheet], true); FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurSheetIndex], true);
while (not SectionEOF) do while (not SectionEOF) do
begin begin
@ -497,30 +591,36 @@ begin
FixRows(FWorksheet); FixRows(FWorksheet);
end; end;
procedure TsSpreadBIFF5Reader.ReadBoundsheet(AStream: TStream); function TsSpreadBIFF5Reader.ReadRPNCellRange3D(AStream: TStream;
var ARPNItem: PRPNItem): Boolean;
var var
Len: Byte; sheetIndex: SmallInt;
s: AnsiString; sheetIndex1, sheetIndex2: Word;
sheetName: String; r1, c1, r2, c2: Cardinal;
flags: TsRelFlags;
begin begin
{ Absolute stream position of the BOF record of the sheet represented Result := true;
by this record }
// Just assume that they are in order
AStream.ReadDWord();
{ Visibility } // 1-based index to EXTERNSHEET record containing name of first referenced worksheet
AStream.ReadByte(); // negative for 3D reference, positive for external reference
sheetIndex := WordLEToN(AStream.ReadWord);
if sheetIndex > 0 then
exit(false); // we support only internal references here!
sheetIndex := abs(sheetindex) - 1; // make it a usable 0-based index although we don't need it any more...
{ Sheet type } // Next 8 bytes not used.
AStream.ReadByte(); AStream.ReadDWord;
AStream.ReadDWord;
{ Sheet name: Byte string, 8-bit length } // Zero-based index to first and last referenced sheet (in case of internal ref)
Len := AStream.ReadByte(); sheetIndex1 := WordLEToN(AStream.ReadWord);
sheetIndex2 := WordLEToN(AStream.ReadWord);
SetLength(s, Len); // Cell range coordinates
AStream.ReadBuffer(s[1], Len*SizeOf(AnsiChar)); ReadRPNCellRangeAddress(AStream, r1, c1, r2, c2, flags);
sheetName := ConvertEncoding(s, FCodePage, EncodingUTF8); if r2 = $FFFF then r2 := Cardinal(-1);
FWorksheetNames.Add(sheetName); if c2 = $FF then c2 := Cardinal(-1);
ARPNItem := RPNCellRange3D(sheetIndex1, r1, c1, sheetIndex2, r2, c2, flags, ARPNItem);
end; end;
procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream); procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream);

View File

@ -56,7 +56,7 @@ interface
uses uses
Classes, SysUtils, fpcanvas, DateUtils, contnrs, lazutf8, Classes, SysUtils, fpcanvas, DateUtils, contnrs, lazutf8,
fpstypes, fpspreadsheet, xlscommon, fpstypes, fpspreadsheet, fpsrpn, xlscommon,
{$ifdef USE_NEW_OLE} {$ifdef USE_NEW_OLE}
fpolebasic, fpolebasic,
{$else} {$else}
@ -66,6 +66,12 @@ uses
type type
TBIFF8ExternSheet = packed record
ExternBookIndex: Word;
FirstSheetIndex: Word;
LastSheetIndex: Word;
end;
{ TsSpreadBIFF8Reader } { TsSpreadBIFF8Reader }
TsSpreadBIFF8Reader = class(TsSpreadBIFFReader) TsSpreadBIFF8Reader = class(TsSpreadBIFFReader)
private private
@ -75,22 +81,26 @@ type
FCommentPending: Boolean; FCommentPending: Boolean;
FCommentID: Integer; FCommentID: Integer;
FCommentLen: Integer; FCommentLen: Integer;
procedure ReadBoundsheet(AStream: TStream); FBiff8ExternSheets: array of TBiff8ExternSheet;
function ReadString(const AStream: TStream; const ALength: Word; function ReadString(const AStream: TStream; const ALength: Word;
out ARichTextParams: TsRichTextParams): String; out ARichTextParams: TsRichTextParams): String;
function ReadUnformattedWideString(const AStream: TStream; function ReadUnformattedWideString(const AStream: TStream;
const ALength: Word): WideString; const ALength: Word): WideString;
function ReadWideString(const AStream: TStream; const ALength: Word; function ReadWideString(const AStream: TStream; const ALength: Word;
out ARichTextParams: TsRichTextParams): WideString; overload; out ARichTextParams: TsRichTextParams): WideString; overload;
function ReadWideString(const AStream: TStream; const AUse8BitLength: Boolean): WideString; overload; function ReadWideString(const AStream: TStream;
const AUse8BitLength: Boolean): WideString; overload;
protected protected
procedure PopulatePalette; override; procedure PopulatePalette; override;
procedure ReadBOUNDSHEET(AStream: TStream);
procedure ReadCONTINUE(const AStream: TStream); procedure ReadCONTINUE(const AStream: TStream);
procedure ReadDEFINEDNAME(const AStream: TStream);
procedure ReadEXTERNSHEET(const AStream: TStream);
procedure ReadFONT(const AStream: TStream); procedure ReadFONT(const AStream: TStream);
procedure ReadFORMAT(AStream: TStream); override; procedure ReadFORMAT(AStream: TStream); override;
procedure ReadHeaderFooter(AStream: TStream; AIsHeader: Boolean); override; procedure ReadHeaderFooter(AStream: TStream; AIsHeader: Boolean); override;
procedure ReadHyperLink(AStream: TStream); procedure ReadHyperLink(const AStream: TStream);
procedure ReadHyperlinkToolTip(AStream: TStream); procedure ReadHyperlinkToolTip(const AStream: TStream);
procedure ReadLABEL(AStream: TStream); override; procedure ReadLABEL(AStream: TStream); override;
procedure ReadLabelSST(const AStream: TStream); procedure ReadLabelSST(const AStream: TStream);
procedure ReadMergedCells(const AStream: TStream); procedure ReadMergedCells(const AStream: TStream);
@ -103,6 +113,7 @@ type
out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); override; out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); override;
procedure ReadRPNCellRangeAddress(AStream: TStream; procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override;
function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override;
procedure ReadRPNCellRangeOffset(AStream: TStream; procedure ReadRPNCellRangeOffset(AStream: TStream;
out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer;
out AFlags: TsRelFlags); override; out AFlags: TsRelFlags); override;
@ -443,13 +454,14 @@ type
Text: String; Text: String;
end; end;
{ TsSpreadBIFF8Reader } { TsSpreadBIFF8Reader }
destructor TsSpreadBIFF8Reader.Destroy; destructor TsSpreadBIFF8Reader.Destroy;
var var
j: Integer; j: Integer;
begin begin
SetLength(FBiff8ExternSheets, 0);
if Assigned(FSharedStringTable) then if Assigned(FSharedStringTable) then
begin begin
for j := FSharedStringTable.Count-1 downto 0 do for j := FSharedStringTable.Count-1 downto 0 do
@ -777,16 +789,18 @@ begin
if RecordType <> INT_EXCEL_ID_CONTINUE then begin if RecordType <> INT_EXCEL_ID_CONTINUE then begin
case RecordType of case RecordType of
INT_EXCEL_ID_BOF : ; INT_EXCEL_ID_BOF : ;
INT_EXCEL_ID_BOUNDSHEET: ReadBoundSheet(AStream); INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream);
INT_EXCEL_ID_EOF : SectionEOF := True; INT_EXCEL_ID_DEFINEDNAME : ReadDEFINEDNAME(AStream);
INT_EXCEL_ID_SST : ReadSST(AStream); INT_EXCEL_ID_EOF : SectionEOF := True;
INT_EXCEL_ID_CODEPAGE : ReadCodepage(AStream); INT_EXCEL_ID_EXTERNSHEET : ReadEXTERNSHEET(AStream);
INT_EXCEL_ID_FONT : ReadFont(AStream); INT_EXCEL_ID_SST : ReadSST(AStream);
INT_EXCEL_ID_FORMAT : ReadFormat(AStream); INT_EXCEL_ID_CODEPAGE : ReadCodepage(AStream);
INT_EXCEL_ID_XF : ReadXF(AStream); INT_EXCEL_ID_FONT : ReadFont(AStream);
INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream); INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
INT_EXCEL_ID_PALETTE : ReadPalette(AStream); INT_EXCEL_ID_XF : ReadXF(AStream);
INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream);
INT_EXCEL_ID_PALETTE : ReadPalette(AStream);
else else
// nothing // nothing
end; end;
@ -809,7 +823,7 @@ var
RecordType: Word; RecordType: Word;
CurStreamPos: Int64; CurStreamPos: Int64;
begin begin
FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurrentWorksheet], true); FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurSheetIndex], true);
while (not SectionEOF) do while (not SectionEOF) do
begin begin
@ -829,6 +843,7 @@ begin
INT_EXCEL_ID_COLINFO : ReadColInfo(AStream); INT_EXCEL_ID_COLINFO : ReadColInfo(AStream);
INT_EXCEL_ID_CONTINUE : ReadCONTINUE(AStream); INT_EXCEL_ID_CONTINUE : ReadCONTINUE(AStream);
INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream); INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream);
INT_EXCEL_ID_DEFINEDNAME : ReadDefinedName(AStream);
INT_EXCEL_ID_EOF : SectionEOF := True; INT_EXCEL_ID_EOF : SectionEOF := True;
INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false); INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
@ -944,7 +959,6 @@ begin
end; end;
end; end;
(* (*
const AStrea
procedure TsSpreadBIFF8Reader.ReadFromStream(AStream: TStream); procedure TsSpreadBIFF8Reader.ReadFromStream(AStream: TStream);
var var
BIFF8EOF: Boolean; BIFF8EOF: Boolean;
@ -1179,6 +1193,26 @@ begin
if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2);
end; end;
function TsSpreadBIFF8Reader.ReadRPNCellRange3D(AStream: TStream;
var ARPNItem: PRPNItem): Boolean;
var
sheetIndex: Integer;
r1, c1, r2, c2: Cardinal;
flags: TsRelFlags;
begin
Result := true;
sheetIndex := WordLEToN(AStream.ReadWord);
if FBiff8ExternSheets[sheetIndex].ExternBookIndex <> 0 then
exit(false);
ReadRPNCellRangeAddress(AStream, r1, c1, r2, c2, flags);
if r2 = $FFFF then r2 := Cardinal(-1);
if c2 = $FF then c2 := Cardinal(-1);
ARPNItem := RPNCellRange3D(
FBiff8ExternSheets[sheetIndex].FirstSheetIndex, r1, c1,
FBiff8ExternSheets[sheetIndex].LastSheetIndex, r2, c2,
flags, ARPNItem);
end;
{ Reads the difference between row and column corner indexes of a cell range { Reads the difference between row and column corner indexes of a cell range
and a reference cell. and a reference cell.
Overriding the implementation in xlscommon. } Overriding the implementation in xlscommon. }
@ -1628,6 +1662,84 @@ begin
FCellFormatList.Add(fmt); FCellFormatList.Add(fmt);
end; end;
{ Reads a DEFINEDNAME record. Currently only extract print ranges and titles. }
procedure TsSpreadBIFF8Reader.ReadDEFINEDNAME(const AStream: TStream);
var
options: Word;
len: byte;
formulaSize: Word;
widestr: WideString;
defName: String;
rpnformula: TsRPNFormula;
rtf: TsRichTextParams;
validOnSheet: Integer;
begin
// 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);
// not used
AStream.ReadWord;
// Sheet index (1-based) on which the name is valid (0 = global)
validOnSheet := 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
wideStr := ReadWideString(AStream, len, rtf);
defName := UTF8Encode(widestr);
// Formula
if not ReadRPNTokenArray(AStream, formulaSize, rpnFormula) then
exit;
// Store defined name in internal list
FDefinedNames.Add(TsBIFFDefinedName.Create(defName, rpnFormula, validOnSheet));
// Skip rest...
end;
{ Reads an EXTERNSHEET record. Needed for named cells and print ranges. }
procedure TsSpreadBIFF8Reader.ReadEXTERNSHEET(const AStream: TStream);
var
numItems: Word;
i: Integer;
begin
numItems := WordLEToN(AStream.ReadWord);
SetLength(FBiff8ExternSheets, numItems);
for i := 0 to numItems-1 do begin
AStream.ReadBuffer(FBiff8ExternSheets[i], Sizeof(FBiff8ExternSheets[i]));
with FBiff8ExternSheets[i] do
begin
ExternBookIndex := WordLEToN(ExternBookIndex);
FirstSheetIndex := WordLEToN(FirstSheetIndex);
LastSheetIndex := WordLEToN(LastSheetIndex);
end;
end;
end;
{ Reads a FONT record. The retrieved font is stored in the workbook's FontList. } { Reads a FONT record. The retrieved font is stored in the workbook's FontList. }
procedure TsSpreadBIFF8Reader.ReadFONT(const AStream: TStream); procedure TsSpreadBIFF8Reader.ReadFONT(const AStream: TStream);
var var
@ -1761,7 +1873,7 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads a HYPERLINK record Reads a HYPERLINK record
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Reader.ReadHyperlink(AStream: TStream); procedure TsSpreadBIFF8Reader.ReadHyperlink(const AStream: TStream);
var var
row, col, row1, col1, row2, col2: word; row, col, row1, col1, row2, col2: word;
guid: TGUID; guid: TGUID;
@ -1902,7 +2014,7 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads a HYPERLINK TOOLTIP record Reads a HYPERLINK TOOLTIP record
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Reader.ReadHyperlinkToolTip(AStream: TStream); procedure TsSpreadBIFF8Reader.ReadHyperlinkToolTip(const AStream: TStream);
var var
txt: String; txt: String;
widestr: widestring; widestr: widestring;

View File

@ -12,7 +12,7 @@ interface
uses uses
Classes, SysUtils, DateUtils, lconvencoding, Classes, SysUtils, DateUtils, lconvencoding,
fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormatParser, fpsPalette, fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormatParser, fpsPalette,
fpsReaderWriter; fpsReaderWriter, fpsrpn;
const const
{ RECORD IDs which didn't change across versions 2-8 } { RECORD IDs which didn't change across versions 2-8 }
@ -349,6 +349,21 @@ type
RecordSize: Word; RecordSize: Word;
end; end;
{TsBIFFDefinedName }
TsBIFFDefinedName = class
private
FName: String;
FFormula: TsRPNFormula;
FValidOnSheet: Integer;
function GetRanges: TsCellRange3dArray;
public
constructor Create(AName: String; AFormula: TsRPNFormula; AValidOnSheet: Integer);
procedure UpdateSheetIndex(ASheetName: String; ASheetIndex: Integer);
property Name: String read FName;
property Ranges: TsCellRange3dArray read GetRanges;
property ValidOnSheet: Integer read FValidOnSheet;
end;
{ TsSpreadBIFFReader } { TsSpreadBIFFReader }
TsSpreadBIFFReader = class(TsCustomSpreadReader) TsSpreadBIFFReader = class(TsCustomSpreadReader)
protected protected
@ -360,9 +375,11 @@ type
FIncompleteNoteLength: Word; FIncompleteNoteLength: Word;
FFirstNumFormatIndexInFile: Integer; FFirstNumFormatIndexInFile: Integer;
FPalette: TsPalette; FPalette: TsPalette;
FDefinedNames: TFPList;
FWorksheetNames: TStrings; FWorksheetNames: TStrings;
FCurrentWorksheet: Integer; FCurSheetIndex: Integer;
FActivePane: Integer; FActivePane: Integer;
FExternSheets: TStrings;
procedure AddBuiltinNumFormats; override; procedure AddBuiltinNumFormats; override;
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual;
@ -375,7 +392,11 @@ type
// Returns the numberformat for a given XF record // Returns the numberformat for a given XF record
procedure ExtractNumberFormat(AXFIndex: WORD; procedure ExtractNumberFormat(AXFIndex: WORD;
out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); virtual; out ANumberFormat: TsNumberFormat; out ANumberFormatStr: String); virtual;
procedure ExtractPrintRanges(AWorksheet: TsWorksheet);
procedure ExtractPrintTitles(AWorksheet: TsWorksheet);
function FindDefinedName(AWorksheet: TsWorksheet; const AName: String): TsBiffDefinedName;
procedure FixColors; procedure FixColors;
procedure FixDefinedNames(AWorksheet: TsWorksheet);
function FixFontIndex(AFontIndex: Integer): Integer; function FixFontIndex(AFontIndex: Integer): Integer;
// Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value // Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value
function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat;
@ -397,6 +418,8 @@ type
procedure ReadDefColWidth(AStream: TStream); procedure ReadDefColWidth(AStream: TStream);
// Read the default row height // Read the default row height
procedure ReadDefRowHeight(AStream: TStream); procedure ReadDefRowHeight(AStream: TStream);
// Read an EXTERNSHEET record (defined names)
procedure ReadExternSheet(AStream: TStream);
// Read FORMAT record (cell formatting) // Read FORMAT record (cell formatting)
procedure ReadFormat(AStream: TStream); virtual; procedure ReadFormat(AStream: TStream); virtual;
// Read FORMULA record // Read FORMULA record
@ -431,13 +454,17 @@ type
out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); virtual; out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); virtual;
procedure ReadRPNCellRangeAddress(AStream: TStream; procedure ReadRPNCellRangeAddress(AStream: TStream;
out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual;
function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; virtual;
procedure ReadRPNCellRangeOffset(AStream: TStream; procedure ReadRPNCellRangeOffset(AStream: TStream;
out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer;
out AFlags: TsRelFlags); virtual; out AFlags: TsRelFlags); virtual;
function ReadRPNFunc(AStream: TStream): Word; virtual; function ReadRPNFunc(AStream: TStream): Word; virtual;
procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual; procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual;
function ReadRPNTokenArray(AStream: TStream; ACell: PCell; function ReadRPNTokenArray(AStream: TStream; ACell: PCell;
ASharedFormulaBase: PCell = nil): Boolean; ASharedFormulaBase: PCell = nil): Boolean; overload;
function ReadRPNTokenArray(AStream: TStream; ARpnTokenArraySize: Word;
out ARpnFormula: TsRPNFormula; ACell: PCell = nil;
ASharedFormulaBase: PCell = nil): Boolean; overload;
function ReadRPNTokenArraySize(AStream: TStream): word; virtual; function ReadRPNTokenArraySize(AStream: TStream): word; virtual;
procedure ReadSELECTION(AStream: TStream); procedure ReadSELECTION(AStream: TStream);
procedure ReadSharedFormula(AStream: TStream); procedure ReadSharedFormula(AStream: TStream);
@ -599,7 +626,8 @@ implementation
uses uses
AVL_Tree, Math, Variants, AVL_Tree, Math, Variants,
{%H-}fpspatches, fpsStrings, fpsClasses, fpsNumFormat, xlsConst, {%H-}fpspatches, fpsStrings, fpsClasses, fpsNumFormat, xlsConst,
fpsrpn, fpsExprParser; //fpsrpn,
fpsExprParser;
const const
{ Helper table for rpn formulas: { Helper table for rpn formulas:
@ -610,6 +638,8 @@ const
INT_EXCEL_TOKEN_TREFR, {fekCellRef} INT_EXCEL_TOKEN_TREFR, {fekCellRef}
INT_EXCEL_TOKEN_TAREA_R, {fekCellRange} INT_EXCEL_TOKEN_TAREA_R, {fekCellRange}
INT_EXCEL_TOKEN_TREFN_V, {fekCellOffset} INT_EXCEL_TOKEN_TREFN_V, {fekCellOffset}
INT_EXCEL_TOKEN_TREF3D_R, {fekCellRef3d }
INT_EXCEL_TOKEN_TAREA3D_R, {fekCellRange3d }
INT_EXCEL_TOKEN_TNUM, {fekNum} INT_EXCEL_TOKEN_TNUM, {fekNum}
INT_EXCEL_TOKEN_TINT, {fekInteger} INT_EXCEL_TOKEN_TINT, {fekInteger}
INT_EXCEL_TOKEN_TSTR, {fekString} INT_EXCEL_TOKEN_TSTR, {fekString}
@ -633,6 +663,7 @@ const
INT_EXCEL_TOKEN_TLT, {fekLess <} INT_EXCEL_TOKEN_TLT, {fekLess <}
INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=} INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=}
INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>} INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>}
INT_EXCEL_TOKEN_TLIST, {List operator (",")}
INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis} INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis}
Word(-1) {fekFunc} Word(-1) {fekFunc}
); );
@ -820,6 +851,76 @@ begin
end; end;
{------------------------------------------------------------------------------}
{ TsBIFFDefinedName }
{------------------------------------------------------------------------------}
constructor TsBIFFDefinedName.Create(AName: String; AFormula: TsRPNFormula;
AValidOnSheet: Integer);
begin
FName := AName;
FFormula := AFormula;
FValidOnSheet := AValidOnSheet;
end;
function TsBIFFDefinedName.GetRanges: TsCellRange3dArray;
var
i, n: Integer;
elem: TsFormulaElement;
begin
SetLength(Result, 0);
for i:=0 to Length(FFormula)-1 do begin
n := Length(Result);
elem := FFormula[i];
case elem.ElementKind of
fekCellRef3D:
begin
SetLength(Result, n+1);
Result[n].Sheet1 := elem.Sheet;
Result[n].Row1 := elem.Row;
Result[n].Col1 := elem.Col;
Result[n].Sheet2 := -1;
Result[n].Row2 := Cardinal(-1);
Result[n].Col2 := Cardinal(-1);
end;
fekCellRange3d:
begin
SetLength(Result, n+1);
Result[n].Sheet1 := elem.Sheet;
Result[n].Row1 := elem.Row;
Result[n].Col1 := elem.Col;
Result[n].Sheet2 := elem.Sheet2;
Result[n].Row2 := elem.Row2;
Result[n].Col2 := elem.Col2;
end;
end;
end;
end;
procedure TsBIFFDefinedName.UpdateSheetIndex(ASheetName: String; ASheetIndex: Integer);
var
elem: TsFormulaElement;
i, p: Integer;
s: String;
begin
for i:=0 to Length(FFormula)-1 do begin
elem := FFormula[i];
if (elem.ElementKind in [fekCellRef3d, fekCellRange3d]) then begin
if elem.SheetNames = '' then
Continue;
p := pos(#9, elem.SheetNames);
if p > 0 then begin
if ASheetName = Copy(elem.SheetNames, 1, p-1) then
elem.Sheet := ASheetIndex;
if ASheetName = Copy(elem.SheetNames, p+1, MaxInt) then
elem.Sheet2 := ASheetIndex;
end else
if ASheetName = elem.SheetNames then
elem.Sheet := ASheetIndex;
end;
end;
end;
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ TsSpreadBIFFReader } { TsSpreadBIFFReader }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
@ -834,6 +935,9 @@ begin
FCellFormatList := TsCellFormatList.Create(true); FCellFormatList := TsCellFormatList.Create(true);
// true = allow duplicates! XF indexes get out of sync if not all format records are in list // true = allow duplicates! XF indexes get out of sync if not all format records are in list
FExternSheets := TStringList.Create;
FDefinedNames := TFPList.Create;
// Initial base date in case it won't be read from file // Initial base date in case it won't be read from file
FDateMode := dm1900; FDateMode := dm1900;
@ -850,8 +954,15 @@ end;
Destructor of the reader class Destructor of the reader class
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
destructor TsSpreadBIFFReader.Destroy; destructor TsSpreadBIFFReader.Destroy;
var
j: Integer;
begin begin
for j:=0 to FDefinedNames.Count-1 do TObject(FDefinedNames[j]).Free;
FDefinedNames.Free;
FExternSheets.Free;
FPalette.Free; FPalette.Free;
inherited Destroy; inherited Destroy;
end; end;
@ -982,6 +1093,64 @@ begin
end; end;
end; end;
procedure TsSpreadBiffReader.ExtractPrintRanges(AWorksheet: TsWorksheet);
var
defName: TsBiffDefinedName;
rng: TsCellRange3DArray;
i: Integer;
begin
// #6 is the symbol for "Print_Area"
defName := FindDefinedName(AWorksheet, #6);
if defName <> nil then
begin
rng := defName.Ranges;
for i := 0 to High(rng) do
AWorksheet.AddPrintRange(rng[i].Row1, rng[i].Col1, rng[i].Row2, rng[i].Col2);
end;
end;
procedure TsSpreadBiffReader.ExtractPrintTitles(AWorksheet: TsWorksheet);
var
defName: TsBiffDefinedName;
rng: TsCellRange3dArray;
i: Integer;
begin
// #7 is the symbol for "Print_Titles"
defName := FindDefinedName(AWorksheet, #7);
if defName <> nil then
begin
rng := defName.Ranges;
for i := 0 to High(rng) do
begin
if (rng[i].Col2 <> Cardinal(-1)) then
AWorksheet.SetRepeatedPrintCols(rng[i].Col1, rng[i].Col2)
else
if (rng[i].Row2 <> Cardinal(-1)) then
AWorksheet.SetRepeatedPrintRows(rng[i].Row1, rng[i].Row2);
end;
end;
end;
function TsSpreadBIffReader.FindDefinedName(AWorksheet: TsWorksheet;
const AName: String): TsBiffDefinedName;
var
i: integer;
wi: Integer;
defName: TsBiffDefinedName;
begin
wi := FWorkbook.GetWorksheetIndex(AWorksheet);
for i := 0 to FDefinedNames.Count-1 do
begin
defName := TsBiffDefinedName(FDefinedNames[i]);
if (defName.ValidOnSheet = wi) and (defName.Name = AName) then
begin
Result := TsBiffDefinedName(FDefinedNames[i]);
exit;
end;
end;
Result := nil;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
It is a problem of the biff file structure that the font is loaded before the It is a problem of the biff file structure that the font is loaded before the
palette. Therefore, when reading the font, we cannot determine its rgb color. palette. Therefore, when reading the font, we cannot determine its rgb color.
@ -1029,6 +1198,21 @@ begin
end; end;
end; end;
procedure TsSpreadBIFFReader.FixDefinedNames(AWorksheet: TsWorksheet);
var
sheetName1, sheetName2: String;
i: Integer;
defname: TsBiffDefinedName;
sheetIndex: Integer;
begin
sheetIndex := FWorkbook.GetWorksheetIndex(AWorksheet);
for i:=0 to FDefinedNames.Count-1 do begin
defname := TsBiffDefinedName(FDefinedNames.Items[i]);
defname.UpdateSheetIndex(AWorksheet.Name, sheetIndex);
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Converts the index of a font in the reader fontlist to the index of this font Converts the index of a font in the reader fontlist to the index of this font
in the workbook's fontlist. If the font is not yet contained in the workbook in the workbook's fontlist. If the font is not yet contained in the workbook
@ -1349,6 +1533,30 @@ begin
FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION; FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION;
end; end;
{@@ ----------------------------------------------------------------------------
In the file format versions up to BIFF5 (incl) this record stores the name of
an external document and a sheet name inside of this document.
NOTE: A character #03 is prepended to the sheet name if the EXTERNSHEET stores
a reference to one of the own sheets.
-------------------------------------------------------------------------------}
procedure TsSpreadBIFFReader.ReadExternSheet(AStream: TStream);
var
len, b: Byte;
ansistr: AnsiString;
s: String;
begin
len := AStream.ReadByte;
b := AStream.ReadByte;
if b = 3 then
inc(len);
SetLength(ansistr, len);
AStream.ReadBuffer(ansistr[2], len-1);
ansistr[1] := char(b);
s := ConvertEncoding(ansistr, FCodePage, encodingUTF8);
FExternSheets.Add(s);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads the (number) FORMAT record for formatting numerical data Reads the (number) FORMAT record for formatting numerical data
To be overridden by descendants. To be overridden by descendants.
@ -1957,6 +2165,13 @@ begin
if (r2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); if (r2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2);
end; end;
function TsSpreadBIFFReader.ReadRPNCellRange3D(AStream: TStream;
var ARPNItem: PRPNItem): Boolean;
begin
Result := false; // "false" means: "not supported"
// must be overridden
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Reads the difference between row and column corner indexes of a cell range Reads the difference between row and column corner indexes of a cell range
and a reference cell. and a reference cell.
@ -2024,6 +2239,7 @@ end;
Reads the array of rpn tokens from the current stream position, creates an Reads the array of rpn tokens from the current stream position, creates an
rpn formula, converts it to a string formula and stores it in the cell. rpn formula, converts it to a string formula and stores it in the cell.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
(*
function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream; function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
ACell: PCell; ASharedFormulaBase: PCell = nil): Boolean; ACell: PCell; ASharedFormulaBase: PCell = nil): Boolean;
var var
@ -2179,6 +2395,174 @@ begin
Result := true; Result := true;
end; end;
end; end;
*)
function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
ACell: PCell; ASharedFormulaBase: PCell = nil): Boolean;
var
n: Word;
rpnFormula: TsRPNformula;
strFormula: String;
begin
n := ReadRPNTokenArraySize(AStream);
Result := ReadRPNTokenArray(AStream, n, rpnFormula, ACell, ASharedFormulaBase);
if Result then begin
strFormula := FWorksheet.ConvertRPNFormulaToStringFormula(rpnFormula);
if strFormula <> '' then
ACell^.FormulaValue := strFormula;
end;
end;
function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream;
ARpnTokenArraySize: Word; out ARpnFormula: TsRPNFormula; ACell: PCell = nil;
ASharedFormulaBase: PCell = nil): Boolean;
var
n: Word;
p0: Int64;
token: Byte;
rpnItem: PRPNItem;
supported: boolean;
dblVal: Double = 0.0; // IEEE 8 byte floating point number
flags: TsRelFlags;
r, c, r2, c2: Cardinal;
dr, dc, dr2, dc2: Integer;
sheetIndex: Integer;
fek: TFEKind;
exprDef: TsBuiltInExprIdentifierDef;
funcCode: Word;
b: Byte;
found: Boolean;
begin
rpnItem := nil;
p0 := AStream.Position;
supported := true;
while (AStream.Position < p0 + ARPNTokenArraySize) and supported do begin
token := AStream.ReadByte;
case token of
INT_EXCEL_TOKEN_TREFV:
begin
ReadRPNCellAddress(AStream, r, c, flags);
rpnItem := RPNCellValue(r, c, flags, rpnItem);
end;
INT_EXCEL_TOKEN_TREFR:
begin
ReadRPNCellAddress(AStream, r, c, flags);
rpnItem := RPNCellRef(r, c, flags, rpnItem);
end;
INT_EXCEL_TOKEN_TAREA_R, INT_EXCEL_TOKEN_TAREA_V:
begin
ReadRPNCellRangeAddress(AStream, r, c, r2, c2, flags);
rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem);
end;
INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V:
begin
ReadRPNCellAddressOffset(AStream, dr, dc, flags);
// For compatibility with other formats, convert offsets back to regular indexes.
if (rfRelRow in flags)
then r := LongInt(ACell^.Row) + dr
else r := dr;
if (rfRelCol in flags)
then c := LongInt(ACell^.Col) + dc
else c := dc;
case token of
INT_EXCEL_TOKEN_TREFN_V: rpnItem := RPNCellValue(r, c, flags, rpnItem);
INT_EXCEL_TOKEN_TREFN_R: rpnItem := RPNCellRef(r, c, flags, rpnItem);
end;
end;
INT_EXCEL_TOKEN_TAREA3D_R:
begin
if not ReadRPNCellRange3D(AStream, rpnItem) then supported := false;
end;
INT_EXCEL_TOKEN_TREFN_A:
begin
ReadRPNCellRangeOffset(AStream, dr, dc, dr2, dc2, flags);
// For compatibility with other formats, convert offsets back to regular indexes.
if (rfRelRow in flags)
then r := LongInt(ACell^.Row) + dr
else r := LongInt(ASharedFormulaBase^.Row) + dr;
if (rfRelRow2 in flags)
then r2 := LongInt(ACell^.Row) + dr2
else r2 := LongInt(ASharedFormulaBase^.Row) + dr2;
if (rfRelCol in flags)
then c := LongInt(ACell^.Col) + dc
else c := LongInt(ASharedFormulaBase^.Col) + dc;
if (rfRelCol2 in flags)
then c2 := LongInt(ACell^.Col) + dc2
else c2 := LongInt(ASharedFormulaBase^.Col) + dc2;
rpnItem := RPNCellRange(r, c, r2, c2, flags, rpnItem);
end;
INT_EXCEL_TOKEN_TMISSARG:
rpnItem := RPNMissingArg(rpnItem);
INT_EXCEL_TOKEN_TSTR:
rpnItem := RPNString(ReadString_8BitLen(AStream), rpnItem);
INT_EXCEL_TOKEN_TERR:
rpnItem := RPNErr(ConvertFromExcelError(AStream.ReadByte), rpnItem);
INT_EXCEL_TOKEN_TBOOL:
rpnItem := RPNBool(AStream.ReadByte=1, rpnItem);
INT_EXCEL_TOKEN_TINT:
rpnItem := RPNInteger(WordLEToN(AStream.ReadWord), rpnItem);
INT_EXCEL_TOKEN_TNUM:
begin
AStream.ReadBuffer(dblVal, 8);
rpnItem := RPNNumber(dblVal, rpnItem);
end;
INT_EXCEL_TOKEN_TPAREN:
rpnItem := RPNParenthesis(rpnItem);
INT_EXCEL_TOKEN_FUNC_R,
INT_EXCEL_TOKEN_FUNC_V,
INT_EXCEL_TOKEN_FUNC_A:
// functions with fixed argument count
begin
funcCode := ReadRPNFunc(AStream);
exprDef := BuiltInIdentifiers.IdentifierByExcelCode(funcCode);
if exprDef <> nil then
rpnItem := RPNFunc(exprDef.Name, rpnItem)
else
supported := false;
end;
INT_EXCEL_TOKEN_FUNCVAR_R,
INT_EXCEL_TOKEN_FUNCVAR_V,
INT_EXCEL_TOKEN_FUNCVAR_A:
// functions with variable argument count
begin
b := AStream.ReadByte;
funcCode := ReadRPNFunc(AStream);
exprDef := BuiltinIdentifiers.IdentifierByExcelCode(funcCode);
if exprDef <> nil then
rpnItem := RPNFunc(exprDef.Name, b, rpnItem)
else
supported := false;
end;
INT_EXCEL_TOKEN_TEXP:
// Indicates that cell belongs to a shared or array formula.
// This information is not needed any more.
ReadRPNSharedFormulaBase(AStream, r, c);
else
found := false;
for fek in TBasicOperationTokens do
if (TokenIDs[fek] = token) then begin
rpnItem := RPNFunc(fek, rpnItem);
found := true;
break;
end;
if not found then
supported := false;
end;
end;
if not supported then begin
DestroyRPNFormula(rpnItem);
ARPNFormula := nil;
Result := false;
end
else begin
ARPNFormula := CreateRPNFormula(rpnItem, true); // true --> we have to flip the order of items!
Result := true;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Helper function for reading of the size of the token array of an RPN formula. Helper function for reading of the size of the token array of an RPN formula.
@ -2385,19 +2769,9 @@ end;
procedure TsSpreadBIFFReader.InternalReadFromStream(AStream: TStream); procedure TsSpreadBIFFReader.InternalReadFromStream(AStream: TStream);
var var
BIFFEOF: Boolean; BIFFEOF: Boolean;
i: Integer;
sheet: TsWorksheet;
begin begin
{ OLEStream := TMemoryStream.Create;
try
OLEStorage := TOLEStorage.Create;
try
// Only one stream is necessary for any number of worksheets
OLEDocument.Stream := AStream; //OLEStream;
OLEStorage.ReadOLEStream(AStream, OLEDocument, AStreamName);
finally
OLEStorage.Free;
end;
}
// Check if the operation succeeded // Check if the operation succeeded
if AStream.Size = 0 then if AStream.Size = 0 then
raise Exception.Create('[TsSpreadBIFFReader.InternalReadFromStream] Reading of OLE document failed'); raise Exception.Create('[TsSpreadBIFFReader.InternalReadFromStream] Reading of OLE document failed');
@ -2408,7 +2782,7 @@ begin
{Initializations } {Initializations }
FWorksheetNames := TStringList.Create; FWorksheetNames := TStringList.Create;
try try
FCurrentWorksheet := 0; FCurSheetIndex := 0;
BIFFEOF := false; BIFFEOF := false;
{ Read workbook globals } { Read workbook globals }
@ -2428,12 +2802,21 @@ begin
BIFFEOF := true; BIFFEOF := true;
// Final preparations // Final preparations
inc(FCurrentWorksheet); inc(FCurSheetIndex);
// It can happen in files written by Office97 that the OLE directory is // It can happen in files written by Office97 that the OLE directory is
// at the end of the file. // at the end of the file.
if FCurrentWorksheet = FWorksheetNames.Count then if FCurSheetIndex = FWorksheetNames.Count then
BIFFEOF := true; BIFFEOF := true;
end; end;
{ Extract print ranges, repeated rows/cols }
for i:=0 to FWorkbook.GetWorksheetCount-1 do begin
sheet := FWorkbook.GetWorksheetByIndex(i);
FixDefinedNames(sheet);
ExtractPrintRanges(sheet);
ExtractPrintTitles(sheet);
end;
finally finally
{ Finalization } { Finalization }
FreeAndNil(FWorksheetNames); FreeAndNil(FWorksheetNames);

View File

@ -55,6 +55,12 @@ const
INT_EXCEL_TOKEN_TAREAN_R = $2D; INT_EXCEL_TOKEN_TAREAN_R = $2D;
INT_EXCEL_TOKEN_TAREAN_V = $4D; INT_EXCEL_TOKEN_TAREAN_V = $4D;
INT_EXCEL_TOKEN_TAREAN_A = $6D; INT_EXCEL_TOKEN_TAREAN_A = $6D;
INT_EXCEL_TOKEN_TREF3d_R = $3A;
INT_EXCEL_TOKEN_TREF3d_V = $5A;
INT_EXCEL_TOKEN_TREF3d_A = $7A;
INT_EXCEL_TOKEN_TAREA3d_R = $3B;
INT_EXCEL_TOKEN_TAREA3d_V = $5B;
INT_EXCEL_TOKEN_TAREA3d_A = $7B;
{ Function Tokens } { Function Tokens }
// _R: reference; _V: value; _A: array // _R: reference; _V: value; _A: array