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
This commit is contained in:
wp_xxyyzz
2014-08-16 23:01:32 +00:00
parent f430301fd8
commit 72ee090ef3
5 changed files with 161 additions and 81 deletions

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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