From 342ba95e8e3e6601c0c9c02f00972f0346b277cf Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Wed, 10 Sep 2014 17:15:37 +0000 Subject: [PATCH] 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 --- components/fpspreadsheet/fpsopendocument.pas | 165 +++++++++++++++--- .../fpspreadsheet/tests/spreadtestgui.lpi | 7 +- 2 files changed, 141 insertions(+), 31 deletions(-) diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index f5f937fcd..0a8caded8 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -1780,10 +1780,14 @@ procedure TsSpreadOpenDocReader.ReadRowsAndCells(ATableNode: TDOMNode); var row: Integer; col: Integer; + cell: PCell; cellNode, rowNode: TDOMNode; paramValueType, paramFormula, tableStyleName: String; + paramColsSpanned, paramRowsSpanned: String; paramColsRepeated, paramRowsRepeated: String; rowsRepeated: Integer; + rowsSpanned: Integer; + colsSpanned: Integer; rowStyleName: String; rowStyleIndex: Integer; rowStyle: TRowStyleData; @@ -1847,7 +1851,21 @@ begin if ParamFormula <> '' then 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'); if paramColsRepeated = '' then paramColsRepeated := '1'; @@ -2926,29 +2944,24 @@ var h1: Single; colsRepeated: Cardinal; rowsRepeated: Cardinal; + colsSpanned: Cardinal; + rowsSpanned: Cardinal; colsRepeatedStr: String; rowsRepeatedStr: String; + colsSpannedStr: String; + rowsSpannedStr: String; firstCol, firstRow, lastCol, lastRow: Cardinal; rowStyleData: TRowStyleData; defFontSize: Single; emptyRowsAbove: Boolean; + r1,c1,r2,c2: Cardinal; begin // some abbreviations... defFontSize := Workbook.GetFont(0).Size; 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; // Now loop through all rows -// r := 0; r := firstRow; while (r <= lastRow) do begin // Look for the row style of the current row (r) @@ -3027,6 +3040,16 @@ begin while c <= lastCol do begin // Get the cell from the sheet cell := ASheet.FindCell(r, c); + + // Belongs to merged block? + if (cell <> nil) and not FWorksheet.IsMergeBase(cell) and (cell^.MergedNeighbors <> []) then + begin + AppendToStream(AStream, + ''); + inc(c); + continue; + end; + // Empty cell? Need to count how many to add "table:number-columns-repeated" colsRepeated := 1; if cell = nil then begin @@ -3216,18 +3239,31 @@ procedure TsSpreadOpenDocWriter.WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); var lIndex: Integer; + colsSpannedStr: String; + rowsSpannedStr: String; + spannedStr: String; + r1,c1,r2,c2: Cardinal; begin Unused(AStream, ACell); 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 lIndex := FindFormattingInList(ACell); AppendToStream(AStream, Format( - '', [lIndex]), + '', [lIndex, spannedStr]), ''); end else AppendToStream(AStream, - ''); + ''); end; { Creates an XML string for inclusion of the background color into the @@ -3612,15 +3648,29 @@ var valuetype: String; value: string; valueStr: String; + colsSpannedStr: String; + rowsSpannedStr: String; + spannedStr: String; + r1,c1,r2,c2: Cardinal; begin Unused(AStream, ARow, ACol); + // Style if ACell^.UsedFormattingFields <> [] then begin lIndex := FindFormattingInList(ACell); lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" '; end else 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! parser := TsSpreadsheetParser.Create(FWorksheet); try @@ -3683,15 +3733,15 @@ begin data type. Seems to work... } if ACell^.CalcState=csCalculated then AppendToStream(AStream, Format( - '' + + '' + valueStr + '', [ - formula, valuetype, value, lStyle + formula, valuetype, value, lStyle, spannedStr ])) else AppendToStream(AStream, Format( - '', [ - formula, lStyle + '', [ + formula, lStyle, spannedStr ])); end; @@ -3707,21 +3757,43 @@ procedure TsSpreadOpenDocWriter.WriteLabel(AStream: TStream; const ARow, var lStyle: string = ''; lIndex: Integer; + colsSpannedStr: String; + rowsSpannedStr: String; + spannedStr: String; + r1,c1,r2,c2: Cardinal; begin Unused(AStream, ACell); Unused(ARow, ACol); + // Style if ACell^.UsedFormattingFields <> [] then begin lIndex := FindFormattingInList(ACell); lStyle := ' table:style-name="ce' + IntToStr(lIndex) + '" '; end else 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( + '' + + '%s'+ + '', [ + lStyle, spannedStr, + UTF8TextToXMLText(AValue) + ])); + { AppendToStream(AStream, '' + '' + UTF8TextToXMLText(AValue) + '' + ''); + } end; procedure TsSpreadOpenDocWriter.WriteNumber(AStream: TStream; const ARow, @@ -3732,6 +3804,10 @@ var lStyle: string = ''; lIndex: Integer; valType: String; + colsSpannedStr: String; + rowsSpannedStr: String; + spannedStr: String; + r1,c1,r2,c2: Cardinal; begin Unused(AStream, ACell); Unused(ARow, ACol); @@ -3747,7 +3823,16 @@ begin end else 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 StrValue := '1.#INF'; DisplayStr := '1.#INF'; @@ -3756,10 +3841,20 @@ begin DisplayStr := FloatToStr(AValue); // Uses locale decimal separator end; - AppendToStream(AStream, + AppendToStream(AStream, Format( + '' + + '%s' + + '', [ + valType, StrValue, lStyle, spannedStr, + DisplayStr + ])); + { + + '' + '' + DisplayStr + '' + ''); + } end; {******************************************************************* @@ -3781,36 +3876,52 @@ var displayStr: String; lIndex: Integer; isTimeOnly: Boolean; + colsSpannedStr: String; + rowsSpannedStr: String; + spannedStr: String; + r1,c1,r2,c2: Cardinal; begin Unused(AStream, ACell); 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 lIndex := FindFormattingInList(ACell); lStyle := 'table:style-name="ce' + IntToStr(lIndex) + '" '; end else lStyle := ''; - // The row should already be the correct one - // nfTimeInterval is a special case - let's handle it first: if (ACell^.NumberFormat = nfTimeInterval) then begin - //lcfmt := Lowercase(Copy(ACell^.NumberFormatStr, 1, 2)); strValue := FormatDateTime(ISO8601FormatHoursOverflow, AValue, [fdoInterval]); displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue, [fdoInterval]); AppendToStream(AStream, Format( - '' + + '' + '%s' + - '', [strValue, lStyle, displayStr])); + '', [ + strValue, lStyle, spannedStr, + displayStr + ])); end else begin // We have to distinguish between time-only values and values that contain date parts. isTimeOnly := IsTimeFormat(ACell^.NumberFormat) or IsTimeFormat(ACell^.NumberFormatStr); strValue := FormatDateTime(FMT[isTimeOnly], AValue); displayStr := FormatDateTime(ACell^.NumberFormatStr, AValue); AppendToStream(AStream, Format( - '' + + '' + '%s ' + - '', [DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, displayStr])); + '', [ + DT[isTimeOnly], DT[isTimeOnly], strValue, lStyle, spannedStr, + displayStr + ])); end; end; diff --git a/components/fpspreadsheet/tests/spreadtestgui.lpi b/components/fpspreadsheet/tests/spreadtestgui.lpi index f4f30ee59..251664efc 100644 --- a/components/fpspreadsheet/tests/spreadtestgui.lpi +++ b/components/fpspreadsheet/tests/spreadtestgui.lpi @@ -56,6 +56,7 @@ + @@ -65,20 +66,20 @@ + - + - @@ -109,12 +110,10 @@ - -