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