From ef4c18081d04a4ff471bcfc686b520b8aa2f63c7 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sat, 18 Feb 2017 16:54:50 +0000 Subject: [PATCH] fpspreadsheet: Replace prev commit by better fix for formulas with return a blank cell being lost. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5752 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../source/common/fpspreadsheet.pas | 11 +++- .../fpspreadsheet/source/common/xlsbiff2.pas | 11 ++++ .../fpspreadsheet/source/common/xlscommon.pas | 24 ++++++++ .../fpspreadsheet/source/common/xlsconst.pas | 29 ++++----- .../fpspreadsheet/source/common/xlsxooxml.pas | 60 ++++++++++++++----- 5 files changed, 102 insertions(+), 33 deletions(-) diff --git a/components/fpspreadsheet/source/common/fpspreadsheet.pas b/components/fpspreadsheet/source/common/fpspreadsheet.pas index 101e9c586..2fb984181 100644 --- a/components/fpspreadsheet/source/common/fpspreadsheet.pas +++ b/components/fpspreadsheet/source/common/fpspreadsheet.pas @@ -1231,7 +1231,9 @@ var p: Integer; link, txt: String; cell: PCell; + formula: String; begin + formula := ACell^.FormulaValue; ACell^.Flags := ACell^.Flags + [cfCalculating] - [cfCalculated]; parser := TsSpreadsheetParser.Create(self); @@ -1283,6 +1285,8 @@ begin parser.Free; end; + // Restore the formula. Could have been erased by WriteBlank or WriteText('') + ACell^.FormulaValue := formula; ACell^.Flags := ACell^.Flags + [cfCalculated] - [cfCalculating]; end; @@ -1315,7 +1319,6 @@ begin for cell in FCells do if HasFormula(cell) and (cell^.ContentType <> cctError) then CalcFormula(cell); - finally dec(FWorkbook.FCalculationLock); end; @@ -4823,14 +4826,16 @@ end; {@@ ---------------------------------------------------------------------------- Writes an empty cell - @param ACel Pointer to the cell + @param ACel Pointer to the cell Note: Empty cells are useful when, for example, a border line extends along a range of cells including empty cells. -------------------------------------------------------------------------------} procedure TsWorksheet.WriteBlank(ACell: PCell); begin if ACell <> nil then begin - //ACell^.FormulaValue := ''; + ACell^.FormulaValue := ''; + // NOTE: Erase the formula because if it would return a non-blank result + // this would be very confusing! if HasHyperlink(ACell) then WriteText(ACell, '') // '' will be replaced by the hyperlink target. else diff --git a/components/fpspreadsheet/source/common/xlsbiff2.pas b/components/fpspreadsheet/source/common/xlsbiff2.pas index e4c4cd84b..8209c08ca 100644 --- a/components/fpspreadsheet/source/common/xlsbiff2.pas +++ b/components/fpspreadsheet/source/common/xlsbiff2.pas @@ -65,6 +65,7 @@ type procedure ReadNumber(AStream: TStream); override; procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); override; procedure ReadRowInfo(AStream: TStream); override; + function ReadRPNAttr(AStream: TStream; AIdentifier: Byte): Boolean; override; function ReadRPNFunc(AStream: TStream): Word; override; procedure ReadRPNSharedFormulaBase(AStream: TStream; out ARow, ACol: Cardinal); override; function ReadRPNTokenArraySize(AStream: TStream): Word; override; @@ -931,6 +932,16 @@ begin lRow^.FormatIndex := XFToFormatIndex(xf); end; +function TsSpreadBIFF2Reader.ReadRPNAttr(AStream: TStream; AIdentifier: Byte): Boolean; +begin + Result := false; + case AIdentifier of + $01: AStream.ReadByte; // tAttrVolatile + else exit; // others not supported by fpspreadsheet --> Result = false + end; + Result := true; +end; + {@@ ---------------------------------------------------------------------------- Reads the identifier for an RPN function with fixed argument count from the stream. diff --git a/components/fpspreadsheet/source/common/xlscommon.pas b/components/fpspreadsheet/source/common/xlscommon.pas index 61fc38ffa..c47e74264 100644 --- a/components/fpspreadsheet/source/common/xlscommon.pas +++ b/components/fpspreadsheet/source/common/xlscommon.pas @@ -452,6 +452,7 @@ type procedure ReadRowColXF(AStream: TStream; out ARow, ACol: Cardinal; out AXF: Word); virtual; // Read row info procedure ReadRowInfo(AStream: TStream); virtual; + function ReadRPNAttr(AStream: TStream; AIdentifier: Byte): boolean; virtual; // Read the array of RPN tokens of a formula procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; out AFlags: TsRelFlags); virtual; @@ -2154,6 +2155,24 @@ begin FWorksheet.WriteRowInfo(rowrec.RowIndex, lRow); end; +{@@ ---------------------------------------------------------------------------- + Reads the special attribute of an RPN formula element. + Attributes are ignored by fpspreadsheet. + Structure is value for BIFF3 - BIFF8. Must be overridden for BIFF2. + Returns false if the structure is too complex for fps. +-------------------------------------------------------------------------------} +function TsSpreadBIFFReader.ReadRPNAttr(AStream: TStream; AIdentifier: Byte): Boolean; +var + w: Word; +begin + Result := false; + case AIdentifier of + $01: AStream.ReadWord; // tAttrVolatile token + else exit; // others not supported by fps --> Result = false + end; + Result := true; +end; + {@@ ---------------------------------------------------------------------------- Reads the cell address used in an RPN formula element. Evaluates the corresponding bits to distinguish between absolute and @@ -2511,6 +2530,11 @@ begin while (AStream.Position < p0 + ARPNTokenArraySize) and supported do begin token := AStream.ReadByte; case token of + INT_EXCEL_TOKEN_TATTR: + begin + b := AStream.ReadByte; + supported := ReadRPNAttr(AStream, b); + end; INT_EXCEL_TOKEN_TREFV: begin ReadRPNCellAddress(AStream, r, c, flags); diff --git a/components/fpspreadsheet/source/common/xlsconst.pas b/components/fpspreadsheet/source/common/xlsconst.pas index aadaae79a..f491ca7ee 100644 --- a/components/fpspreadsheet/source/common/xlsconst.pas +++ b/components/fpspreadsheet/source/common/xlsconst.pas @@ -34,12 +34,21 @@ const INT_EXCEL_TOKEN_TPAREN = $15; // Operator in parenthesis { Constant Operand Tokens, 3.8} - INT_EXCEL_TOKEN_TMISSARG = $16; //missing operand - INT_EXCEL_TOKEN_TSTR = $17; //string - INT_EXCEL_TOKEN_TERR = $1C; //error value - INT_EXCEL_TOKEN_TBOOL = $1D; //boolean - INT_EXCEL_TOKEN_TINT = $1E; //(unsigned) integer - INT_EXCEL_TOKEN_TNUM = $1F; //floating-point + INT_EXCEL_TOKEN_TMISSARG = $16; // missing operand + INT_EXCEL_TOKEN_TSTR = $17; // string + INT_EXCEL_TOKEN_TERR = $1C; // error value + INT_EXCEL_TOKEN_TBOOL = $1D; // boolean + INT_EXCEL_TOKEN_TINT = $1E; // (unsigned) integer + INT_EXCEL_TOKEN_TNUM = $1F; // floating-point + + { Control Tokens, Special Tokens, 3.10 } +// 01H tExp Matrix formula or shared formula +// 02H tTbl Multiple operation table +// 15H tParen Parentheses +// 18H tNlr Natural language reference (BIFF8) + INT_EXCEL_TOKEN_TATTR = $19; // tAttr Special attribute +// 1AH tSheet Start of external sheet reference (BIFF2-BIFF4) +// 1BH tEndSheet End of external sheet reference (BIFF2-BIFF4) { Operand Tokens } // _R: reference; _V: value; _A: array @@ -259,14 +268,6 @@ const INT_EXCEL_SHEET_FUNC_HYPERLINK = 359; // BIFF8 only - { Control Tokens, Special Tokens } -// 01H tExp Matrix formula or shared formula -// 02H tTbl Multiple operation table -// 15H tParen Parentheses -// 18H tNlr Natural language reference (BIFF8) - INT_EXCEL_TOKEN_TATTR = $19; // tAttr Special attribute -// 1AH tSheet Start of external sheet reference (BIFF2-BIFF4) -// 1BH tEndSheet End of external sheet reference (BIFF2-BIFF4) implementation diff --git a/components/fpspreadsheet/source/common/xlsxooxml.pas b/components/fpspreadsheet/source/common/xlsxooxml.pas index 1332c0c78..465bfdb97 100644 --- a/components/fpspreadsheet/source/common/xlsxooxml.pas +++ b/components/fpspreadsheet/source/common/xlsxooxml.pas @@ -308,6 +308,13 @@ type Tooltip: String; end; + TSharedFormulaData = class + Worksheet: TsWorksheet; + Row: Integer; + Col: Integer; + Formula: String; + end; + const PATTERN_TYPES: array [TsFillStyle] of string = ( 'none', // fsNoFill @@ -327,7 +334,7 @@ const 'lightDown', // fsThinStripeDiagDown 'darkTrellis', // fsHatchDiag 'lightTrellis', // fsHatchThinDiag - 'darkTellis', // fsHatchTickDiag + 'darkTellis', // fsHatchThickDiag 'lightGrid' // fsHatchThinHor ); @@ -359,19 +366,25 @@ destructor TsSpreadOOXMLReader.Destroy; var j: Integer; begin - for j := FFillList.Count-1 downto 0 do TObject(FFillList[j]).Free; + for j := FFillList.Count-1 downto 0 do + TObject(FFillList[j]).Free; FFillList.Free; - for j := FBorderList.Count-1 downto 0 do TObject(FBorderList[j]).Free; + for j := FBorderList.Count-1 downto 0 do + TObject(FBorderList[j]).Free; FBorderList.Free; - for j := FHyperlinkList.Count-1 downto 0 do TObject(FHyperlinkList[j]).Free; + for j := FHyperlinkList.Count-1 downto 0 do + TObject(FHyperlinkList[j]).Free; FHyperlinkList.Free; for j := FSharedStrings.Count-1 downto 0 do - if FSharedstrings.Objects[j] <> nil then FSharedStrings.Objects[j].Free; + FSharedStrings.Objects[j].Free; FSharedStrings.Free; - FSharedFormulaBaseList.Free; // Don't free items, they are worksheet cells + + for j := FSharedFormulaBaseList.Count-1 downto 0 do + TObject(FSharedFormulaBaseList[j]).Free; + FSharedFormulaBaseList.Free; // FCellFormatList, FNumFormatList and FFontList are destroyed by ancestor @@ -582,7 +595,9 @@ procedure TsSpreadOOXMLReader.ReadCell(ANode: TDOMNode; AWorksheet: TsWorksheet) var addr, s: String; rowIndex, colIndex: Cardinal; - cell, sharedformulabase: PCell; + cell: PCell; + lCell: TCell; + sharedFormulabase: TSharedFormulaData; datanode, tnode: TDOMNode; dataStr: String; formulaStr: String; @@ -655,18 +670,28 @@ begin s := GetAttrValue(datanode, 'ref'); if (s <> '') then // This defines the shared formula range begin - AWorksheet.WriteFormula(cell, FormulaStr); + AWorksheet.WriteFormula(cell, formulaStr); // We store the shared formula base in the SharedFormulaBaseList. // The list index is identical with the 'si' attribute of the node. - FSharedFormulaBaseList.Add(cell); + sharedformulabase := TSharedFormulaData.Create; + sharedformulabase.Worksheet := FWorksheet; + sharedformulabase.Row := rowindex; + sharedformulabase.Col := colindex; + sharedformulabase.Formula := formulaStr; + FSharedFormulaBaseList.Add(sharedformulabase); end else begin - // Get index into the SharedFormulaBaseList + // Get index into the SharedFormulaBaseList... s := GetAttrValue(datanode, 'si'); if s <> '' then begin - sharedformulabase := PCell(FSharedFormulaBaseList[StrToInt(s)]); - FWorksheet.CopyFormula(sharedformulabase, rowindex, colindex); + sharedformulabase := TSharedFormulaData(FSharedFormulaBaseList[StrToInt(s)]); + // ... and copy shared formula to destination cell + InitCell(sharedformulabase.Row, sharedformulabase.Col, lCell); + lCell.Formulavalue := sharedformulabase.Formula; + lCell.Worksheet := sharedformulabase.Worksheet; + FWorksheet.CopyFormula(@lCell, cell); + cell^.ContentType := cctFormula; end; end; end @@ -681,8 +706,9 @@ begin s := GetAttrValue(ANode, 't'); // "t" = data type if (s = '') and (dataStr = '') then begin + formulaStr := cell^.FormulaValue; AWorksheet.WriteBlank(cell); // this erases the formula!!! - if formulaStr <> '' then cell^.FormulaValue := formulaStr; + cell^.FormulaValue := formulaStr; end else if (s = '') or (s = 'n') then begin // Number or date/time, depending on format @@ -713,10 +739,12 @@ begin ms.ReadBuffer(cell^.RichTextParams[0], n*SizeOf(TsRichTextParam)); end; end else - if (s = 'str') or (s = 'inlineStr') then + if (s = 'str') or (s = 'inlineStr') then begin // literal string - AWorksheet.WriteText(cell, datastr) - else + formulaStr := cell^.FormulaValue; + AWorksheet.WriteText(cell, datastr); + cell^.FormulaValue := formulaStr; + end else if s = 'b' then // boolean AWorksheet.WriteBoolValue(cell, dataStr='1')