fpspreadsheet: Read/write merged cells to/from ODS files.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3542 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2014-09-10 17:15:37 +00:00
parent 9d3732ba03
commit 342ba95e8e
2 changed files with 141 additions and 31 deletions

View File

@ -1780,10 +1780,14 @@ procedure TsSpreadOpenDocReader.ReadRowsAndCells(ATableNode: TDOMNode);
var var
row: Integer; row: Integer;
col: Integer; col: Integer;
cell: PCell;
cellNode, rowNode: TDOMNode; cellNode, rowNode: TDOMNode;
paramValueType, paramFormula, tableStyleName: String; paramValueType, paramFormula, tableStyleName: String;
paramColsSpanned, paramRowsSpanned: String;
paramColsRepeated, paramRowsRepeated: String; paramColsRepeated, paramRowsRepeated: String;
rowsRepeated: Integer; rowsRepeated: Integer;
rowsSpanned: Integer;
colsSpanned: Integer;
rowStyleName: String; rowStyleName: String;
rowStyleIndex: Integer; rowStyleIndex: Integer;
rowStyle: TRowStyleData; rowStyle: TRowStyleData;
@ -1847,7 +1851,21 @@ begin
if ParamFormula <> '' then if ParamFormula <> '' then
ReadFormula(row, col, cellNode); ReadFormula(row, col, cellNode);
// ReadLabel(row, col, cellNode);
paramColsSpanned := GetAttrValue(cellNode, 'table:number-columns-spanned');
if paramColsSpanned <> '' then
colsSpanned := StrToInt(paramColsSpanned) - 1
else
colsSpanned := 0;
paramRowsSpanned := GetAttrValue(cellNode, 'table:number-rows-spanned');
if paramRowsSpanned <> '' then
rowsSpanned := StrToInt(paramRowsSpanned) - 1
else
rowsSpanned := 0;
if (colsSpanned <> 0) or (rowsSpanned <> 0) then
FWorksheet.MergeCells(row, col, row+rowsSpanned, col+colsSpanned);
paramColsRepeated := GetAttrValue(cellNode, 'table:number-columns-repeated'); paramColsRepeated := GetAttrValue(cellNode, 'table:number-columns-repeated');
if paramColsRepeated = '' then paramColsRepeated := '1'; if paramColsRepeated = '' then paramColsRepeated := '1';
@ -2926,29 +2944,24 @@ var
h1: Single; h1: Single;
colsRepeated: Cardinal; colsRepeated: Cardinal;
rowsRepeated: Cardinal; rowsRepeated: Cardinal;
colsSpanned: Cardinal;
rowsSpanned: Cardinal;
colsRepeatedStr: String; colsRepeatedStr: String;
rowsRepeatedStr: String; rowsRepeatedStr: String;
colsSpannedStr: String;
rowsSpannedStr: String;
firstCol, firstRow, lastCol, lastRow: Cardinal; firstCol, firstRow, lastCol, lastRow: Cardinal;
rowStyleData: TRowStyleData; rowStyleData: TRowStyleData;
defFontSize: Single; defFontSize: Single;
emptyRowsAbove: Boolean; emptyRowsAbove: Boolean;
r1,c1,r2,c2: Cardinal;
begin begin
// some abbreviations... // some abbreviations...
defFontSize := Workbook.GetFont(0).Size; defFontSize := Workbook.GetFont(0).Size;
GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol); GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol);
{
firstCol := ASheet.GetFirstColIndex;
firstRow := ASheet.GetFirstRowIndex;
lastCol := ASheet.GetLastColIndex;
lastRow := ASheet.GetLastRowIndex;
// avoid arithmetic overflow in case of empty worksheet
if (firstCol = $FFFFFFFF) and (lastCol = 0) then firstCol := 0;
if (FirstRow = $FFFFFFFF) and (lastRow = 0) then firstRow := 0;
}
emptyRowsAbove := firstRow > 0; emptyRowsAbove := firstRow > 0;
// Now loop through all rows // Now loop through all rows
// r := 0;
r := firstRow; r := firstRow;
while (r <= lastRow) do begin while (r <= lastRow) do begin
// Look for the row style of the current row (r) // Look for the row style of the current row (r)
@ -3027,6 +3040,16 @@ begin
while c <= lastCol do begin while c <= lastCol do begin
// Get the cell from the sheet // Get the cell from the sheet
cell := ASheet.FindCell(r, c); cell := ASheet.FindCell(r, c);
// Belongs to merged block?
if (cell <> nil) and not FWorksheet.IsMergeBase(cell) and (cell^.MergedNeighbors <> []) then
begin
AppendToStream(AStream,
'<table:covered-table-cell />');
inc(c);
continue;
end;
// Empty cell? Need to count how many to add "table:number-columns-repeated" // Empty cell? Need to count how many to add "table:number-columns-repeated"
colsRepeated := 1; colsRepeated := 1;
if cell = nil then begin if cell = nil then begin
@ -3216,18 +3239,31 @@ procedure TsSpreadOpenDocWriter.WriteBlank(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell); const ARow, ACol: Cardinal; ACell: PCell);
var var
lIndex: Integer; lIndex: Integer;
colsSpannedStr: String;
rowsSpannedStr: String;
spannedStr: String;
r1,c1,r2,c2: Cardinal;
begin begin
Unused(AStream, ACell); Unused(AStream, ACell);
Unused(ARow, ACol); Unused(ARow, ACol);
// Merged?
if FWorksheet.IsMergeBase(ACell) then begin
FWorksheet.FindMergedRange(ACell, r1, c1, r2, c2);
rowsSpannedStr := Format('table:number-rows-spanned="%d"', [r2 - r1 + 1]);
colsSpannedStr := Format('table:number-columns-spanned="%d"', [c2 - c1 + 1]);
spannedStr := colsSpannedStr + ' ' + rowsSpannedStr;
end else
spannedStr := '';
if ACell^.UsedFormattingFields <> [] then begin if ACell^.UsedFormattingFields <> [] then begin
lIndex := FindFormattingInList(ACell); lIndex := FindFormattingInList(ACell);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d">', [lIndex]), '<table:table-cell table:style-name="ce%d" %s>', [lIndex, spannedStr]),
'</table:table-cell>'); '</table:table-cell>');
end else end else
AppendToStream(AStream, AppendToStream(AStream,
'<table:table-cell />'); '<table:table-cell ' + spannedStr + '/>');
end; end;
{ Creates an XML string for inclusion of the background color into the { Creates an XML string for inclusion of the background color into the
@ -3612,15 +3648,29 @@ var
valuetype: String; valuetype: String;
value: string; value: string;
valueStr: String; valueStr: String;
colsSpannedStr: String;
rowsSpannedStr: String;
spannedStr: String;
r1,c1,r2,c2: Cardinal;
begin begin
Unused(AStream, ARow, ACol); Unused(AStream, ARow, ACol);
// Style
if ACell^.UsedFormattingFields <> [] then begin if ACell^.UsedFormattingFields <> [] then begin
lIndex := FindFormattingInList(ACell); lIndex := FindFormattingInList(ACell);
lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" '; lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" ';
end else end else
lStyle := ''; lStyle := '';
// Merged?
if FWorksheet.IsMergeBase(ACell) then begin
FWorksheet.FindMergedRange(ACell, r1, c1, r2, c2);
rowsSpannedStr := Format('table:number-rows-spanned="%d"', [r2 - r1 + 1]);
colsSpannedStr := Format('table:number-columns-spanned="%d"', [c2 - c1 + 1]);
spannedStr := colsSpannedStr + ' ' + rowsSpannedStr;
end else
spannedStr := '';
// Convert string formula to the format needed by ods: semicolon list separators! // Convert string formula to the format needed by ods: semicolon list separators!
parser := TsSpreadsheetParser.Create(FWorksheet); parser := TsSpreadsheetParser.Create(FWorksheet);
try try
@ -3683,15 +3733,15 @@ begin
data type. Seems to work... } data type. Seems to work... }
if ACell^.CalcState=csCalculated then if ACell^.CalcState=csCalculated then
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:formula="=%s" office:value-type="%s" %s %s>' + '<table:table-cell table:formula="=%s" office:value-type="%s" %s %s %s>' +
valueStr + valueStr +
'</table:table-cell>', [ '</table:table-cell>', [
formula, valuetype, value, lStyle formula, valuetype, value, lStyle, spannedStr
])) ]))
else else
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell table:formula="=%s" %s/>', [ '<table:table-cell table:formula="=%s" %s %s/>', [
formula, lStyle formula, lStyle, spannedStr
])); ]));
end; end;
@ -3707,21 +3757,43 @@ procedure TsSpreadOpenDocWriter.WriteLabel(AStream: TStream; const ARow,
var var
lStyle: string = ''; lStyle: string = '';
lIndex: Integer; lIndex: Integer;
colsSpannedStr: String;
rowsSpannedStr: String;
spannedStr: String;
r1,c1,r2,c2: Cardinal;
begin begin
Unused(AStream, ACell); Unused(AStream, ACell);
Unused(ARow, ACol); Unused(ARow, ACol);
// Style
if ACell^.UsedFormattingFields <> [] then begin if ACell^.UsedFormattingFields <> [] then begin
lIndex := FindFormattingInList(ACell); lIndex := FindFormattingInList(ACell);
lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" '; lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" ';
end else end else
lStyle := ''; lStyle := '';
// The row should already be the correct one // Merged?
if FWorksheet.IsMergeBase(ACell) then begin
FWorksheet.FindMergedRange(ACell, r1, c1, r2, c2);
rowsSpannedStr := Format('table:number-rows-spanned="%d"', [r2 - r1 + 1]);
colsSpannedStr := Format('table:number-columns-spanned="%d"', [c2 - c1 + 1]);
spannedStr := colsSpannedStr + ' ' + rowsSpannedStr;
end else
spannedStr := '';
AppendToStream(AStream, Format(
'<table:table-cell office:value-type="string" %s %s>' +
'<text:p>%s</text:p>'+
'</table:table-cell>', [
lStyle, spannedStr,
UTF8TextToXMLText(AValue)
]));
{
AppendToStream(AStream, AppendToStream(AStream,
'<table:table-cell office:value-type="string"' + lStyle + '>' + '<table:table-cell office:value-type="string"' + lStyle + '>' +
'<text:p>' + UTF8TextToXMLText(AValue) + '</text:p>' + '<text:p>' + UTF8TextToXMLText(AValue) + '</text:p>' +
'</table:table-cell>'); '</table:table-cell>');
}
end; end;
procedure TsSpreadOpenDocWriter.WriteNumber(AStream: TStream; const ARow, procedure TsSpreadOpenDocWriter.WriteNumber(AStream: TStream; const ARow,
@ -3732,6 +3804,10 @@ var
lStyle: string = ''; lStyle: string = '';
lIndex: Integer; lIndex: Integer;
valType: String; valType: String;
colsSpannedStr: String;
rowsSpannedStr: String;
spannedStr: String;
r1,c1,r2,c2: Cardinal;
begin begin
Unused(AStream, ACell); Unused(AStream, ACell);
Unused(ARow, ACol); Unused(ARow, ACol);
@ -3747,7 +3823,16 @@ begin
end else end else
lStyle := ''; lStyle := '';
// The row should already be the correct one // Merged?
if FWorksheet.IsMergeBase(ACell) then begin
FWorksheet.FindMergedRange(ACell, r1, c1, r2, c2);
rowsSpannedStr := Format('table:number-rows-spanned="%d"', [r2 - r1 + 1]);
colsSpannedStr := Format('table:number-columns-spanned="%d"', [c2 - c1 + 1]);
spannedStr := colsSpannedStr + ' ' + rowsSpannedStr;
end else
spannedStr := '';
// Displayed value
if IsInfinite(AValue) then begin if IsInfinite(AValue) then begin
StrValue := '1.#INF'; StrValue := '1.#INF';
DisplayStr := '1.#INF'; DisplayStr := '1.#INF';
@ -3756,10 +3841,20 @@ begin
DisplayStr := FloatToStr(AValue); // Uses locale decimal separator DisplayStr := FloatToStr(AValue); // Uses locale decimal separator
end; end;
AppendToStream(AStream, AppendToStream(AStream, Format(
'<table:table-cell office:value-type="%s" office:value="%s" %s %s >' +
'<text:p>%s</text:p>' +
'</table:table-cell>', [
valType, StrValue, lStyle, spannedStr,
DisplayStr
]));
{
'<table:table-cell office:value-type="' + valType + '" office:value="' + StrValue + '"' + lStyle + '>' + '<table:table-cell office:value-type="' + valType + '" office:value="' + StrValue + '"' + lStyle + '>' +
'<text:p>' + DisplayStr + '</text:p>' + '<text:p>' + DisplayStr + '</text:p>' +
'</table:table-cell>'); '</table:table-cell>');
}
end; end;
{******************************************************************* {*******************************************************************
@ -3781,36 +3876,52 @@ var
displayStr: String; displayStr: String;
lIndex: Integer; lIndex: Integer;
isTimeOnly: Boolean; isTimeOnly: Boolean;
colsSpannedStr: String;
rowsSpannedStr: String;
spannedStr: String;
r1,c1,r2,c2: Cardinal;
begin begin
Unused(AStream, ACell); Unused(AStream, ACell);
Unused(ARow, ACol); Unused(ARow, ACol);
// Merged?
if FWorksheet.IsMergeBase(ACell) then begin
FWorksheet.FindMergedRange(ACell, r1, c1, r2, c2);
rowsSpannedStr := Format('table:number-rows-spanned="%d"', [r2 - r1 + 1]);
colsSpannedStr := Format('table:number-columns-spanned="%d"', [c2 - c1 + 1]);
spannedStr := colsSpannedStr + ' ' + rowsSpannedStr;
end else
spannedStr := '';
if ACell^.UsedFormattingFields <> [] then begin if ACell^.UsedFormattingFields <> [] then begin
lIndex := FindFormattingInList(ACell); lIndex := FindFormattingInList(ACell);
lStyle := 'table:style-name="ce' + IntToStr(lIndex) + '" '; lStyle := 'table:style-name="ce' + IntToStr(lIndex) + '" ';
end else end else
lStyle := ''; lStyle := '';
// The row should already be the correct one
// nfTimeInterval is a special case - let's handle it first: // nfTimeInterval is a special case - let's handle it first:
if (ACell^.NumberFormat = nfTimeInterval) then begin if (ACell^.NumberFormat = nfTimeInterval) then begin
//lcfmt := Lowercase(Copy(ACell^.NumberFormatStr, 1, 2));
strValue := FormatDateTime(ISO8601FormatHoursOverflow, AValue, [fdoInterval]); strValue := FormatDateTime(ISO8601FormatHoursOverflow, AValue, [fdoInterval]);
displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue, [fdoInterval]); displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue, [fdoInterval]);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell office:value-type="time" office:time-value="%s" %s>' + '<table:table-cell office:value-type="time" office:time-value="%s" %s %s>' +
'<text:p>%s</text:p>' + '<text:p>%s</text:p>' +
'</table:table-cell>', [strValue, lStyle, displayStr])); '</table:table-cell>', [
strValue, lStyle, spannedStr,
displayStr
]));
end else begin end else begin
// We have to distinguish between time-only values and values that contain date parts. // We have to distinguish between time-only values and values that contain date parts.
isTimeOnly := IsTimeFormat(ACell^.NumberFormat) or IsTimeFormat(ACell^.NumberFormatStr); isTimeOnly := IsTimeFormat(ACell^.NumberFormat) or IsTimeFormat(ACell^.NumberFormatStr);
strValue := FormatDateTime(FMT[isTimeOnly], AValue); strValue := FormatDateTime(FMT[isTimeOnly], AValue);
displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue); displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell office:value-type="%s" office:%s-value="%s" %s>' + '<table:table-cell office:value-type="%s" office:%s-value="%s" %s %s>' +
'<text:p>%s</text:p> ' + '<text:p>%s</text:p> ' +
'</table:table-cell>', [DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, displayStr])); '</table:table-cell>', [
DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, spannedStr,
displayStr
]));
end; end;
end; end;

