You've already forked lazarus-ccr
fpspreadsheet: Implement cell comments for biff2 and biff5 (reading and writing). Complete cell comments for ods.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3915 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@ -3415,11 +3415,15 @@ var
|
||||
colsSpannedStr: String;
|
||||
rowsSpannedStr: String;
|
||||
spannedStr: String;
|
||||
comment: String;
|
||||
r1,c1,r2,c2: Cardinal;
|
||||
fmt: TsCellFormat;
|
||||
begin
|
||||
Unused(ARow, ACol);
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Merged?
|
||||
if FWorksheet.IsMergeBase(ACell) then
|
||||
begin
|
||||
@ -3434,6 +3438,7 @@ begin
|
||||
if fmt.UsedFormattingFields <> [] then
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell table:style-name="ce%d" %s>', [ACell^.FormatIndex, spannedStr]),
|
||||
comment,
|
||||
'</table:table-cell>')
|
||||
else
|
||||
AppendToStream(AStream,
|
||||
@ -3449,6 +3454,7 @@ var
|
||||
lStyle, valType: String;
|
||||
r1,c1,r2,c2: Cardinal;
|
||||
rowsSpannedStr, colsSpannedStr, spannedStr: String;
|
||||
comment: String;
|
||||
strValue: String;
|
||||
displayStr: String;
|
||||
fmt: TsCellFormat;
|
||||
@ -3463,6 +3469,9 @@ begin
|
||||
else
|
||||
lStyle := '';
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Merged?
|
||||
if FWorksheet.IsMergeBase(ACell) then
|
||||
begin
|
||||
@ -3486,6 +3495,7 @@ begin
|
||||
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell office:value-type="%s" office:boolean-value="%s" %s %s >' +
|
||||
comment +
|
||||
'<text:p>%s</text:p>' +
|
||||
'</table:table-cell>', [
|
||||
valType, StrValue, lStyle, spannedStr,
|
||||
@ -3923,6 +3933,7 @@ var
|
||||
colsSpannedStr: String;
|
||||
rowsSpannedStr: String;
|
||||
spannedStr: String;
|
||||
comment: String;
|
||||
r1,c1,r2,c2: Cardinal;
|
||||
fmt: TsCellFormat;
|
||||
begin
|
||||
@ -3935,6 +3946,9 @@ begin
|
||||
else
|
||||
lStyle := '';
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Merged?
|
||||
if FWorksheet.IsMergeBase(ACell) then
|
||||
begin
|
||||
@ -4009,6 +4023,7 @@ begin
|
||||
if ACell^.CalcState=csCalculated then
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell table:formula="=%s" office:value-type="%s" %s %s %s>' +
|
||||
comment +
|
||||
valueStr +
|
||||
'</table:table-cell>', [
|
||||
formula, valuetype, value, lStyle, spannedStr
|
||||
@ -4048,6 +4063,9 @@ begin
|
||||
else
|
||||
lStyle := '';
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Merged?
|
||||
if FWorksheet.IsMergeBase(ACell) then
|
||||
begin
|
||||
@ -4066,8 +4084,6 @@ begin
|
||||
GetCellString(ARow, ACol)
|
||||
]);
|
||||
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Write it ...
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell office:value-type="string" %s %s>' +
|
||||
@ -4089,6 +4105,7 @@ var
|
||||
colsSpannedStr: String;
|
||||
rowsSpannedStr: String;
|
||||
spannedStr: String;
|
||||
comment: String;
|
||||
r1,c1,r2,c2: Cardinal;
|
||||
fmt: TsCellFormat;
|
||||
begin
|
||||
@ -4106,6 +4123,9 @@ begin
|
||||
end else
|
||||
lStyle := '';
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// Merged?
|
||||
if FWorksheet.IsMergeBase(ACell) then
|
||||
begin
|
||||
@ -4128,6 +4148,7 @@ begin
|
||||
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell office:value-type="%s" office:value="%s" %s %s >' +
|
||||
comment +
|
||||
'<text:p>%s</text:p>' +
|
||||
'</table:table-cell>', [
|
||||
valType, StrValue, lStyle, spannedStr,
|
||||
@ -4152,6 +4173,7 @@ var
|
||||
colsSpannedStr: String;
|
||||
rowsSpannedStr: String;
|
||||
spannedStr: String;
|
||||
comment: String;
|
||||
r1,c1,r2,c2: Cardinal;
|
||||
fmt: TsCellFormat;
|
||||
begin
|
||||
@ -4173,6 +4195,9 @@ begin
|
||||
else
|
||||
lStyle := '';
|
||||
|
||||
// Comment
|
||||
comment := WriteCommentXMLAsString(ACell^.Comment);
|
||||
|
||||
// nfTimeInterval is a special case - let's handle it first:
|
||||
|
||||
if (fmt.NumberFormat = nfTimeInterval) then
|
||||
@ -4181,6 +4206,7 @@ begin
|
||||
displayStr := FormatDateTime(fmt.NumberFormatStr, AValue, [fdoInterval]);
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell office:value-type="time" office:time-value="%s" %s %s>' +
|
||||
comment +
|
||||
'<text:p>%s</text:p>' +
|
||||
'</table:table-cell>', [
|
||||
strValue, lStyle, spannedStr,
|
||||
@ -4194,6 +4220,7 @@ begin
|
||||
displayStr := FormatDateTime(fmt.NumberFormatStr, AValue);
|
||||
AppendToStream(AStream, Format(
|
||||
'<table:table-cell office:value-type="%s" office:%s-value="%s" %s %s>' +
|
||||
comment +
|
||||
'<text:p>%s</text:p> ' +
|
||||
'</table:table-cell>', [
|
||||
DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, spannedStr,
|
||||
|
@ -901,6 +901,8 @@ type
|
||||
{@@ Abstract method for writing a boolean cell. Must be overridden by descendent classes. }
|
||||
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
|
||||
const AValue: Boolean; ACell: PCell); virtual; abstract;
|
||||
{@@ (Pseudo-)abstract method for writing a cell comment. Must be overridden by descendent classes }
|
||||
procedure WriteComment(AStream: TStream; ACell: PCell); virtual;
|
||||
{@@ Abstract method for writing a date/time value to a cell. Must be overridden by descendent classes. }
|
||||
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
|
||||
const AValue: TDateTime; ACell: PCell); virtual; abstract;
|
||||
@ -8708,6 +8710,8 @@ begin
|
||||
cctUTF8String:
|
||||
WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell);
|
||||
end;
|
||||
if ACell^.Comment <> '' then
|
||||
WriteComment(AStream, ACell);
|
||||
end;
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
@ -8724,6 +8728,16 @@ begin
|
||||
IterateThroughCells(AStream, ACells, WriteCellCallback);
|
||||
end;
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
(Pseudo-) abstract method writing a cell comment to the stream.
|
||||
Must be overridden by descendents.
|
||||
|
||||
@param ACell Pointer to the cell to be written
|
||||
-------------------------------------------------------------------------------}
|
||||
procedure TsCustomSpreadWriter.WriteComment(AStream: TStream; ACell: PCell);
|
||||
begin
|
||||
end;
|
||||
|
||||
{@@ ----------------------------------------------------------------------------
|
||||
A generic method to iterate through all cells in a worksheet and call a callback
|
||||
routine for each cell.
|
||||
|
@ -531,6 +531,7 @@ begin
|
||||
case RecordType of
|
||||
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
|
||||
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
|
||||
INT_EXCEL_ID_NOTE : ReadComment(AStream);
|
||||
INT_EXCEL_ID_FONT : ReadFont(AStream);
|
||||
INT_EXCEL_ID_FONTCOLOR : ReadFontColor(AStream);
|
||||
INT_EXCEL_ID_FORMAT : ReadFormat(AStream);
|
||||
|
@ -390,6 +390,7 @@ begin
|
||||
INT_EXCEL_ID_BLANK : ReadBlank(AStream);
|
||||
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
|
||||
INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream);
|
||||
INT_EXCEL_ID_NOTE : ReadComment(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.
|
||||
|
@ -23,6 +23,7 @@ uses
|
||||
const
|
||||
{ RECORD IDs which didn't change across versions 2-8 }
|
||||
INT_EXCEL_ID_EOF = $000A;
|
||||
INT_EXCEL_ID_NOTE = $001C;
|
||||
INT_EXCEL_ID_SELECTION = $001D;
|
||||
INT_EXCEL_ID_CONTINUE = $003C;
|
||||
INT_EXCEL_ID_DATEMODE = $0022;
|
||||
@ -221,6 +222,8 @@ type
|
||||
FDateMode: TDateMode;
|
||||
FPaletteFound: Boolean;
|
||||
FIncompleteCell: PCell;
|
||||
FIncompleteNote: String;
|
||||
FIncompleteNoteLength: Word;
|
||||
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual; //overload;
|
||||
procedure CreateNumFormatList; override;
|
||||
// Extracts a number out of an RK value
|
||||
@ -238,6 +241,8 @@ type
|
||||
procedure ReadCodePage(AStream: TStream);
|
||||
// Read column info
|
||||
procedure ReadColInfo(const AStream: TStream);
|
||||
// Read attached comment
|
||||
procedure ReadComment(const AStream: TStream);
|
||||
// Figures out what the base year for dates is for this file
|
||||
procedure ReadDateMode(AStream: TStream);
|
||||
// Reads the default column width
|
||||
@ -320,6 +325,8 @@ type
|
||||
// Writes out column info(s)
|
||||
procedure WriteColInfo(AStream: TStream; ACol: PCol);
|
||||
procedure WriteColInfos(AStream: TStream; ASheet: TsWorksheet);
|
||||
// Writes out NOTE record(s)
|
||||
procedure WriteComment(AStream: TStream; ACell: PCell); override;
|
||||
// Writes out DATEMODE record depending on FDateMode
|
||||
procedure WriteDateMode(AStream: TStream);
|
||||
// Writes out a TIME/DATE/TIMETIME
|
||||
@ -462,6 +469,14 @@ type
|
||||
Value: Double;
|
||||
end;
|
||||
|
||||
TBIFF25NoteRecord = packed record
|
||||
RecordID: Word;
|
||||
RecordSize: Word;
|
||||
Row: Word;
|
||||
Col: Word;
|
||||
TextLen: Word;
|
||||
end;
|
||||
|
||||
function ConvertExcelDateTimeToDateTime(const AExcelDateNum: Double;
|
||||
ADateMode: TDateMode): TDateTime;
|
||||
begin
|
||||
@ -882,6 +897,74 @@ begin
|
||||
FWorksheet.WriteColInfo(c, col);
|
||||
end;
|
||||
|
||||
// Read a NOTE record which describes an attached comment
|
||||
// Valid for BIFF2-BIFF5
|
||||
procedure TsSpreadBIFFReader.ReadComment(const AStream: TStream);
|
||||
var
|
||||
rec: TBIFF25NoteRecord;
|
||||
r, c: Cardinal;
|
||||
n: Word;
|
||||
s: ansiString;
|
||||
List: TStringList;
|
||||
begin
|
||||
rec.Row := 0; // to silence the compiler...
|
||||
AStream.ReadBuffer(rec.Row, SizeOf(TBIFF25NoteRecord) - 2*SizeOf(Word));
|
||||
r := WordLEToN(rec.Row);
|
||||
c := WordLEToN(rec.Col);
|
||||
n := WordLEToN(rec.TextLen);
|
||||
// First NOTE record
|
||||
if r <> $FFFF then
|
||||
begin
|
||||
// entire note is in this record
|
||||
if n <= self.RecordSize - 3*SizeOf(word) then
|
||||
begin
|
||||
SetLength(s, n);
|
||||
AStream.ReadBuffer(s[1], n);
|
||||
FIncompleteNote := '';
|
||||
FIncompleteNoteLength := 0;
|
||||
List := TStringList.Create;
|
||||
try
|
||||
List.Text := s; // Fix line endings which are #10 in file
|
||||
s := Copy(List.Text, 1, Length(List.Text) - Length(LineEnding));
|
||||
FWorksheet.WriteComment(r, c, s);
|
||||
finally
|
||||
List.Free;
|
||||
end;
|
||||
end else
|
||||
// note will be continued in following record(s): Store partial string
|
||||
begin
|
||||
FIncompleteNoteLength := n;
|
||||
n := self.RecordSize - 3*SizeOf(Word);
|
||||
SetLength(s, n);
|
||||
AStream.ReadBuffer(s[1], n);
|
||||
FIncompleteNote := s;
|
||||
FIncompleteCell := FWorksheet.GetCell(r, c);
|
||||
end;
|
||||
end else
|
||||
// One of the continuation records
|
||||
begin
|
||||
SetLength(s, n);
|
||||
AStream.ReadBuffer(s[1], n);
|
||||
FIncompleteNote := FIncompleteNote + s;
|
||||
// last continuation record
|
||||
if Length(FIncompleteNote) = FIncompleteNoteLength then
|
||||
begin
|
||||
List := TStringList.Create;
|
||||
try
|
||||
List.Text := FIncompleteNote; // Fix line endings which are #10 in file
|
||||
s := Copy(List.Text, 1, Length(List.Text) - Length(LineEnding));
|
||||
FIncompleteCell^.Comment := s;
|
||||
finally
|
||||
List.Free;
|
||||
end;
|
||||
FIncompleteNote := '';
|
||||
FIncompleteCell := nil;
|
||||
FIncompleteNoteLength := 0;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
procedure TsSpreadBIFFReader.ReadDateMode(AStream: TStream);
|
||||
var
|
||||
lBaseMode: Word;
|
||||
@ -1875,6 +1958,61 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ Writes a NOTE record which describes a comment attached to a cell }
|
||||
procedure TsSpreadBIFFWriter.WriteComment(AStream: TStream; ACell: PCell);
|
||||
const
|
||||
CHUNK_SIZE = 2048;
|
||||
var
|
||||
rec: TBIFF25NoteRecord;
|
||||
L: Integer;
|
||||
base_size: Word;
|
||||
p: Integer;
|
||||
comment: ansistring;
|
||||
List: TStringList;
|
||||
begin
|
||||
Unused(ACell);
|
||||
|
||||
if (ACell^.Comment = '') then
|
||||
exit;
|
||||
|
||||
List := TStringList.Create;
|
||||
try
|
||||
List.Text := UTF8ToAnsi(ACell^.Comment);
|
||||
comment := List[0];
|
||||
for p := 1 to List.Count-1 do
|
||||
comment := comment + #$0A + List[p];
|
||||
finally
|
||||
List.Free;
|
||||
end;
|
||||
|
||||
L := Length(comment);
|
||||
base_size := SizeOf(rec) - 2*SizeOf(word);
|
||||
|
||||
// First NOTE record
|
||||
rec.RecordID := WordToLE(INT_EXCEL_ID_NOTE);
|
||||
rec.Row := WordToLE(ACell^.Row);
|
||||
rec.Col := WordToLE(ACell^.Col);
|
||||
rec.TextLen := L;
|
||||
rec.RecordSize := base_size + Min(L, CHUNK_SIZE);
|
||||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||||
AStream.WriteBuffer(comment[1], Min(L, CHUNK_SIZE)); // Write text
|
||||
|
||||
// If the comment text does not fit into 2048 bytes continuation records
|
||||
// have to be written.
|
||||
rec.Row := $FFFF; // indicator that this will be a continuation record
|
||||
rec.Col := 0;
|
||||
p := CHUNK_SIZE;
|
||||
dec(L, CHUNK_SIZE);
|
||||
while L > 0 do begin
|
||||
rec.TextLen := Min(L, CHUNK_SIZE);
|
||||
rec.RecordSize := base_size + rec.TextLen;
|
||||
AStream.WriteBuffer(rec, SizeOf(rec));
|
||||
AStream.WriteBuffer(comment[p], rec.TextLen);
|
||||
dec(L, CHUNK_SIZE);
|
||||
inc(p, CHUNK_SIZE);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TsSpreadBIFFWriter.WriteDateMode(AStream: TStream);
|
||||
begin
|
||||
{ BIFF Record header }
|
||||
|
Reference in New Issue
Block a user