From 72ee090ef3d01d6dcd0ce7192c6ce2d171031e4f Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Sat, 16 Aug 2014 23:01:32 +0000 Subject: [PATCH] fpspreadsheet: Fix absolute/relative cell reference issues when reading shared formulas. Update BIFFExplorer for shared formulas. Implement reading of shared formulas for biff5 (nothing to be done for biff2 which does not support shared formulas). git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3490 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/fpspreadsheet/fpspreadsheet.pas | 23 ++- .../reference/BIFFExplorer/bebiffgrid.pas | 148 ++++++++++++------ components/fpspreadsheet/xlsbiff5.pas | 31 ++-- components/fpspreadsheet/xlsbiff8.pas | 13 +- components/fpspreadsheet/xlscommon.pas | 27 ++-- 5 files changed, 161 insertions(+), 81 deletions(-) diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index e8baf7ba0..1bb64b79b 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -1146,7 +1146,8 @@ type ANext: PRPNItem): PRPNItem; overload; function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; - function RPNCellOffset(ARowOffset, AColOffset: Integer; ANext: PRPNItem): PRPNItem; + function RPNCellOffset(ARowOffset, AColOffset: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem; function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNMissingArg(ANext: PRPNItem): PRPNItem; @@ -2707,6 +2708,7 @@ var ptr: Pointer; fek: TFEKind; formula: TsRPNFormula; + r,c: Cardinal; begin Result := ''; if ACell = nil then @@ -2721,6 +2723,9 @@ begin else formula := ACell^.SharedFormulaBase^.RPNFormulaValue; + if Length(formula) = 0 then + exit; + // We store the cell values and operation codes in a stringlist which serves // as kind of stack. Therefore, we do not destroy the original formula array. // We reverse the order of the items because in the next step stringlist @@ -2744,7 +2749,15 @@ begin fekCellRange: L.AddObject(GetCellRangeString(elem.Row, elem.Col, elem.Row2, elem.Col2, elem.RelFlags), ptr); fekCellOffset: - L.AddObject(GetCellString(ACell^.Row + SmallInt(elem.Row), ACell^.Col + SmallInt(elem.Col)), ptr); + begin + if rfRelRow in elem.RelFlags + then r := ACell^.Row + SmallInt(elem.Row) + else r := elem.Row; + if rfRelCol in elem.RelFlags + then c := ACell^.Col + SmallInt(elem.Col) + else c := elem.Col; + L.AddObject(GetCellString(r, c, elem.RelFlags), ptr); + end // Operations: else L.AddObject(FEProps[elem.ElementKind].Symbol, ptr); @@ -7195,15 +7208,17 @@ end; @param ARowOffset Offset between current row and the row of a reference cell @param AColOffset Offset between current column and the column of a reference cell + @param AFlags Flags specifying absolute or relative cell addresses @param ANext Pointer to the next RPN item in the list } -function RPNCellOffset(ARowOffset, AColOffset: Integer; ANext: PRPNItem): PRPNItem; +function RPNCellOffset(ARowOffset, AColOffset: Integer; AFlags: TsRelFlags; + ANext: PRPNItem): PRPNItem; begin Result := NewRPNItem; Result^.FE.ElementKind := fekCellOffset; Result^.FE.Row := ARowOffset; Result^.FE.Col := AColOffset; - Result^.FE.RelFlags := [rfRelRow, rfRelCol]; + Result^.FE.RelFlags := AFlags; Result^.Next := ANext; end; diff --git a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas index 368358f7f..388c04cae 100644 --- a/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas +++ b/components/fpspreadsheet/reference/BIFFExplorer/bebiffgrid.pas @@ -702,6 +702,8 @@ end; procedure TBIFFGrid.ShowCellAddress; +{ Note: The bitmask assignment to relative column/row is reversed in relation + to OpenOffice documentation in order to match with Excel files. } var numBytes: Word; b: Byte; @@ -726,15 +728,16 @@ begin FDetails.Add('ColIndex information:'#13); FDetails.Add(Format('Bits 0-13: ColIndex = %d (%s)', [c and $3FFF, ABS_REL[c and $8000 <> 0]])); if c and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if c and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; s := Format('%d ($%.4x)', [c, c]); ShowInRow(FCurrRow, FBufferIndex, numBytes, s, 'Column index'); - end else begin + end else + begin numbytes := 1; Move(FBuffer[FBufferIndex+2], b, numBytes); c := b; @@ -742,11 +745,11 @@ begin FDetails.Add('RowIndex information:'#13); FDetails.Add(Format('Bits 0-13: RowIndex = %d (%s)', [r and $3FFF, ABS_REL[r and $4000 <> 0]])); if r and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if r and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; //s := Format('$%.4x (%d, %s)', [r, r and $3FFF, ABS_REL[r and $4000 <> 0]]); s := Format('%d ($%.4x)', [r, r]); @@ -763,6 +766,8 @@ end; procedure TBIFFGrid.ShowCellAddressRange; +{ Note: The bitmask assignment to relative column/row is reversed in relation + to OpenOffice documentation in order to match with Excel files. } var numbytes: Word; b: Byte; @@ -799,11 +804,11 @@ begin FDetails.Add('ColIndex information:'#13); FDetails.Add(Format('Bits 0-13: ColIndex = %d (%s)', [c and $3FFF, ABS_REL[c and $8000 <> 0]])); if c and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if c and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; s := Format('%d ($%.4x)', [c, c]); ShowInRow(FCurrRow, FBufferIndex, numBytes, s, 'First column index'); @@ -813,11 +818,11 @@ begin FDetails.Add('ColIndex information:'#13); FDetails.Add(Format('Bits 0-13: ColIndex = %d (%s)', [c2 and $3FFF, ABS_REL[c2 and $8000 <> 0]])); if c2 and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if c2 and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; s := Format('%d ($%.4x)', [c2, c2]); ShowInRow(FCurrRow, FBufferIndex, numBytes, s, 'Last column index'); @@ -839,11 +844,11 @@ begin FDetails.Add('RowIndex information:'#13); FDetails.Add(Format('Bits 0-13: RowIndex = %d (%s)', [r and $3FFF, ABS_REL[r and $4000 <> 0]])); if r and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if r and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; s := Format('%d ($%.4x)', [r, r]); ShowInRow(FCurrRow, FBufferIndex, 2, s, 'First row index'); @@ -853,11 +858,11 @@ begin FDetails.Add('RowIndex information:'#13); FDetails.Add(Format('Bits 0-13: RowIndex = %d (%s)', [r2 and $3FFF, ABS_REL[r2 and $4000 <> 0]])); if r2 and $4000 = 0 - then FDetails.Add('Bit 14=0: absolute row index') - else FDetails.Add('Bit 14=1: relative row index'); + then FDetails.Add('Bit 14=0: absolute column index') + else FDetails.Add('Bit 14=1: relative column index'); if r2 and $8000 = 0 - then FDetails.Add('Bit 15=0: absolute column index') - else FDetails.Add('Bit 15=1: relative column index'); + then FDetails.Add('Bit 15=0: absolute row index') + else FDetails.Add('Bit 15=1: relative row index'); end; s := Format('%d ($%.4x)', [r2, r2]); ShowInRow(FCurrRow, FBufferIndex, 2, s, 'Last row index'); @@ -2373,6 +2378,7 @@ var dbl: Double; firstTokenBufIndex: Integer; token: Byte; + r, c: Word; begin // Tokens and parameters firstTokenBufIndex := FBufferIndex; @@ -2599,52 +2605,96 @@ begin if FFormat = sfExcel8 then begin numBytes := 2; - Move(FBuffer[FBufferIndex], w, numBytes); - w := WordLEToN(w); + Move(FBuffer[FBufferIndex], w, numBytes); // row + r := WordLEToN(w); + Move(FBuffer[FBufferIndex+2], w, numBytes); // column with flags + c := WordLEToN(w); + + { Note: The bitmask assignment to relative column/row is reversed in relation + to OpenOffice documentation in order to match with Excel files. } if Row = FCurrRow then begin FDetails.Add('Encoded cell address (row):'#13); - FDetails.Add('Relative row index: ' + IntToStr(Smallint(w))); + if c and $8000 = 0 then + begin + FDetails.Add('Row index is ABSOLUTE (see Encoded column index)'); + FDetails.Add('Absolute row index: ' + IntToStr(r)); + end else + begin + FDetails.Add('Row index is RELATIVE (see Encoded column index)'); + FDetails.Add('Relative row index: ' + IntToStr(Smallint(r))); + end; end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d', [SmallInt(w)]), - 'Relative row index'); + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.4x)', [r, r]), + 'Encoded row index'); - numBytes := 2; - Move(FBuffer[FBufferIndex], w, numbytes); - w := WordLEToN(w); - b := Lo(w); + // Bit mask $4000 --> column + // Bit mask $8000 --> row if Row = FCurrRow then begin FDetails.Add('Encoded cell address (column):'#13); - FDetails.Add('Relative column index: ' + IntToStr(ShortInt(b))); + if c and $4000 = 0 + then FDetails.Add('Bit 14=0: Column index is ABSOLUTE') + else FDetails.Add('Bit 14=1: Column index is RELATIVE'); + if c and $8000 = 0 + then FDetails.Add('Bit 15=0: Row index is ABSOLUTE') + else FDetails.Add('Bit 15=1: Row index is RELATIVE'); + FDetails.Add(''); + if c and $4000 = 0 + then FDetails.Add('Absolute column index: ' + IntToStr(Lo(c))) + else FDetails.Add('Relative column index: ' + IntToStr(ShortInt(Lo(c)))); end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d', [ShortInt(b)]), - 'Relative column index'); - end else + + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.4x)', [c, c]), + 'Encoded column index'); + end + else + // Excel5 (Excel2 does not support shared formulas) begin numBytes := 2; Move(FBuffer[FBufferIndex], w, numBytes); - w := WordLEToN(w) and $3FFF; + w := WordLEToN(w); + r := w and $3FFF; + b := FBuffer[FBufferIndex+2]; + c := b; + + // Bit mask $4000 --> column + // Bit mask $8000 --> row if Row = FCurrRow then begin FDetails.Add('Encoded cell address (row):'#13); - FDetails.Add('Relative row index: ' + IntToStr(Smallint(w))); + if w and $4000 = 0 + then FDetails.Add('Bit 14=0: Column index is ABSOLUTE') + else FDetails.Add('Bit 14=0: Column index is RELATIVE'); + if w and $8000 = 0 + then FDetails.Add('Bit 15=0: Row index is ABSOLUTE') + else FDetails.Add('Bit 15=1: Row index is RELATIVE'); + FDetails.Add(''); + if w and $8000 = 0 + then FDetails.Add('Absolute row index: ' + IntToStr(r)) + else FDetails.Add('Relative row index: ' + IntToStr(Smallint(r))); end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d', [SmallInt(w)]), - 'Relative row index'); + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.4x)', [w, w]), + 'Encoded row index'); - numBytes := 1; - b := FBuffer[FBufferIndex]; if Row = FCurrRow then begin FDetails.Add('Encoded cell address (column):'#13); - FDetails.Add('Relative column index: ' + IntToStr(ShortInt(b))); + if w and $4000 = 0 then begin + FDetails.Add('Column index is ABSOLUTE (see Encoded row index)'); + FDetails.Add('Absolute column index: ' + IntToStr(c)); + end else begin + FDetails.Add('Column index is RELATIVE (see Encoded row index)'); + FDetails.Add('Relative column index: ' + IntToStr(ShortInt(c))); + end; end; - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d', [ShortInt(b)]), - 'Relative column index'); + numBytes := 1; + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('%d ($%.2x)', [b, b]), + 'Encoded column index'); end; end; + else - ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.2x', [token]), - '(unknown token)'); + ShowInRow(FCurrRow, FBufferIndex, numBytes, Format('$%.2x', [token]), + '(unknown token)'); end; // case end; // while diff --git a/components/fpspreadsheet/xlsbiff5.pas b/components/fpspreadsheet/xlsbiff5.pas index 2def8afb6..92437eb68 100755 --- a/components/fpspreadsheet/xlsbiff5.pas +++ b/components/fpspreadsheet/xlsbiff5.pas @@ -1338,21 +1338,22 @@ begin case RecordType of - INT_EXCEL_ID_BLANK : ReadBlank(AStream); - INT_EXCEL_ID_MULBLANK: ReadMulBlank(AStream); - INT_EXCEL_ID_NUMBER : ReadNumber(AStream); - INT_EXCEL_ID_LABEL : ReadLabel(AStream); - INT_EXCEL_ID_RSTRING : ReadRichString(AStream); //(RSTRING) This record stores a formatted text cell (Rich-Text). In BIFF8 it is usually replaced by the LABELSST record. Excel still uses this record, if it copies formatted text cells to the clipboard. - INT_EXCEL_ID_RK : ReadRKValue(AStream); //(RK) This record represents a cell that contains an RK value (encoded integer or floating-point value). If a floating-point value cannot be encoded to an RK value, a NUMBER record will be written. This record replaces the record INTEGER written in BIFF2. - INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream); - INT_EXCEL_ID_COLINFO : ReadColInfo(AStream); - INT_EXCEL_ID_ROW : ReadRowInfo(AStream); - INT_EXCEL_ID_FORMULA : ReadFormula(AStream); - INT_EXCEL_ID_STRING : ReadStringRecord(AStream); - INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream); - INT_EXCEL_ID_PANE : ReadPane(AStream); - INT_EXCEL_ID_BOF : ; - INT_EXCEL_ID_EOF : SectionEOF := True; + INT_EXCEL_ID_BLANK : ReadBlank(AStream); + INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream); + INT_EXCEL_ID_NUMBER : ReadNumber(AStream); + INT_EXCEL_ID_LABEL : ReadLabel(AStream); + INT_EXCEL_ID_RSTRING : ReadRichString(AStream); //(RSTRING) This record stores a formatted text cell (Rich-Text). In BIFF8 it is usually replaced by the LABELSST record. Excel still uses this record, if it copies formatted text cells to the clipboard. + INT_EXCEL_ID_RK : ReadRKValue(AStream); //(RK) This record represents a cell that contains an RK value (encoded integer or floating-point value). If a floating-point value cannot be encoded to an RK value, a NUMBER record will be written. This record replaces the record INTEGER written in BIFF2. + INT_EXCEL_ID_MULRK : ReadMulRKValues(AStream); + INT_EXCEL_ID_COLINFO : ReadColInfo(AStream); + INT_EXCEL_ID_ROW : ReadRowInfo(AStream); + INT_EXCEL_ID_FORMULA : ReadFormula(AStream); + INT_EXCEL_ID_SHAREDFMLA: ReadSharedFormula(AStream); + INT_EXCEL_ID_STRING : ReadStringRecord(AStream); + INT_EXCEL_ID_WINDOW2 : ReadWindow2(AStream); + INT_EXCEL_ID_PANE : ReadPane(AStream); + INT_EXCEL_ID_BOF : ; + INT_EXCEL_ID_EOF : SectionEOF := True; {$IFDEF FPSPREADDEBUG} // Only write out if debugging else diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index f067245f2..ed7ce542a 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -88,7 +88,7 @@ type procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; out AFlags: TsRelFlags); override; procedure ReadRPNCellAddressOffset(AStream: TStream; - out ARowOffset, AColOffset: Integer); override; + out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); override; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); override; procedure ReadSST(const AStream: TStream); @@ -1710,18 +1710,25 @@ end; cell. Overriding the implementation in xlscommon. } procedure TsSpreadBIFF8Reader.ReadRPNCellAddressOffset(AStream: TStream; - out ARowOffset, AColOffset: Integer); + out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); var dr: SmallInt; dc: ShortInt; + c: Word; begin // 2 bytes for row offset dr := WordLEToN(AStream.ReadWord); ARowOffset := dr; // 2 bytes for column offset - dc := Lo(WordLEToN(AStream.ReadWord)); + c := WordLEToN(AStream.ReadWord); + dc := Lo(c); AColOffset := dc; + + // Extract info on absolute/relative addresses. + AFlags := []; + if (c and MASK_EXCEL_RELATIVE_COL <> 0) then Include(AFlags, rfRelCol); + if (c and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow); end; { Reads a cell range address used in an RPN formula element. diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 5b803c51f..11c909310 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -439,7 +439,7 @@ type procedure ReadRPNCellAddress(AStream: TStream; out ARow, ACol: Cardinal; out AFlags: TsRelFlags); virtual; procedure ReadRPNCellAddressOffset(AStream: TStream; - out ARowOffset, AColOffset: Integer); virtual; + out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); virtual; procedure ReadRPNCellRangeAddress(AStream: TStream; out ARow1, ACol1, ARow2, ACol2: Cardinal; out AFlags: TsRelFlags); virtual; function ReadRPNFunc(AStream: TStream): Word; virtual; @@ -1548,18 +1548,25 @@ end; cell. Implemented here for BIFF5. BIFF8 must be overridden. Not used by BIFF2. } procedure TsSpreadBIFFReader.ReadRPNCellAddressOffset(AStream: TStream; - out ARowOffset, AColOffset: Integer); + out ARowOffset, AColOffset: Integer; out AFlags: TsRelFlags); var - r: SmallInt; - c: ShortInt; + r: Word; + dr: SmallInt; + dc: ShortInt; begin // 2 bytes for row - r := WordLEToN(AStream.ReadWord) and $3FFF; - ARowOffset := r; + r := WordLEToN(AStream.ReadWord); + dr := SmallInt(r and $3FFF); + ARowOffset := dr; // 1 byte for column - c := AStream.ReadByte; - AColOffset := c; + dc := AStream.ReadByte; + AColOffset := dc; + + // Extract absolute/relative flags + AFlags := []; + if (r and MASK_EXCEL_RELATIVE_COL <> 0) then Include(AFlags, rfRelCol); + if (r and MASK_EXCEL_RELATIVE_ROW <> 0) then Include(AFlags, rfRelRow); end; { Reads the cell address used in an RPN formula element. Evaluates the corresponding @@ -1649,8 +1656,8 @@ begin end; INT_EXCEL_TOKEN_TREFN_R, INT_EXCEL_TOKEN_TREFN_V: begin - ReadRPNCellAddressOffset(AStream, dr, dc); - rpnItem := RPNCellOffset(dr, dc, rpnItem); + ReadRPNCellAddressOffset(AStream, dr, dc, flags); + rpnItem := RPNCellOffset(dr, dc, flags, rpnItem); end; INT_EXCEL_TOKEN_TREFN_A: begin