View File

@ -56,6 +56,7 @@
<Unit3> <Unit3>
<Filename Value="numberstests.pas"/> <Filename Value="numberstests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="numberstests"/>
</Unit3> </Unit3>
<Unit4> <Unit4>
<Filename Value="manualtests.pas"/> <Filename Value="manualtests.pas"/>
@ -65,20 +66,20 @@
<Unit5> <Unit5>
<Filename Value="testsutility.pas"/> <Filename Value="testsutility.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="testsutility"/>
</Unit5> </Unit5>
<Unit6> <Unit6>
<Filename Value="internaltests.pas"/> <Filename Value="internaltests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="internaltests"/>
</Unit6> </Unit6>
<Unit7> <Unit7>
<Filename Value="formattests.pas"/> <Filename Value="formattests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="formattests"/>
</Unit7> </Unit7>
<Unit8> <Unit8>
<Filename Value="colortests.pas"/> <Filename Value="colortests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="colortests"/>
</Unit8> </Unit8>
<Unit9> <Unit9>
<Filename Value="fonttests.pas"/> <Filename Value="fonttests.pas"/>
@ -109,12 +110,10 @@
<Unit15> <Unit15>
<Filename Value="errortests.pas"/> <Filename Value="errortests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="errortests"/>
</Unit15> </Unit15>
<Unit16> <Unit16>
<Filename Value="virtualmodetests.pas"/> <Filename Value="virtualmodetests.pas"/>
<IsPartOfProject Value="True"/> <IsPartOfProject Value="True"/>
<UnitName Value="virtualmodetests"/>
</Unit16> </Unit16>
</Units> </Units>
</ProjectOptions> </ProjectOptions>