From 1f55bbd8b73c6a1c3d4a8a53ac322addb51ec49d Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sun, 14 Feb 2016 14:01:51 +0000 Subject: [PATCH] fpspreadsheet. Support writing of print ranges and repeated header rows/columns by biff8 format git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4499 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpspreadsheet/xlsbiff8.pas | 213 ++++++++++++++++++++++++- components/fpspreadsheet/xlscommon.pas | 2 + 2 files changed, 214 insertions(+), 1 deletion(-) diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index 4d528fa42..0000e3fea 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -131,8 +131,13 @@ type function WriteBoundsheet(AStream: TStream; ASheetName: string): Int64; procedure WriteComment(AStream: TStream; ACell: PCell); override; procedure WriteComments(AStream: TStream; AWorksheet: TsWorksheet); + procedure WriteDefinedName(AStream: TStream; AWorksheet: TsWorksheet; + AIndexToREF: Word; AKind: Byte); + procedure WriteDefinedNames(AStream: TStream); procedure WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteEOF(AStream: TStream); + procedure WriteEXTERNBOOK(AStream: TStream); + procedure WriteEXTERNSHEET(AStream: TStream); procedure WriteFONT(AStream: TStream; AFont: TsFont); procedure WriteFonts(AStream: TStream); procedure WriteFORMAT(AStream: TStream; ANumFormatStr: String; @@ -273,6 +278,7 @@ const INT_EXCEL_ID_MSODRAWING = $00EC; // BIFF8 only INT_EXCEL_ID_SST = $00FC; // BIFF8 only INT_EXCEL_ID_LABELSST = $00FD; // BIFF8 only + INT_EXCEL_ID_EXTERNBOOK = $01AE; // BIFF8 only INT_EXCEL_ID_TXO = $01B6; // BIFF8 only INT_EXCEL_ID_HYPERLINK = $01B8; // BIFF8 only INT_EXCEL_ID_HLINKTOOLTIP = $0800; // BIFF8 only @@ -1982,6 +1988,10 @@ begin for i := 0 to Workbook.GetWorksheetCount - 1 do Boundsheets[i] := WriteBoundsheet(AStream, Workbook.GetWorksheetByIndex(i).Name); + WriteEXTERNBOOK(AStream); + WriteEXTERNSHEET(AStream); + WriteDefinedNames(AStream); + WriteEOF(AStream); { Write each worksheet } @@ -2224,6 +2234,150 @@ begin end; end; +{@@ ---------------------------------------------------------------------------- + Writes a DEFINEDNAME record. + Implements only the builtin defined names for print ranges and titles! +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteDefinedName(AStream: TStream; + AWorksheet: TsWorksheet; AIndexToREF: Word; AKind: Byte); + + procedure WriteRangeFormula(MemStream: TMemoryStream; ARange: TsCellRange; + AIndexToRef, ACounter: Word); + begin + { Token for tArea3dR } + MemStream.WriteByte($3B); + + { Index to REF entry in EXTERNSHEET record } + MemStream.WriteWord(WordToLE(AIndexToREF)); + + { First row index } + MemStream.WriteWord(WordToLE(ARange.Row1)); + + { Last row index } + MemStream.WriteWord(WordToLE(ARange.Row2)); + + { First column index } + MemStream.WriteWord(WordToLE(ARange.Col1)); + + { Last column index } + MemStream.WriteWord(WordToLE(ARange.Col2)); + + { Token for list if formula refers to more than 1 range } + if ACounter > 1 then + MemStream.WriteByte($10); + end; + +var + memstream: TMemoryStream; + rng: TsCellRange; + j: Integer; +begin + // Since this is a variable length record we begin by writing the formula + // to a memory stream + + memstream := TMemoryStream.Create; + try + case AKind of + $06: begin // Print range + for j := 0 to AWorksheet.NumPrintRanges-1 do + begin + rng := AWorksheet.GetPrintRange(j); + WriteRangeFormula(memstream, rng, AIndexToRef, j+1); + end; + end; + $07: begin + j := 1; + if AWorksheet.HasRepeatedPrintCols then + begin + rng.Col1 := AWorksheet.PageLayout.RepeatedCols.FirstIndex; + rng.Col2 := AWorksheet.PageLayout.RepeatedCols.LastIndex; + if rng.Col2 = UNASSIGNED_ROW_COL_INDEX then rng.Col2 := rng.Col1; + rng.Row1 := 0; + rng.Row2 := 65535; + WriteRangeFormula(memstream, rng, AIndexToRef, j); + inc(j); + end; + if AWorksheet.HasRepeatedPrintRows then + begin + rng.Row1 := AWorksheet.PageLayout.RepeatedRows.FirstIndex; + rng.Row2 := AWorksheet.PageLayout.RepeatedRows.LastIndex; + if rng.Row2 = UNASSIGNED_ROW_COL_INDEX then rng.Row2 := rng.Row1; + rng.Col1 := 0; + rng.Col2 := 255; + WriteRangeFormula(memstream, rng, AIndexToRef, j); + end; + end; + end; // case + + { BIFF record header } + WriteBIFFHeader(AStream, INT_EXCEL_ID_DEFINEDNAME, 16 + memstream.Size); + // NOTE: 16 only valid for internal names !!!! + + { Option flags: built-in defined names only } + AStream.WriteWord(WordToLE($0020)); + + { Keyboard shortcut (only for command macro names) } + AStream.WriteByte(0); + + { Length of name (character count). Always 1 for builtin names } + AStream.WriteByte(1); + + { Size of formula data } + AStream.WriteWord(WordToLE(memstream.Size)); + + { not used } + AStream.WriteWord(0); + + { Index to sheet (1-based) } + AStream.WriteWord(WordToLE(FWorkbook.GetWorksheetIndex(AWorksheet)+1)); + + { Length of menu text } + AStream.WriteByte(0); + + { Length of description text } + AStream.WriteByte(0); + + { Length of help topic text } + AStream.WriteByte(0); + + { Length of status bar text } + AStream.WriteByte(0); + + { Name } + AStream.WriteWord(WordToLE(AKind shl 8)); + + { Formula } + memstream.Position := 0; + AStream.CopyFrom(memstream, memstream.Size); + + finally + memstream.Free; + end; +end; + +procedure TsSpreadBIFF8Writer.WriteDefinedNames(AStream: TStream); +var + sheet: TsWorksheet; + i: Integer; + n: Word; +begin + n := 0; + for i:=0 to FWorkbook.GetWorksheetCount-1 do + begin + sheet := FWorkbook.GetWorksheetByIndex(i); + if (sheet.NumPrintRanges > 0) or + sheet.HasRepeatedPrintCols or sheet.HasRepeatedPrintRows then + begin + if sheet.NumPrintRanges > 0 then + WriteDefinedName(AStream, sheet, n, $06); + if sheet.HasRepeatedPrintCols or sheet.HasRepeatedPrintRows then + WriteDefinedName(AStream, sheet, n, $07); + inc(n); + end; + end; +end; + + {@@ ---------------------------------------------------------------------------- Writes an Excel 8 DIMENSIONS record @@ -2234,7 +2388,8 @@ end; See bug 18886: excel5 files are truncated when imported -------------------------------------------------------------------------------} -procedure TsSpreadBIFF8Writer.WriteDimensions(AStream: TStream; AWorksheet: TsWorksheet); +procedure TsSpreadBIFF8Writer.WriteDimensions(AStream: TStream; + AWorksheet: TsWorksheet); var firstRow, lastRow, firstCol, lastCol: Cardinal; rec: TBIFF8_DimensionsRecord; @@ -2265,6 +2420,62 @@ begin WriteBIFFHeader(AStream, INT_EXCEL_ID_EOF, 0); end; +{@@ ---------------------------------------------------------------------------- + Writes an EXTERNBOOK record needed for defined names and links. + NOTE: This writes only the case for "internal references" required for print + ranges and titles. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteEXTERNBOOK(AStream: TStream); +begin + { BIFF record header } + WriteBIFFHeader(AStream, INT_EXCEL_ID_EXTERNBOOK, 4); + + { Number of sheets in this workbook } + AStream.WriteWord(WordToLE(FWorkbook.GetWorksheetCount)); + + { Relict from BIFF5 } + AStream.WriteWord(WordToLE($0401)); +end; + +{@@ ---------------------------------------------------------------------------- + Writes an EXTERNSHEET record needed for defined names and links. + NOTE: This writes only what is required for print ranges and titles. +-------------------------------------------------------------------------------} +procedure TsSpreadBIFF8Writer.WriteEXTERNSHEET(AStream: TStream); +var + sheets: Array of Integer; + sheet: TsWorksheet; + i: Integer; + n: Word; +begin + n := 0; + SetLength(sheets, FWorkbook.GetWorksheetCount); + for i := 0 to FWorkbook.GetWorksheetCount-1 do begin + sheet := FWorkbook.GetWorksheetByIndex(i); + if (sheet.NumPrintRanges > 0) or + sheet.HasRepeatedPrintCols or sheet.HasRepeatedPrintRows then + begin + sheets[n] := i; + inc(n); + end; + end; + SetLength(sheets, n); + + { BIFF record header } + WriteBIFFHeader(AStream, INT_EXCEL_ID_EXTERNSHEET, 2 + 6*n); + + { Count of following REF structures } + AStream.WriteWord(WordToLE(n)); + + { REF record for each sheet } + for i := 0 to n-1 do + begin + AStream.WriteWord(0); // Index to EXTERNBOOK record, always 0 + AStream.WriteWord(WordToLE(sheets[i])); // Index to first sheet in EXTERNBOOK sheet list + AStream.WriteWord(WordToLE(sheets[i])); // Index to last sheet in EXTERNBOOK sheet list + end; +end; + {@@ ---------------------------------------------------------------------------- Writes an Excel 8 FONT record. The font data is passed as an instance of TsFont diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 4e0989cc5..91b472e9e 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -19,6 +19,7 @@ const INT_EXCEL_ID_EOF = $000A; INT_EXCEL_ID_HEADER = $0014; INT_EXCEL_ID_FOOTER = $0015; + INT_EXCEL_ID_EXTERNSHEET = $0017; INT_EXCEL_ID_NOTE = $001C; INT_EXCEL_ID_SELECTION = $001D; INT_EXCEL_ID_DATEMODE = $0022; @@ -36,6 +37,7 @@ const { RECORD IDs which did not change across versions 2, 5, 8} INT_EXCEL_ID_FORMULA = $0006; // BIFF3: $0206, BIFF4: $0406 + INT_EXCEL_ID_DEFINEDNAME = $0018; // BIFF3: $0218, BIFF4: $0218 INT_EXCEL_ID_FONT = $0031; // BIFF3-4: $0231 { RECORD IDs which did not change across version 3-8}