diff --git a/components/fpspreadsheet/fpsrpn.pas b/components/fpspreadsheet/fpsrpn.pas index 11a8dd5bb..bf50ea0b5 100644 --- a/components/fpspreadsheet/fpsrpn.pas +++ b/components/fpspreadsheet/fpsrpn.pas @@ -51,6 +51,10 @@ function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; function RPNCellOffset(ARowOffset, AColOffset: Integer; AFlags: TsRelFlags; 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 RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNMissingArg(ANext: PRPNItem): PRPNItem; @@ -82,6 +86,7 @@ begin New(Result); FillChar(Result^.FE, SizeOf(Result^.FE), 0); Result^.FE.StringValue := ''; + Result^.FE.SheetNames := ''; end; {@@ ---------------------------------------------------------------------------- @@ -254,6 +259,30 @@ begin Result^.Next := ANext; 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. @@ -437,6 +466,7 @@ begin nextitem := item^.Next; Result[n] := item^.FE; Result[n].StringValue := item^.FE.StringValue; + Result[n].Sheetnames := item^.FE.SheetNames; if AReverse then dec(n) else inc(n); DisposeRPNItem(item); item := nextitem; diff --git a/components/fpspreadsheet/fpstypes.pas b/components/fpspreadsheet/fpstypes.pas index 397aae0c0..ab149c01d 100644 --- a/components/fpspreadsheet/fpstypes.pas +++ b/components/fpspreadsheet/fpstypes.pas @@ -104,13 +104,15 @@ type } TFEKind = ( { Basic operands } - fekCell, fekCellRef, fekCellRange, fekCellOffset, fekNum, fekInteger, - fekString, fekBool, fekErr, fekMissingArg, + fekCell, fekCellRef, fekCellRange, fekCellOffset, + fekCellRef3d, fekCellRange3d, + fekNum, fekInteger, fekString, fekBool, fekErr, fekMissingArg, { Basic operations } fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, fekConcat, // string concatenation 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 } fekFunc ); @@ -139,16 +141,17 @@ const type {@@ Elements of an expanded formula. - Note: If ElementKind is fekCellOffset, "Row" and "Col" have to be cast - to signed integers! } + Note: If ElementKind is fekCellOffset, "Row" and "Col" have to be cast to signed integers! } TsFormulaElement = record ElementKind: TFEKind; - Row, Row2: Cardinal; // zero-based - Col, Col2: Cardinal; // zero-based + Row, Row2: 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; IntValue: Word; StringValue: String; - RelFlags: TsRelFlags; // store info on relative/absolute addresses + RelFlags: TsRelFlags; // info on relative/absolute addresses FuncName: String; ParamsNum: Byte; end; @@ -569,6 +572,15 @@ type {@@ Array with cell ranges } 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 } TsRowColRange = record FirstIndex, LastIndex: Cardinal; diff --git a/components/fpspreadsheet/tests/pagelayouttests.pas b/components/fpspreadsheet/tests/pagelayouttests.pas index 0a7875380..372d37852 100644 --- a/components/fpspreadsheet/tests/pagelayouttests.pas +++ b/components/fpspreadsheet/tests/pagelayouttests.pas @@ -115,6 +115,25 @@ type procedure TestWriteRead_BIFF5_HeaderFooterFontColor_2sheets; 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 } procedure TestWriteRead_BIFF8_PageMargins_1sheet_0; procedure TestWriteRead_BIFF8_PageMargins_1sheet_1; @@ -169,6 +188,25 @@ type procedure TestWriteRead_BIFF8_HeaderFooterFontColor_2sheets; 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 } procedure TestWriteRead_OOXML_PageMargins_1sheet_0; procedure TestWriteRead_OOXML_PageMargins_1sheet_1; @@ -1138,6 +1176,87 @@ begin 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 } procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_BIFF8_PageMargins_1sheet_0; @@ -1363,6 +1482,87 @@ begin 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 } procedure TSpreadWriteReadPageLayoutTests.TestWriteRead_OOXML_PageMargins_1sheet_0; diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 7df56e416..374aef9f5 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -59,7 +59,7 @@ interface uses Classes, SysUtils, fpcanvas, lconvencoding, - fpsTypes, fpspreadsheet, + fpsTypes, fpspreadsheet, fpsrpn, xlscommon, {$ifdef USE_NEW_OLE} fpolebasic, @@ -69,7 +69,6 @@ uses fpsUtils; type - { TsSpreadBIFF5Reader } TsSpreadBIFF5Reader = class(TsSpreadBIFFReader) @@ -77,9 +76,11 @@ type procedure PopulatePalette; override; { Record writing methods } procedure ReadBoundsheet(AStream: TStream); + procedure ReadDEFINEDNAME(AStream: TStream); procedure ReadFONT(const AStream: TStream); procedure ReadFORMAT(AStream: TStream); override; procedure ReadLABEL(AStream: TStream); override; + function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; procedure ReadRSTRING(AStream: TStream); procedure ReadStandardWidth(AStream: TStream; ASheet: TsWorksheet); procedure ReadStringRecord(AStream: TStream); override; @@ -88,7 +89,6 @@ type procedure ReadXF(AStream: TStream); public { General reading methods } -// procedure ReadFromFile(AFileName: string); override; procedure ReadFromStream(AStream: TStream; AParams: TsStreamParams = []); override; end; @@ -346,7 +346,9 @@ type end; -{ TsSpreadBIFF5Reader } +{------------------------------------------------------------------------------} +{ TsSpreadBIFF5Reader } +{------------------------------------------------------------------------------} {@@ ---------------------------------------------------------------------------- Populates the reader's default palette using the BIFF5 default colors. @@ -357,6 +359,96 @@ begin FPalette.UseColors(PALETTE_BIFF5); 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); var SectionEOF: Boolean = False; @@ -372,14 +464,16 @@ begin CurStreamPos := AStream.Position; case RecordType of - INT_EXCEL_ID_BOF : ; - INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); - INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream); - INT_EXCEL_ID_FONT : ReadFont(AStream); - INT_EXCEL_ID_FORMAT : ReadFormat(AStream); - INT_EXCEL_ID_XF : ReadXF(AStream); - INT_EXCEL_ID_PALETTE : ReadPalette(AStream); - INT_EXCEL_ID_EOF : SectionEOF := True; + INT_EXCEL_ID_BOF : ; + INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); + INT_EXCEL_ID_CODEPAGE : ReadCodePage(AStream); + INT_EXCEL_ID_DEFINEDNAME : ReadDefinedName(AStream); + INT_EXCEL_ID_EXTERNSHEET : ReadExternSheet(AStream); + INT_EXCEL_ID_FONT : ReadFont(AStream); + INT_EXCEL_ID_FORMAT : ReadFormat(AStream); + INT_EXCEL_ID_XF : ReadXF(AStream); + INT_EXCEL_ID_PALETTE : ReadPalette(AStream); + INT_EXCEL_ID_EOF : SectionEOF := True; else // nothing end; @@ -401,7 +495,7 @@ var RecordType: Word; CurStreamPos: Int64; begin - FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurrentWorksheet], true); + FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurSheetIndex], true); while (not SectionEOF) do begin @@ -497,30 +591,36 @@ begin FixRows(FWorksheet); end; -procedure TsSpreadBIFF5Reader.ReadBoundsheet(AStream: TStream); +function TsSpreadBIFF5Reader.ReadRPNCellRange3D(AStream: TStream; + var ARPNItem: PRPNItem): Boolean; var - Len: Byte; - s: AnsiString; - sheetName: String; + sheetIndex: SmallInt; + sheetIndex1, sheetIndex2: Word; + r1, c1, r2, c2: Cardinal; + flags: TsRelFlags; begin - { Absolute stream position of the BOF record of the sheet represented - by this record } - // Just assume that they are in order - AStream.ReadDWord(); + Result := true; - { Visibility } - AStream.ReadByte(); + // 1-based index to EXTERNSHEET record containing name of first referenced worksheet + // 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 } - AStream.ReadByte(); + // Next 8 bytes not used. + AStream.ReadDWord; + AStream.ReadDWord; - { Sheet name: Byte string, 8-bit length } - Len := AStream.ReadByte(); + // Zero-based index to first and last referenced sheet (in case of internal ref) + sheetIndex1 := WordLEToN(AStream.ReadWord); + sheetIndex2 := WordLEToN(AStream.ReadWord); - SetLength(s, Len); - AStream.ReadBuffer(s[1], Len*SizeOf(AnsiChar)); - sheetName := ConvertEncoding(s, FCodePage, EncodingUTF8); - FWorksheetNames.Add(sheetName); + // Cell range coordinates + ReadRPNCellRangeAddress(AStream, r1, c1, r2, c2, flags); + if r2 = $FFFF then r2 := Cardinal(-1); + if c2 = $FF then c2 := Cardinal(-1); + ARPNItem := RPNCellRange3D(sheetIndex1, r1, c1, sheetIndex2, r2, c2, flags, ARPNItem); end; procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream); diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 23a6a0ae4..796282683 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -56,7 +56,7 @@ interface uses Classes, SysUtils, fpcanvas, DateUtils, contnrs, lazutf8, - fpstypes, fpspreadsheet, xlscommon, + fpstypes, fpspreadsheet, fpsrpn, xlscommon, {$ifdef USE_NEW_OLE} fpolebasic, {$else} @@ -66,6 +66,12 @@ uses type + TBIFF8ExternSheet = packed record + ExternBookIndex: Word; + FirstSheetIndex: Word; + LastSheetIndex: Word; + end; + { TsSpreadBIFF8Reader } TsSpreadBIFF8Reader = class(TsSpreadBIFFReader) private @@ -75,22 +81,26 @@ type FCommentPending: Boolean; FCommentID: Integer; FCommentLen: Integer; - procedure ReadBoundsheet(AStream: TStream); + FBiff8ExternSheets: array of TBiff8ExternSheet; function ReadString(const AStream: TStream; const ALength: Word; out ARichTextParams: TsRichTextParams): String; function ReadUnformattedWideString(const AStream: TStream; const ALength: Word): WideString; function ReadWideString(const AStream: TStream; const ALength: Word; 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 procedure PopulatePalette; override; + procedure ReadBOUNDSHEET(AStream: TStream); procedure ReadCONTINUE(const AStream: TStream); + procedure ReadDEFINEDNAME(const AStream: TStream); + procedure ReadEXTERNSHEET(const AStream: TStream); procedure ReadFONT(const AStream: TStream); procedure ReadFORMAT(AStream: TStream); override; procedure ReadHeaderFooter(AStream: TStream; AIsHeader: Boolean); override; - procedure ReadHyperLink(AStream: TStream); - procedure ReadHyperlinkToolTip(AStream: TStream); + procedure ReadHyperLink(const AStream: TStream); + procedure ReadHyperlinkToolTip(const AStream: TStream); procedure ReadLABEL(AStream: TStream); override; procedure ReadLabelSST(const AStream: TStream); procedure ReadMergedCells(const AStream: TStream); @@ -103,6 +113,7 @@ type out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); override; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override; + function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; override; procedure ReadRPNCellRangeOffset(AStream: TStream; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out AFlags: TsRelFlags); override; @@ -443,13 +454,14 @@ type Text: String; end; - { TsSpreadBIFF8Reader } destructor TsSpreadBIFF8Reader.Destroy; var j: Integer; begin + SetLength(FBiff8ExternSheets, 0); + if Assigned(FSharedStringTable) then begin for j := FSharedStringTable.Count-1 downto 0 do @@ -777,16 +789,18 @@ begin if RecordType <> INT_EXCEL_ID_CONTINUE then begin case RecordType of - INT_EXCEL_ID_BOF : ; - INT_EXCEL_ID_BOUNDSHEET: ReadBoundSheet(AStream); - INT_EXCEL_ID_EOF : SectionEOF := True; - INT_EXCEL_ID_SST : ReadSST(AStream); - INT_EXCEL_ID_CODEPAGE : ReadCodepage(AStream); - INT_EXCEL_ID_FONT : ReadFont(AStream); - INT_EXCEL_ID_FORMAT : ReadFormat(AStream); - INT_EXCEL_ID_XF : ReadXF(AStream); - INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream); - INT_EXCEL_ID_PALETTE : ReadPalette(AStream); + INT_EXCEL_ID_BOF : ; + INT_EXCEL_ID_BOUNDSHEET : ReadBoundSheet(AStream); + INT_EXCEL_ID_DEFINEDNAME : ReadDEFINEDNAME(AStream); + INT_EXCEL_ID_EOF : SectionEOF := True; + INT_EXCEL_ID_EXTERNSHEET : ReadEXTERNSHEET(AStream); + INT_EXCEL_ID_SST : ReadSST(AStream); + INT_EXCEL_ID_CODEPAGE : ReadCodepage(AStream); + INT_EXCEL_ID_FONT : ReadFont(AStream); + INT_EXCEL_ID_FORMAT : ReadFormat(AStream); + INT_EXCEL_ID_XF : ReadXF(AStream); + INT_EXCEL_ID_DATEMODE : ReadDateMode(AStream); + INT_EXCEL_ID_PALETTE : ReadPalette(AStream); else // nothing end; @@ -809,7 +823,7 @@ var RecordType: Word; CurStreamPos: Int64; begin - FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurrentWorksheet], true); + FWorksheet := FWorkbook.AddWorksheet(FWorksheetNames[FCurSheetIndex], true); while (not SectionEOF) do begin @@ -829,6 +843,7 @@ begin INT_EXCEL_ID_COLINFO : ReadColInfo(AStream); INT_EXCEL_ID_CONTINUE : ReadCONTINUE(AStream); INT_EXCEL_ID_DEFCOLWIDTH : ReadDefColWidth(AStream); + INT_EXCEL_ID_DEFINEDNAME : ReadDefinedName(AStream); INT_EXCEL_ID_EOF : SectionEOF := True; INT_EXCEL_ID_FOOTER : ReadHeaderFooter(AStream, false); INT_EXCEL_ID_FORMULA : ReadFormula(AStream); @@ -944,7 +959,6 @@ begin end; end; (* - const AStrea procedure TsSpreadBIFF8Reader.ReadFromStream(AStream: TStream); var BIFF8EOF: Boolean; @@ -1179,6 +1193,26 @@ begin if (c2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); 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 and a reference cell. Overriding the implementation in xlscommon. } @@ -1628,6 +1662,84 @@ begin FCellFormatList.Add(fmt); 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. } procedure TsSpreadBIFF8Reader.ReadFONT(const AStream: TStream); var @@ -1761,7 +1873,7 @@ end; {@@ ---------------------------------------------------------------------------- Reads a HYPERLINK record -------------------------------------------------------------------------------} -procedure TsSpreadBIFF8Reader.ReadHyperlink(AStream: TStream); +procedure TsSpreadBIFF8Reader.ReadHyperlink(const AStream: TStream); var row, col, row1, col1, row2, col2: word; guid: TGUID; @@ -1902,7 +2014,7 @@ end; {@@ ---------------------------------------------------------------------------- Reads a HYPERLINK TOOLTIP record -------------------------------------------------------------------------------} -procedure TsSpreadBIFF8Reader.ReadHyperlinkToolTip(AStream: TStream); +procedure TsSpreadBIFF8Reader.ReadHyperlinkToolTip(const AStream: TStream); var txt: String; widestr: widestring; diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index e7256746c..bf46da6c3 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -12,7 +12,7 @@ interface uses Classes, SysUtils, DateUtils, lconvencoding, fpsTypes, fpSpreadsheet, fpsUtils, fpsNumFormatParser, fpsPalette, - fpsReaderWriter; + fpsReaderWriter, fpsrpn; const { RECORD IDs which didn't change across versions 2-8 } @@ -349,6 +349,21 @@ type RecordSize: Word; 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 = class(TsCustomSpreadReader) protected @@ -360,9 +375,11 @@ type FIncompleteNoteLength: Word; FFirstNumFormatIndexInFile: Integer; FPalette: TsPalette; + FDefinedNames: TFPList; FWorksheetNames: TStrings; - FCurrentWorksheet: Integer; + FCurSheetIndex: Integer; FActivePane: Integer; + FExternSheets: TStrings; procedure AddBuiltinNumFormats; override; procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; @@ -375,7 +392,11 @@ type // Returns the numberformat for a given XF record procedure ExtractNumberFormat(AXFIndex: WORD; 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 FixDefinedNames(AWorksheet: TsWorksheet); function FixFontIndex(AFontIndex: Integer): Integer; // Tries to find if a number cell is actually a date/datetime/time cell and retrieves the value function IsDateTime(Number: Double; ANumberFormat: TsNumberFormat; @@ -397,6 +418,8 @@ type procedure ReadDefColWidth(AStream: TStream); // Read the default row height procedure ReadDefRowHeight(AStream: TStream); + // Read an EXTERNSHEET record (defined names) + procedure ReadExternSheet(AStream: TStream); // Read FORMAT record (cell formatting) procedure ReadFormat(AStream: TStream); virtual; // Read FORMULA record @@ -431,13 +454,17 @@ type out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); virtual; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual; + function ReadRPNCellRange3D(AStream: TStream; var ARPNItem: PRPNItem): Boolean; virtual; procedure ReadRPNCellRangeOffset(AStream: TStream; out ARow1Offset, ACol1Offset, ARow2Offset, ACol2Offset: Integer; out AFlags: TsRelFlags); virtual; function ReadRPNFunc(AStream: TStream): Word; virtual; procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); virtual; 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; procedure ReadSELECTION(AStream: TStream); procedure ReadSharedFormula(AStream: TStream); @@ -599,7 +626,8 @@ implementation uses AVL_Tree, Math, Variants, {%H-}fpspatches, fpsStrings, fpsClasses, fpsNumFormat, xlsConst, - fpsrpn, fpsExprParser; + //fpsrpn, + fpsExprParser; const { Helper table for rpn formulas: @@ -610,6 +638,8 @@ const INT_EXCEL_TOKEN_TREFR, {fekCellRef} INT_EXCEL_TOKEN_TAREA_R, {fekCellRange} 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_TINT, {fekInteger} INT_EXCEL_TOKEN_TSTR, {fekString} @@ -633,6 +663,7 @@ const INT_EXCEL_TOKEN_TLT, {fekLess <} INT_EXCEL_TOKEN_TLE, {fekLessEqual, <=} INT_EXCEL_TOKEN_TNE, {fekNotEqual, <>} + INT_EXCEL_TOKEN_TLIST, {List operator (",")} INT_EXCEL_TOKEN_TPAREN, {Operator in parenthesis} Word(-1) {fekFunc} ); @@ -820,6 +851,76 @@ begin 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 } {------------------------------------------------------------------------------} @@ -834,6 +935,9 @@ begin FCellFormatList := TsCellFormatList.Create(true); // 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 FDateMode := dm1900; @@ -850,8 +954,15 @@ end; Destructor of the reader class -------------------------------------------------------------------------------} destructor TsSpreadBIFFReader.Destroy; +var + j: Integer; begin + for j:=0 to FDefinedNames.Count-1 do TObject(FDefinedNames[j]).Free; + FDefinedNames.Free; + + FExternSheets.Free; FPalette.Free; + inherited Destroy; end; @@ -982,6 +1093,64 @@ begin 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 palette. Therefore, when reading the font, we cannot determine its rgb color. @@ -1029,6 +1198,21 @@ begin 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 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; 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 To be overridden by descendants. @@ -1957,6 +2165,13 @@ begin if (r2 and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow2); 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 and a reference cell. @@ -2024,6 +2239,7 @@ end; 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. -------------------------------------------------------------------------------} +(* function TsSpreadBIFFReader.ReadRPNTokenArray(AStream: TStream; ACell: PCell; ASharedFormulaBase: PCell = nil): Boolean; var @@ -2179,6 +2395,174 @@ begin Result := true; 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. @@ -2385,19 +2769,9 @@ end; procedure TsSpreadBIFFReader.InternalReadFromStream(AStream: TStream); var BIFFEOF: Boolean; + i: Integer; + sheet: TsWorksheet; 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 if AStream.Size = 0 then raise Exception.Create('[TsSpreadBIFFReader.InternalReadFromStream] Reading of OLE document failed'); @@ -2408,7 +2782,7 @@ begin {Initializations } FWorksheetNames := TStringList.Create; try - FCurrentWorksheet := 0; + FCurSheetIndex := 0; BIFFEOF := false; { Read workbook globals } @@ -2428,12 +2802,21 @@ begin BIFFEOF := true; // Final preparations - inc(FCurrentWorksheet); + inc(FCurSheetIndex); // It can happen in files written by Office97 that the OLE directory is // at the end of the file. - if FCurrentWorksheet = FWorksheetNames.Count then + if FCurSheetIndex = FWorksheetNames.Count then BIFFEOF := true; 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 { Finalization } FreeAndNil(FWorksheetNames); diff --git a/components/fpspreadsheet/xlsconst.pas b/components/fpspreadsheet/xlsconst.pas index 39952fd56..aadaae79a 100644 --- a/components/fpspreadsheet/xlsconst.pas +++ b/components/fpspreadsheet/xlsconst.pas @@ -55,6 +55,12 @@ const INT_EXCEL_TOKEN_TAREAN_R = $2D; INT_EXCEL_TOKEN_TAREAN_V = $4D; 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 } // _R: reference; _V: value; _A: array