From cbc6beae9a374a8c26f599b81f0e13f43e86d4c9 Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Thu, 4 Sep 2014 15:27:28 +0000 Subject: [PATCH] fpspreadsheet: Writing of shared formulas to biff5 and biff8 is working again now. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3525 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpspreadsheet/fpspreadsheet.pas | 41 +---------- components/fpspreadsheet/xlsbiff2.pas | 35 ++------- components/fpspreadsheet/xlscommon.pas | 63 +++++++++------- components/fpspreadsheet/xlsconst.pas | 85 +++++++++++----------- 4 files changed, 90 insertions(+), 134 deletions(-) diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index c11f9f845..165f5e5e2 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -1097,7 +1097,6 @@ type procedure FixCellColors(ACell: PCell); function FixColor(AColor: TsColor): TsColor; virtual; procedure FixFormat(ACell: PCell); virtual; - procedure FixRelativeReferences(ACell: PCell; var AElement: TsFormulaElement); virtual; procedure GetSheetDimensions(AWorksheet: TsWorksheet; out AFirstRow, ALastRow, AFirstCol, ALastCol: Cardinal); virtual; procedure ListAllFormattingStylesCallback(ACell: PCell; AStream: TStream); @@ -1749,6 +1748,8 @@ end; Helper function which constructs an rpn formula from the cell's string formula. This is needed, for example, when writing a formula to xls biff file format. + If the cell belongs to a shared formula the formula is taken from the + shared formula base cell, cell references used are adapted accordingly. } function TsWorksheet.BuildRPNFormula(ACell: PCell): TsRPNFormula; var @@ -6666,44 +6667,6 @@ begin // to be overridden end; -{@@ - Adjusts relative references in the formula element to the position of cell - and the shared formula base. } -procedure TsCustomSpreadWriter.FixRelativeReferences(ACell: PCell; - var AElement: TsFormulaElement); -var - rowOffset: Integer; - colOffset: Integer; -begin - if (ACell = nil) or (ACell^.SharedFormulaBase = nil) then - exit; - - case AElement.ElementKind of - fekCell: - begin - rowOffset := AElement.Row - ACell^.SharedFormulaBase^.Row; - colOffset := AElement.Col - ACell^.SharedFormulaBase^.Col; - if (rfRelRow in AElement.RelFlags) then - AElement.Row := Integer(ACell^.Row) + rowOffset; - if (rfRelCol in AElement.RelFlags) then - AElement.Col := Integer(ACell^.Col) + colOffset; - end; - fekCellRange: - begin - rowOffset := AElement.Row - ACell^.SharedFormulaBase^.Row; - colOffset := AElement.Col - ACell^.SharedFormulaBase^.Col; - if (rfRelRow in AElement.RelFlags) then - AElement.Row := Integer(ACell^.Row) + rowOffset; - if (rfRelCol in AElement.RelFlags) then - AElement.Col := Integer(ACell^.Col) + colOffset; - if (rfRelRow2 in AElement.RelFlags) then - AElement.Row2 := Integer(ACell^.Row) + rowOffset; - if (rfRelCol2 in AElement.RelFlags) then - AElement.Col2 := Integer(ACell^.Col) + colOffset; - end; - end; -end; - {@@ Determines the size of the worksheet to be written. VirtualMode is respected. Is called when the writer needs the size for output. Column and row count diff --git a/components/fpspreadsheet/xlsbiff2.pas b/components/fpspreadsheet/xlsbiff2.pas index c354d6357..abc887055 100755 --- a/components/fpspreadsheet/xlsbiff2.pas +++ b/components/fpspreadsheet/xlsbiff2.pas @@ -112,7 +112,6 @@ type procedure WriteXFRecords(AStream: TStream); protected procedure CreateNumFormatList; override; - procedure FixRelativeReferences(ACell: PCell; var AElement: TsFormulaElement); override; procedure ListAllNumFormats; override; procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); override; procedure WriteFormat(AStream: TStream; AFormatData: TsNumFormatData; @@ -993,26 +992,6 @@ begin end; end; -{ Since BIFF2 does not support shared formulas it cannot handle the fekCellOffset - token. It is replaced by a fekCellValue token and correct relative references. } -procedure TsSpreadBIFF2Writer.FixRelativeReferences(ACell: PCell; - var AElement: TsFormulaElement); -begin - inherited FixRelativeReferences(ACell, AElement); - - if (ACell = nil) or (ACell^.SharedFormulaBase = nil) then - exit; - - if AElement.ElementKind = fekCellOffset then - begin - AElement.ElementKind := fekCell; - if (rfRelRow in AElement.RelFlags) then - AElement.Row := ACell^.Row + Integer(AElement.Row); // AElement.Row means here: "RowOffsset" - if (rfRelCol in AElement.RelFlags) then - AElement.Col := ACell^.Col + Integer(AElement.Col); // AElement.Col means here: "ColOffsset" - end; -end; - { Determines the cell attributes needed for writing a cell content record, such as WriteLabel, WriteNumber, etc. The cell attributes contain, in bit masks, xf record index, font index, borders, etc.} @@ -1627,7 +1606,7 @@ begin if ACell^.SharedFormulaBase <> nil then WriteRPNSharedFormulaLink(AStream, ACell, RPNLength) else - WriteRPNTokenArray(AStream, AFormula, true, RPNLength); + WriteRPNTokenArray(AStream, ACell, AFormula, false, RPNLength); { Finally write sizes after we know them } FinalPos := AStream.Position; @@ -1660,14 +1639,14 @@ var formula: TsRPNFormula; begin // Create RPN formula from the shared formula base's string formula - formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase); - - // Adapt relative cell references - for i:=0 to Length(formula)-1 do - FixRelativeReferences(ACell, formula[i]); + formula := FWorksheet.BuildRPNFormula(ACell); + // Don't use ACell^.SharedFormulaBase here because this lookup is made + // by the worksheet automatically. // Write adapted copy of shared formula to stream. - WriteRPNTokenArray(AStream, formula, true, RPNLength); + WriteRPNTokenArray(AStream, ACell, formula, false, RPNLength); + // false --> "do not convert cess addresses to relative offsets", because + // biff2 does not support shared formulas! // Clean up SetLength(formula, 0); diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index efd2f9951..2cbf99840 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -360,8 +360,8 @@ type procedure WriteRPNResult(AStream: TStream; ACell: PCell); procedure WriteRPNSharedFormulaLink(AStream: TStream; ACell: PCell; var RPNLength: Word); virtual; - procedure WriteRPNTokenArray(AStream: TStream; const AFormula: TsRPNFormula; - WriteTokenArraySize: Boolean; var RPNLength: Word); + procedure WriteRPNTokenArray(AStream: TStream; ACell: PCell; + const AFormula: TsRPNFormula; UseRelAddr: Boolean; var RPNLength: Word); procedure WriteRPNTokenArraySize(AStream: TStream; ASize: Word); virtual; // Writes out a SELECTION record procedure WriteSelection(AStream: TStream; ASheet: TsWorksheet; APane: Byte); @@ -2368,7 +2368,7 @@ begin if ACell^.SharedFormulaBase <> nil then WriteRPNSharedFormulaLink(AStream, ACell, RPNLength) else - WriteRPNTokenArray(AStream, AFormula, true, RPNLength); + WriteRPNTokenArray(AStream, ACell, AFormula, false, RPNLength); { Write sizes in the end, after we known them } FinalPos := AStream.Position; @@ -2479,10 +2479,12 @@ end; { Writes the token array of the given RPN formula and returns its size } procedure TsSpreadBIFFWriter.WriteRPNTokenArray(AStream: TStream; - const AFormula: TsRPNFormula; WriteTokenArraySize: Boolean; var RPNLength: Word); + ACell: PCell; const AFormula: TsRPNFormula; UseRelAddr: boolean; + var RPNLength: Word); var i: Integer; n: Word; + dr, dc: Integer; TokenArraySizePos: Int64; FinalPos: Int64; exprDef: TsExprIdentifierDef; @@ -2491,12 +2493,10 @@ var begin RPNLength := 0; - if WriteTokenArraySize then begin - { The size of the token array is written later, because it's necessary to - calculate it first, and this is done at the same time it is written } - TokenArraySizePos := AStream.Position; - WriteRPNTokenArraySize(AStream, 0); - end; + { The size of the token array is written later, because it's necessary to + calculate it first, and this is done at the same time it is written } + TokenArraySizePos := AStream.Position; + WriteRPNTokenArraySize(AStream, 0); { Formula data (RPN token array) } for i := 0 to Length(AFormula) - 1 do begin @@ -2513,6 +2513,18 @@ begin primaryExcelCode := TokenIDs[AFormula[i].ElementKind]; secondaryExcelCode := 0; end; + + if UseRelAddr then + case primaryExcelCode of + INT_EXCEL_TOKEN_TREFR : primaryExcelCode := INT_EXCEL_TOKEN_TREFN_R; + INT_EXCEL_TOKEN_TREFV : primaryExcelCode := INT_EXCEL_TOKEN_TREFN_V; + INT_EXCEL_TOKEN_TREFA : primaryExcelCode := INT_EXCEL_TOKEN_TREFN_A; + + INT_EXCEL_TOKEN_TAREA_R: primaryExcelCode := INT_EXCEL_TOKEN_TAREAN_R; + INT_EXCEL_TOKEN_TAREA_V: primaryExcelCode := INT_EXCEL_TOKEN_TAREAN_V; + INT_EXCEL_TOKEN_TAREA_A: primaryExcelCode := INT_EXCEL_TOKEN_TAREAN_A; + end; + AStream.WriteByte(primaryExcelCode); inc(RPNLength); @@ -2520,7 +2532,6 @@ begin case primaryExcelCode of { Operand Tokens } INT_EXCEL_TOKEN_TREFR, INT_EXCEL_TOKEN_TREFV, INT_EXCEL_TOKEN_TREFA: { fekCell } -// INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset} begin n := WriteRPNCellAddress( AStream, @@ -2545,11 +2556,13 @@ begin INT_EXCEL_TOKEN_TREFN_V, INT_EXCEL_TOKEN_TREFN_A: { fekCellOffset } begin - n := WriteRPNCellOffset( - AStream, - integer(AFormula[i].Row), integer(AFormula[i].Col), - AFormula[i].RelFlags - ); + if rfRelRow in AFormula[i].RelFlags + then dr := integer(AFormula[i].Row) - ACell^.Row + else dr := integer(AFormula[i].Row) - ACell^.SharedFormulaBase^.Row; + if rfRelCol in AFormula[i].RelFlags + then dc := integer(AFormula[i].Col) - ACell^.Col + else dc := integer(AFormula[i].Col) - ACell^.SharedFormulaBase^.Col; + n := WriteRPNCellOffset(AStream, dr, dc, AFormula[i].RelFlags); inc(RPNLength, n); end; @@ -2608,17 +2621,15 @@ begin end; // for // Now update the size of the token array. - if WriteTokenArraySize then begin - finalPos := AStream.Position; - AStream.Position := TokenArraySizePos; - WriteRPNTokenArraySize(AStream, RPNLength); - AStream.Position := finalPos; - end; + finalPos := AStream.Position; + AStream.Position := TokenArraySizePos; + WriteRPNTokenArraySize(AStream, RPNLength); + AStream.Position := finalPos; end; { Writes the size of the RPN token array. Called from WriteRPNFormula. Valid for BIFF3-BIFF8. Override in BIFF2. } -procedure TsSPREADBIFFWriter.WriteRPNTokenArraySize(AStream: TStream; ASize: Word); +procedure TsSpreadBIFFWriter.WriteRPNTokenArraySize(AStream: TStream; ASize: Word); begin AStream.WriteWord(WordToLE(ASize)); end; @@ -2850,11 +2861,11 @@ begin // Create an RPN formula from the shared formula base's string formula // and adjust relative references formula := FWorksheet.BuildRPNFormula(ACell^.SharedFormulaBase); - for i:=0 to Length(formula)-1 do +{ for i:=0 to Length(formula)-1 do FixRelativeReferences(ACell, formula[i]); - + } // Writes the rpn token array - WriteRPNTokenArray(AStream, formula, true, RPNLength); + WriteRPNTokenArray(AStream, ACell, formula, true, RPNLength); { Write record size at the end after we known it } finalPos := AStream.Position; diff --git a/components/fpspreadsheet/xlsconst.pas b/components/fpspreadsheet/xlsconst.pas index f722a5221..536daba60 100644 --- a/components/fpspreadsheet/xlsconst.pas +++ b/components/fpspreadsheet/xlsconst.pas @@ -9,60 +9,63 @@ const { Formula constants TokenID values } { Binary Operator Tokens 3.6} - INT_EXCEL_TOKEN_TADD = $03; - INT_EXCEL_TOKEN_TSUB = $04; - INT_EXCEL_TOKEN_TMUL = $05; - INT_EXCEL_TOKEN_TDIV = $06; - INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^ - INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation & - INT_EXCEL_TOKEN_TLT = $09; // Less than < - INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <= - INT_EXCEL_TOKEN_TEQ = $0B; // Equal = - INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >= - INT_EXCEL_TOKEN_TGT = $0D; // Greater than > - INT_EXCEL_TOKEN_TNE = $0E; // Not equal <> - INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection - INT_EXCEL_TOKEN_TLIST = $10; // Cell range list - INT_EXCEL_TOKEN_TRANGE = $11; // Cell range - INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus + - INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus + - INT_EXCEL_TOKEN_TPERCENT= $14; // Percent (%, divides operand by 100) - INT_EXCEL_TOKEN_TPAREN = $15; // Operator in parenthesis + INT_EXCEL_TOKEN_TADD = $03; + INT_EXCEL_TOKEN_TSUB = $04; + INT_EXCEL_TOKEN_TMUL = $05; + INT_EXCEL_TOKEN_TDIV = $06; + INT_EXCEL_TOKEN_TPOWER = $07; // Power Exponentiation ^ + INT_EXCEL_TOKEN_TCONCAT = $08; // Concatenation & + INT_EXCEL_TOKEN_TLT = $09; // Less than < + INT_EXCEL_TOKEN_TLE = $0A; // Less than or equal <= + INT_EXCEL_TOKEN_TEQ = $0B; // Equal = + INT_EXCEL_TOKEN_TGE = $0C; // Greater than or equal >= + INT_EXCEL_TOKEN_TGT = $0D; // Greater than > + INT_EXCEL_TOKEN_TNE = $0E; // Not equal <> + INT_EXCEL_TOKEN_TISECT = $0F; // Cell range intersection + INT_EXCEL_TOKEN_TLIST = $10; // Cell range list + INT_EXCEL_TOKEN_TRANGE = $11; // Cell range + INT_EXCEL_TOKEN_TUPLUS = $12; // Unary plus + + INT_EXCEL_TOKEN_TUMINUS = $13; // Unary minus + + INT_EXCEL_TOKEN_TPERCENT = $14; // Percent (%, divides operand by 100) + 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 { Operand Tokens } // _R: reference; _V: value; _A: array - INT_EXCEL_TOKEN_TREFR = $24; - INT_EXCEL_TOKEN_TREFV = $44; - INT_EXCEL_TOKEN_TREFA = $64; - INT_EXCEL_TOKEN_TAREA_R = $25; - INT_EXCEL_TOKEN_TAREA_V = $45; - INT_EXCEL_TOKEN_TAREA_A = $65; - INT_EXCEL_TOKEN_TREFN_R = $2C; - INT_EXCEL_TOKEN_TREFN_V = $4C; - INT_EXCEL_TOKEN_TREFN_A = $6C; + INT_EXCEL_TOKEN_TREFR = $24; + INT_EXCEL_TOKEN_TREFV = $44; + INT_EXCEL_TOKEN_TREFA = $64; + INT_EXCEL_TOKEN_TAREA_R = $25; + INT_EXCEL_TOKEN_TAREA_V = $45; + INT_EXCEL_TOKEN_TAREA_A = $65; + INT_EXCEL_TOKEN_TREFN_R = $2C; + INT_EXCEL_TOKEN_TREFN_V = $4C; + INT_EXCEL_TOKEN_TREFN_A = $6C; + INT_EXCEL_TOKEN_TAREAN_R = $2D; + INT_EXCEL_TOKEN_TAREAN_V = $4D; + INT_EXCEL_TOKEN_TAREAN_A = $6D; { Function Tokens } // _R: reference; _V: value; _A: array // Offset 0: token; offset 1: index to a built-in sheet function ( ➜ 3.111) - INT_EXCEL_TOKEN_FUNC_R = $21; - INT_EXCEL_TOKEN_FUNC_V = $41; - INT_EXCEL_TOKEN_FUNC_A = $61; + INT_EXCEL_TOKEN_FUNC_R = $21; + INT_EXCEL_TOKEN_FUNC_V = $41; + INT_EXCEL_TOKEN_FUNC_A = $61; //VAR: variable number of arguments: - INT_EXCEL_TOKEN_FUNCVAR_R = $22; - INT_EXCEL_TOKEN_FUNCVAR_V = $42; - INT_EXCEL_TOKEN_FUNCVAR_A = $62; + INT_EXCEL_TOKEN_FUNCVAR_R = $22; + INT_EXCEL_TOKEN_FUNCVAR_V = $42; + INT_EXCEL_TOKEN_FUNCVAR_A = $62; { Special tokens } - INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula + INT_EXCEL_TOKEN_TEXP = $01; // cell belongs to shared formula { Built-in/worksheet functions } INT_EXCEL_SHEET_FUNC_COUNT = 0;