fpspreadsheet: Massive refactoring of ods cell and row writing. ods can write column and row formats now. Fix unit tests (100% ok).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5265 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2016-10-18 16:42:24 +00:00
parent 5f7a98d5b9
commit d570c75403
3 changed files with 575 additions and 104 deletions

View File

@ -179,9 +179,14 @@ type
// Routines to write parts of files // Routines to write parts of files
procedure WriteAutomaticStyles(AStream: TStream); procedure WriteAutomaticStyles(AStream: TStream);
procedure WriteCellRow(AStream: TStream; ASheet: TsWorksheet;
ARowIndex, ALastColIndex: Integer);
procedure WriteCellStyles(AStream: TStream); procedure WriteCellStyles(AStream: TStream);
procedure WriteColStyles(AStream: TStream); procedure WriteColStyles(AStream: TStream);
procedure WriteColumns(AStream: TStream; ASheet: TsWorksheet); procedure WriteColumns(AStream: TStream; ASheet: TsWorksheet);
procedure WriteEmptyRow(AStream: TStream; ASheet: TsWorksheet;
ARowIndex, AFirstColIndex, ALastColIndex, ALastRowIndex: Integer;
out ARowsRepeated: Integer);
procedure WriteFontNames(AStream: TStream); procedure WriteFontNames(AStream: TStream);
procedure WriteMasterStyles(AStream: TStream); procedure WriteMasterStyles(AStream: TStream);
procedure WriteNamedExpressions(AStream: TStream; ASheet: TsWorksheet); procedure WriteNamedExpressions(AStream: TStream; ASheet: TsWorksheet);
@ -220,8 +225,12 @@ type
procedure AddBuiltinNumFormats; override; procedure AddBuiltinNumFormats; override;
procedure CreateStreams; procedure CreateStreams;
procedure DestroyStreams; procedure DestroyStreams;
procedure GetHeaderFooterImageName(APageLayout: TsPageLayout; out AHeader, AFooter: String); procedure GetHeaderFooterImageName(APageLayout: TsPageLayout;
procedure GetHeaderFooterImagePosStr(APagelayout: TsPageLayout; out AHeader, AFooter: String); out AHeader, AFooter: String);
procedure GetHeaderFooterImagePosStr(APagelayout: TsPageLayout;
out AHeader, AFooter: String);
procedure GetRowStyleAndHeight(ASheet: TsWorksheet; ARowIndex: Integer;
out AStyleName: String; out AHeight: Single);
procedure InternalWriteToStream(AStream: TStream); procedure InternalWriteToStream(AStream: TStream);
procedure ListAllColumnStyles; procedure ListAllColumnStyles;
procedure ListAllHeaderFooterFonts; procedure ListAllHeaderFooterFonts;
@ -1750,25 +1759,14 @@ end;
procedure TsSpreadOpenDocReader.ReadBlank(ARow, ACol: Cardinal; procedure TsSpreadOpenDocReader.ReadBlank(ARow, ACol: Cardinal;
AStyleIndex: Integer; ACellNode: TDOMNode); AStyleIndex: Integer; ACellNode: TDOMNode);
var var
// styleName: String;
cell: PCell; cell: PCell;
// lCell: TCell;
begin begin
Unused(ACellNode);
// No need to store a record for an empty, unformatted cell // No need to store a record for an empty, unformatted cell
if AStyleIndex = 0 then if AStyleIndex = 0 then
exit; exit;
(*
// a temporary cell record to store the formatting if there is any
lCell.Row := ARow; // to silence a compiler warning...
InitCell(ARow, ACol, lCell);
lCell.ContentType := cctEmpty;
lCell
styleName := GetAttrValue(ACellNode, 'table:style-name');
if not ApplyStyleToCell(@lCell, stylename) then
exit;
// No need to store a record for an empty, unformatted cell
*)
if FIsVirtualMode then if FIsVirtualMode then
begin begin
InitCell(ARow, ACol, FVirtualCell); InitCell(ARow, ACol, FVirtualCell);
@ -1777,7 +1775,6 @@ begin
cell := FWorksheet.AddCell(ARow, ACol); cell := FWorksheet.AddCell(ARow, ACol);
FWorkSheet.WriteBlank(cell); FWorkSheet.WriteBlank(cell);
ApplyStyleToCell(cell, AStyleIndex); ApplyStyleToCell(cell, AStyleIndex);
// FWorksheet.CopyFormat(@lCell, cell);
if FIsVirtualMode then if FIsVirtualMode then
Workbook.OnReadCellData(Workbook, ARow, ACol, cell); Workbook.OnReadCellData(Workbook, ARow, ACol, cell);
@ -2662,6 +2659,8 @@ begin
end; end;
AddToCellText(spanText); AddToCellText(spanText);
end; end;
'text:line-break':
AddToCellText(FPS_LINE_ENDING);
end; end;
subnode := subnode.NextSibling; subnode := subnode.NextSibling;
end; end;
@ -4438,6 +4437,7 @@ var
found: Boolean; found: Boolean;
colstyle: TColumnStyleData; colstyle: TColumnStyleData;
w: Double; w: Double;
col: PCol;
begin begin
{ At first, add the default column width } { At first, add the default column width }
colStyle := TColumnStyleData.Create; colStyle := TColumnStyleData.Create;
@ -4445,9 +4445,36 @@ begin
colStyle.ColWidth := FWorkbook.ConvertUnits(12, suChars, FWorkbook.Units); colStyle.ColWidth := FWorkbook.ConvertUnits(12, suChars, FWorkbook.Units);
FColumnStyleList.Add(colStyle); FColumnStyleList.Add(colStyle);
{ Then iterate through all sheets and all columns and store the unique
column widths in the FColumnStyleList. }
for i:=0 to Workbook.GetWorksheetCount-1 do for i:=0 to Workbook.GetWorksheetCount-1 do
begin begin
sheet := Workbook.GetWorksheetByIndex(i); sheet := Workbook.GetWorksheetByIndex(i);
for c := 0 to sheet.Cols.Count-1 do
begin
col := PCol(sheet.Cols[c]);
if (col <> nil) and (col^.ColWidthType = cwtCustom) then
begin
w := col^.Width; // is in workbook units
// Look for this width in the current ColumnStyleList
found := false;
for j := 0 to FColumnStyleList.Count - 1 do
if SameValue(TColumnStyleData(FColumnstyleList[j]).ColWidth, w, COLWIDTH_EPS) then
begin
found := true;
break;
end;
// Not found? Then add the column as a new column style
if not found then
begin
colStyle := TColumnStyleData.Create;
colStyle.Name := Format('co%d', [FColumnStyleList.Count + 1]);
colStyle.ColWidth := w;
FColumnStyleList.Add(colStyle);
end;
end;
end;
{
for c:=0 to sheet.GetLastColIndex do for c:=0 to sheet.GetLastColIndex do
begin begin
w := sheet.GetColWidth(c, FWorkbook.Units); w := sheet.GetColWidth(c, FWorkbook.Units);
@ -4468,6 +4495,7 @@ begin
FColumnStyleList.Add(colStyle); FColumnStyleList.Add(colStyle);
end; end;
end; end;
}
end; end;
(* (*
{ fpspreadsheet's column width is the count of '0' characters of the { fpspreadsheet's column width is the count of '0' characters of the
@ -5095,9 +5123,6 @@ var
lastCol: Integer; lastCol: Integer;
c, k: Integer; c, k: Integer;
w: Double; w: Double;
fmt: Integer;
// w, w_mm: Double;
// widthMultiplier: Double;
styleName: String; styleName: String;
colsRepeated: Integer; colsRepeated: Integer;
colsRepeatedStr: String; colsRepeatedStr: String;
@ -5466,26 +5491,14 @@ end;
procedure TsSpreadOpenDocWriter.WriteRowsAndCells(AStream: TStream; ASheet: TsWorksheet); procedure TsSpreadOpenDocWriter.WriteRowsAndCells(AStream: TStream; ASheet: TsWorksheet);
var var
r, rr: Cardinal; // row index in sheet r: Integer;
c, cc: Cardinal; // column index in sheet rowsRepeated: Integer;
row: PRow; // sheet row record
cell: PCell; // current cell
styleName: String;
k: Integer;
h, h1: Double;
colsRepeated: Cardinal;
rowsRepeated: Cardinal;
colsRepeatedStr: String;
rowsRepeatedStr: String;
firstCol, firstRow, lastCol, lastRow: Cardinal; firstCol, firstRow, lastCol, lastRow: Cardinal;
firstRepeatedPrintRow, lastRepeatedPrintRow: Cardinal; firstRepeatedPrintRow, lastRepeatedPrintRow: Integer;
rowStyleData: TRowStyleData;
emptyRowsAbove: Boolean;
headerRows: Boolean; headerRows: Boolean;
begin begin
// some abbreviations... // some abbreviations...
GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol); GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol);
emptyRowsAbove := firstRow > 0;
headerRows := false; headerRows := false;
firstRepeatedPrintRow := ASheet.PageLayout.RepeatedRows.FirstIndex; firstRepeatedPrintRow := ASheet.PageLayout.RepeatedRows.FirstIndex;
@ -5495,6 +5508,38 @@ begin
then then
lastRepeatedPrintRow := firstRepeatedPrintRow; lastRepeatedPrintRow := firstRepeatedPrintRow;
r := 0;
while r <= Integer(lastRow) do
begin
if (r = firstRepeatedPrintRow) then begin
AppendToStream(AStream, '<table:table-header-rows>');
headerRows := true;
end;
// Write rows
if ASheet.IsEmptyRow(r) then
WriteEmptyRow(AStream, ASheet, r, firstCol, lastCol, lastRow, rowsRepeated)
else begin
WriteCellRow(AStream, ASheet, r, lastCol);
rowsRepeated := 1;
end;
r := r + rowsRepeated;
// Header rows need a special tag
if headerRows and (r > lastRepeatedPrintRow) then
begin
AppendToStream(AStream, '</table:table-header-rows>');
headerRows := false;
end;
end;
// Finally, if the sheet contains column formats an empty row has to be
// added which is repeated up to the max worksheet size.
if FHasColFormats then
WriteEmptyRow(AStream, ASheet, r, firstCol, lastCol, -1, rowsRepeated);
end;
(*
// Now loop through all rows // Now loop through all rows
r := firstRow; r := firstRow;
while (r <= lastRow) do while (r <= lastRow) do
@ -5532,7 +5577,7 @@ begin
h := ASheet.ReadDefaultRowHeight(FWorkbook.Units); h := ASheet.ReadDefaultRowHeight(FWorkbook.Units);
end; end;
// Take care of empty rows above the first row // Take care of empty rows above the first row with cells
if (r = firstRow) and emptyRowsAbove then if (r = firstRow) and emptyRowsAbove then
begin begin
rowsRepeated := r; rowsRepeated := r;
@ -5613,20 +5658,41 @@ begin
continue; continue;
end; end;
// Empty cell? Need to count how many to add "table:number-columns-repeated"
colsRepeated := 1; colsRepeated := 1;
if cell = nil then if cell <> nil then
WriteCellToStream(AStream, cell)
else
begin begin
row := ASheet.FindRow(r);
col := ASheet.FindCol(c);
// Empty cell with column format
if (col <> nil) and (col^.FormatIndex > 0) and
((row = nil) or (row^.FormatIndex = 0))
then
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" />',
[col^.FormatIndex]))
else
begin
// Empty cell? Need to count how often to add "table:number-columns-repeated"
cc := c + 1; cc := c + 1;
while (cc <= lastCol) do while (cc <= lastCol) do
begin begin
col := nil;
cell := ASheet.FindCell(r, cc); cell := ASheet.FindCell(r, cc);
if cell <> nil then if cell <> nil then
break; break;
if (row = nil) or (row^.FormatIndex = 0) then
begin
col := ASheet.FindCol(cc);
if (col <> nil) and (col^.FormatIndex > 0) then
break;
end;
inc(cc) inc(cc)
end; end;
if FHasRowFormats and (cc > lastcol) then if FHasRowFormats and (cc > lastcol) then
colsRepeated := FLimitations.MaxColCount - c else colsRepeated := FLimitations.MaxColCount - c
else
colsRepeated := cc - c; colsRepeated := cc - c;
colsRepeatedStr := IfThen(colsRepeated = 1, '', colsRepeatedStr := IfThen(colsRepeated = 1, '',
Format(' table:number-columns-repeated="%d"', [colsRepeated])); Format(' table:number-columns-repeated="%d"', [colsRepeated]));
@ -5635,9 +5701,16 @@ begin
stylename := Format(' table:style-name="ce%d"', [row^.FormatIndex]) else stylename := Format(' table:style-name="ce%d"', [row^.FormatIndex]) else
stylename := ''; stylename := '';
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell%s%s/>', [colsRepeatedStr, stylename])); '<table:table-cell%s%s />', [colsRepeatedStr, stylename]));
end else if (col <> nil) then //and ((row = nil) or (row^.FormatIndex = 0)) then
WriteCellToStream(AStream, cell); begin
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" />', [col^.FormatIndex]));
end;
if (col <> nil) and (cc = lastcol) then
break;
end;
end;
inc(c, colsRepeated); inc(c, colsRepeated);
end; end;
@ -5654,6 +5727,330 @@ begin
// Next row // Next row
inc(r, rowsRepeated); inc(r, rowsRepeated);
end; end;
// Finally, if the sheet contains column formats an empty row has to be
// added which is repeated up to the max worksheet size.
if FHasColFormats then begin
k := 0;
c := 0;
cellStr := '';
while k < ASheet.Cols.Count do begin
col := PCol(ASheet.Cols[k]);
if col^.FormatIndex > 0 then
begin
colsRepeated := col^.Col - c;
if colsRepeated > 0 then begin
cellStr := cellStr + Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]);
end;
cellStr := cellStr + Format(
'<table:table-cell table:style-name="ce%d" />',
[col^.FormatIndex]);
c := col^.Col + 1;
end;
inc(k);
end;
colsRepeated := IfThen(FHasRowFormats, FLimitations.MaxColCount, lastcol) - c;
if colsRepeated > 0 then
cellStr := cellStr + Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]);
rowsRepeated := FLimitations.MaxRowCount - r;
AppendToStream(AStream, Format(
'<table:table-row table:style-name="ro1" table:number-rows-repeated="%d">' +
'%s' +
'</table:table-row>', [
rowsRepeated,
cellStr
]));
end;
end;
*)
procedure TsSpreadOpenDocWriter.WriteCellRow(AStream: TStream;
ASheet: TsWorksheet; ARowIndex, ALastColIndex: Integer);
var
row: PRow;
col: PCol;
cell: PCell;
stylename: string;
h: Single;
firstcol: Integer;
lastcol: Integer;
c, cc: integer;
colsRepeated: Integer;
fmtIndex: integer;
begin
// Get row
row := ASheet.FindRow(ARowIndex);
// Get style and height of row
GetRowStyleAndHeight(ASheet, ARowIndex, stylename, h);
// Write opening row tag. We don't support repeatedRows here.
AppendToStream(AStream, Format(
'<table:table-row table:style-name="%s">', [stylename]));
// Find first cell or column in this row
cell := ASheet.Cells.GetFirstCellOfRow(ARowIndex); // first cell
col := ASheet.FindFirstCol; // left-most column
if col <> nil then
firstcol := Min(col^.Col, cell^.Col) else
firstcol := cell^.Col;
// Find last cell or column in this row
cell := ASheet.Cells.GetlastCellOfRow(ARowIndex);
if ASheet.Cols.Count = 0 then
lastCol := cell^.Col
else begin
col := ASheet.Cols[ASheet.Cols.Count-1];
if col <> nil then
lastcol := Max(col^.Col, cell^.Col) else
lastCol := cell^.Col;
end;
// Cells left to the first col are "empty" with default format
if firstcol > 0 then
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />', [firstcol]));
// Iterate between first and last column
c := firstcol;
while (c <= lastcol) do
begin
cell := ASheet.FindCell(ARowIndex, c);
if cell <> nil then
begin
// Belongs to merged block?
if not FWorksheet.IsMergeBase(cell) and FWorksheet.IsMerged(cell) then
// this means: all cells of a merged block except for the merge base
begin
AppendToStream(AStream,
'<table:covered-table-cell />');
inc(c);
continue;
end;
// Ordinary cell
WriteCellToStream(AStream, cell);
inc(c);
Continue;
end;
// Column format
col := ASheet.FindCol(c);
if (col <> nil) and (col^.FormatIndex > 0) then
begin
// row format has priority...
if (row <> nil) and (row^.FormatIndex > 0) then
fmtIndex := row^.FormatIndex else
fmtIndex := col^.FormatIndex;
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" />', [fmtIndex]));
inc(c);
Continue;
end;
// Empty cell
cc := c + 1;
while (cc <= lastcol) do begin
cell := ASheet.FindCell(ARowIndex, cc);
if cell <> nil then
break;
col := ASheet.FindCol(cc);
if (col <> nil) and (col^.FormatIndex > 0) then
break;
inc(cc);
end;
colsRepeated := cc - c;
// Empty cell with row format?
if (row <> nil) and (row^.FormatIndex > 0) then
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" table:number-columns-repeated="%d" />',
[row^.FormatIndex, colsRepeated]))
else
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]));
inc(c, colsRepeated);
end;
// Fill empty cells at right, in case of RowFormats up to limit of format.
if FHasRowFormats then
colsRepeated := FLimitations.MaxColCount - c
else if c <= ALastColIndex then
colsRepeated := ALastColIndex - c
else
colsRepeated := 0;
if colsRepeated > 0 then
begin
if (row <> nil) and (row^.FormatIndex > 0) then
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" table:number-columns-repeated="%d" />',
[row^.FormatIndex, colsRepeated]))
else
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]));
end;
// Write closing row tag.
AppendToStream(AStream,
'</table:table-row>');
end;
{ Writes a complete row node for the specified row of the worksheet. Correctly
handles row and column formats.
If ALastRowIndex = -1 then the filler rows below the used sheet are written }
procedure TsSpreadOpenDocWriter.WriteEmptyRow(AStream: TStream;
ASheet: TsWorksheet; ARowIndex, AFirstColIndex, ALastColIndex, ALastRowIndex: Integer;
out ARowsRepeated: Integer);
var
row: PRow;
col: PCol;
c, cc, r: Integer;
colsRepeated: Integer;
stylename: String;
h, h1: Single;
fmtIndex: Integer;
begin
// Get style and height of row
GetRowStyleAndHeight(ASheet, ARowIndex, stylename, h);
// Determine how often this row is repeated
row := ASheet.FindRow(ARowIndex);
// Rows with format are not repeated - too complicated...
if (row <> nil) and (row^.FormatIndex > 0) then
ARowsRepeated := 1
else
// Count how many rows are empty and have the same height
if ALastRowIndex > -1 then begin
r := ARowIndex + 1;
while r <= ALastRowIndex do
begin
if not ASheet.IsEmptyRow(r) then
break;
row := ASheet.FindRow(r);
if (row <> nil) and (row^.FormatIndex > 0) then
break;
h1 := ASheet.GetRowHeight(r, FWorkbook.Units);
if not SameValue(h, h1, ROWHEIGHT_EPS) then
break;
inc(r);
end;
ARowsRepeated := r - ARowIndex;
end else
ARowsRepeated := FLimitations.MaxRowCount - ARowIndex;
// Write opening row tag
if ARowsRepeated > 1 then
AppendToStream(AStream, Format(
'<table:table-row table:style-name="%s" table:number-rows-repeated="%d">',
[stylename, ARowsRepeated]))
else
AppendToStream(AStream, Format(
'<table:table-row table:style-name="%s">',
[styleName]));
// Empty cells left of the first column
colsRepeated := AFirstColIndex;
if colsRepeated > 0 then
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />', [colsRepeated]));
// Cells between first and last columns
r := ARowIndex;
c := AFirstColIndex;
row := ASheet.FindRow(r);
while (c <= ALastColIndex) do
begin
// Empty cell in a column with a column format
col := ASheet.FindCol(c);
if (col <> nil) and (col^.FormatIndex > 0) then
begin
if (row <> nil) and (row^.FormatIndex > 0) then
fmtIndex := row^.FormatIndex
else
fmtIndex := col^.FormatIndex;
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" />', [fmtIndex]));
inc(c);
Continue;
end;
// Empty cell? Need to count how often to add "table:number-columns-repeated"
cc := c + 1;
while (cc <= ALastColIndex) do
begin
col := ASheet.FindCol(cc);
if (col <> nil) and (col^.FormatIndex > 0) then
break;
inc(cc);
end;
if (c = ALastColIndex) and FHasRowFormats then
colsRepeated := FLimitations.MaxColCount - c else
colsRepeated := cc - c;
if (row <> nil) and (row^.FormatIndex > 0) then
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" table:number-columns-repeated="%d" />',
[row^.FormatIndex, colsRepeated]))
else
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]));
c := cc
end;
// in case of row formats: extend up to the max column limit of the format
if FHasRowFormats then begin
colsRepeated := FLimitations.MaxColCount - ALastColIndex;
if (row <> nil) and (row^.FormatIndex > 0) then
AppendToStream(AStream, Format(
'<table:table-cell table:style-name="ce%d" table:number-columns-repeated="%d" />',
[row^.FormatIndex, colsRepeated]))
else
AppendToStream(AStream, Format(
'<table:table-cell table:number-columns-repeated="%d" />',
[colsRepeated]));
end;
// Write out closing tag for this row
AppendToStream(AStream,
'</table:table-row>');
end;
procedure TsSpreadOpenDocWriter.GetRowStyleAndHeight(ASheet: TsWorksheet;
ARowIndex: Integer; out AStyleName: String; out AHeight: Single);
var
row: PRow;
rowStyleData: TRowStyleData;
k: Integer;
begin
AStyleName := '';
row := ASheet.FindRow(ARowIndex);
if row <> nil then
begin
AHeight := row^.Height; // row height in workbook units
for k := 0 to FRowStyleList.Count-1 do begin
rowStyleData := TRowStyleData(FRowStyleList[k]);
// Compare row heights, but be aware of rounding errors
if SameValue(rowStyleData.RowHeight, AHeight, ROWHEIGHT_EPS) and
(rowstyleData.RowHeightType = row^.RowHeightType) and
(rowstyleData.RowHeightType <> rhtDefault)
then begin
AStyleName := rowStyleData.Name;
break;
end;
end;
end;
if AStyleName = '' then begin
AStyleName := 'ro1'; // "ro1" is default row record - see ListAllRowStyles
AHeight := ASheet.ReadDefaultRowHeight(FWorkbook.Units);
end;
end; end;
{ Write the style nodes for rows ("ro1", "ro2", ...); they contain only { Write the style nodes for rows ("ro1", "ro2", ...); they contain only
@ -6933,7 +7330,7 @@ var
wideStr, txt: WideString; wideStr, txt: WideString;
ch: WideChar; ch: WideChar;
function NewLine(var idx: Integer): Boolean; function IsNewLine(var idx: Integer): Boolean;
begin begin
if (wideStr[idx] = #13) or (wideStr[idx] = #10) then if (wideStr[idx] = #13) or (wideStr[idx] = #10) then
begin begin
@ -7029,9 +7426,28 @@ begin
begin begin
// No hyperlink, normal text only // No hyperlink, normal text only
if Length(ACell^.RichTextParams) = 0 then if Length(ACell^.RichTextParams) = 0 then
begin
// Standard text formatting // Standard text formatting
totaltxt := '<text:p>' + totaltxt + '</text:p>' (*
else { ods writes "<text:line-break/>" nodes for line-breaks. BUT:
LibreOffice Calc fails to detect these during reading.
OpenOffice Calc and Excel are ok.
Therefore, we skip this part until LO gets fixed. }
wideStr := UTF8Decode(AValue);
len := Length(wideStr);
idx := 1;
totaltxt := '<text:p>';
while idx <= len do
begin
ch := widestr[idx];
totaltxt := totaltxt + IfThen(IsNewLine(idx), '<text:line-break />', ch);
inc(idx);
end;
totaltxt := totaltxt + '</text:p>';
*)
totaltxt := '<text:p>' + totaltxt + '</text:p>' ; // has &#13; and &#10; for line breaks
end else
begin begin
// "Rich-text" formatting // "Rich-text" formatting
wideStr := UTF8Decode(AValue); // Convert to unicode wideStr := UTF8Decode(AValue); // Convert to unicode
@ -7046,7 +7462,7 @@ begin
while (idx <= len) and (idx < rtParam.FirstIndex) do while (idx <= len) and (idx < rtParam.FirstIndex) do
begin begin
ch := wideStr[idx]; ch := wideStr[idx];
if NewLine(idx) then if IsNewLine(idx) then
AppendTxt(true, '') AppendTxt(true, '')
else else
txt := txt + ch; txt := txt + ch;
@ -7069,7 +7485,7 @@ begin
while (idx <= len) and (idx <= endidx) do while (idx <= len) and (idx <= endidx) do
begin begin
ch := wideStr[idx]; ch := wideStr[idx];
if NewLine(idx) then if IsNewLine(idx) then
AppendTxt(true, fntName) AppendTxt(true, fntName)
else else
txt := txt + ch; txt := txt + ch;

View File

@ -409,9 +409,12 @@ type
function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex'; function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex';
{ Data manipulation methods - For Rows and Cols } { Data manipulation methods - For Rows and Cols }
function AddCol(ACol: Cardinal): PCol;
function AddRow(ARow: Cardinal): PRow; function AddRow(ARow: Cardinal): PRow;
function CalcAutoRowHeight(ARow: Cardinal): Single; function CalcAutoRowHeight(ARow: Cardinal): Single;
function CalcRowHeight(ARow: Cardinal): Single; function CalcRowHeight(ARow: Cardinal): Single;
function FindFirstCol: PCol;
function FindFirstRow: PRow;
function FindRow(ARow: Cardinal): PRow; function FindRow(ARow: Cardinal): PRow;
function FindCol(ACol: Cardinal): PCol; function FindCol(ACol: Cardinal): PCol;
function GetCellCountInRow(ARow: Cardinal): Cardinal; function GetCellCountInRow(ARow: Cardinal): Cardinal;
@ -427,6 +430,7 @@ type
function GetColWidth(ACol: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.'; function GetColWidth(ACol: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.';
function HasColFormats: Boolean; function HasColFormats: Boolean;
function HasRowFormats: Boolean; function HasRowFormats: Boolean;
function IsEmptyRow(ARow: Cardinal): Boolean;
procedure DeleteCol(ACol: Cardinal); procedure DeleteCol(ACol: Cardinal);
procedure DeleteRow(ARow: Cardinal); procedure DeleteRow(ARow: Cardinal);
procedure InsertCol(ACol: Cardinal); procedure InsertCol(ACol: Cardinal);
@ -4650,6 +4654,7 @@ begin
exit; exit;
end; end;
// Check for a fraction string
if TryFractionStrToFloat(AValue, number, ismixed, maxdig) then if TryFractionStrToFloat(AValue, number, ismixed, maxdig) then
begin begin
WriteNumber(ACell, number); WriteNumber(ACell, number);
@ -4662,6 +4667,29 @@ begin
exit; exit;
end; end;
// Check for a "number" value (floating point, or integer)
if TryStrToFloat(AValue, number, FWorkbook.FormatSettings) then
begin
if isPercent then
WriteNumber(ACell, number/100, nfPercentage)
else
begin
if IsDateTimeFormat(numFmtParams) then
WriteNumber(ACell, number, nfGeneral)
else
WriteNumber(ACell, number);
end;
if IsTextFormat(numFmtParams) then
begin
WriteNumberFormat(ACell, nfText);
WriteText(ACell, AValue);
end;
exit;
end;
// Check for a date/time value:
// Must be after float detection because StrToDateTime will accept a string
// "1" as a valid date/time.
if TryStrToDateTime(AValue, number, FWorkbook.FormatSettings) then if TryStrToDateTime(AValue, number, FWorkbook.FormatSettings) then
begin begin
if number < 1.0 then // this is a time alone if number < 1.0 then // this is a time alone
@ -4691,25 +4719,6 @@ begin
exit; exit;
end; end;
if TryStrToFloat(AValue, number, FWorkbook.FormatSettings) then
begin
if isPercent then
WriteNumber(ACell, number/100, nfPercentage)
else
begin
if IsDateTimeFormat(numFmtParams) then
WriteNumber(ACell, number, nfGeneral)
else
WriteNumber(ACell, number);
end;
if IsTextFormat(numFmtParams) then
begin
WriteNumberFormat(ACell, nfText);
WriteText(ACell, AValue);
end;
exit;
end;
HTMLToRichText(FWorkbook, ReadcellFont(ACell), AValue, plain, rtParams); HTMLToRichText(FWorkbook, ReadcellFont(ACell), AValue, plain, rtParams);
WriteText(ACell, plain, rtParams); WriteText(ACell, plain, rtParams);
end; end;
@ -6355,6 +6364,30 @@ begin
Result := GetRowHeight(ARow, FWorkbook.Units); Result := GetRowHeight(ARow, FWorkbook.Units);
end; end;
{@@ ----------------------------------------------------------------------------
Returns the first column record, i.e. that of the left-most column
-------------------------------------------------------------------------------}
function TsWorksheet.FindFirstCol: PCol;
var
AVLNode: TAVGLVLTreeNode;
begin
Result := nil;
AVLNode := FCols.FindLowest;
if AVLNode <> nil then Result := PCol(AVLNode.Data);
end;
{@@ ----------------------------------------------------------------------------
Returns the first row record, i.e. that of the top-most row
-------------------------------------------------------------------------------}
function TsWorksheet.FindFirstRow: PRow;
var
AVLNode: TAVGLVLTreeNode;
begin
Result := nil;
AVLNode := FRows.FindLowest;
if AVLNode <> nil then Result := PRow(AVLNode.Data);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Checks if a row record exists for the given row index and returns a pointer Checks if a row record exists for the given row index and returns a pointer
to the row record, or nil if not found to the row record, or nil if not found
@ -6413,7 +6446,7 @@ end;
Creates a new row record for the specific row index. It is not checked whether Creates a new row record for the specific row index. It is not checked whether
a row record already exists for this index. Dupliate records must be avoided! a row record already exists for this index. Dupliate records must be avoided!
@param ARow Index of the row considered @param ARow Index of the row to be added
@return Pointer to the row record with this row index. @return Pointer to the row record with this row index.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorksheet.AddRow(ARow: Cardinal): PRow; function TsWorksheet.AddRow(ARow: Cardinal): PRow;
@ -6422,10 +6455,12 @@ begin
FillChar(Result^, SizeOf(TRow), #0); FillChar(Result^, SizeOf(TRow), #0);
Result^.Row := ARow; Result^.Row := ARow;
FRows.Add(Result); FRows.Add(Result);
if FLastRowIndex = 0 then if FFirstRowIndex = UNASSIGNED_ROW_COL_INDEX
FLastRowIndex := GetLastRowIndex(true) then FFirstRowIndex := GetFirstRowIndex(true)
else else FFirstRowIndex := Min(FFirstRowIndex, ARow);
FLastRowIndex := Max(FLastRowIndex, ARow); if FLastRowIndex = 0
then FLastRowIndex := GetLastRowIndex(true)
else FLastRowIndex := Max(FLastRowIndex, ARow);
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -6439,7 +6474,20 @@ end;
function TsWorksheet.GetCol(ACol: Cardinal): PCol; function TsWorksheet.GetCol(ACol: Cardinal): PCol;
begin begin
Result := FindCol(ACol); Result := FindCol(ACol);
if (Result = nil) then begin if (Result = nil) then
Result := AddCol(ACol);
end;
{@@ ----------------------------------------------------------------------------
Creates a new column record for the specific column index.
It is not checked whether a column record already exists for this index.
Dupliate records must be avoided!
@param ACol Index of the column to be added
@return Pointer to the column record with this column index.
-------------------------------------------------------------------------------}
function TsWorksheet.AddCol(ACol: Cardinal): PCol;
begin
Result := GetMem(SizeOf(TCol)); Result := GetMem(SizeOf(TCol));
FillChar(Result^, SizeOf(TCol), #0); FillChar(Result^, SizeOf(TCol), #0);
Result^.Col := ACol; Result^.Col := ACol;
@ -6450,7 +6498,6 @@ begin
if FLastColIndex = UNASSIGNED_ROW_COL_INDEX if FLastColIndex = UNASSIGNED_ROW_COL_INDEX
then FLastColIndex := GetLastColIndex(true) then FLastColIndex := GetLastColIndex(true)
else FLastColIndex := Max(FLastColIndex, ACol); else FLastColIndex := Max(FLastColIndex, ACol);
end;
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -6542,10 +6589,10 @@ begin
else else
begin begin
col := FindCol(ACol); col := FindCol(ACol);
if col <> nil then if (col = nil) or (col^.ColWidthType = cwtDefault) then
Result := col^.Width Result := FDefaultColWidth
else else
Result := FDefaultColWidth; Result := col^.Width;
Result := FWorkbook.ConvertUnits(Result, FWorkbook.Units, AUnits); Result := FWorkbook.ConvertUnits(Result, FWorkbook.Units, AUnits);
end; end;
end; end;
@ -6656,6 +6703,14 @@ begin
Result := false; Result := false;
end; end;
{@@ ----------------------------------------------------------------------------
Determines whether the specified row contains any occupied cell.
-------------------------------------------------------------------------------}
function TsWorksheet.IsEmptyRow(ARow: Cardinal): Boolean;
begin
Result := Cells.GetFirstCellOfRow(ARow) = nil;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Deletes the column at the index specified. Cells with greader column indexes Deletes the column at the index specified. Cells with greader column indexes
are moved one column to the left. Merged cell blocks and cell references in are moved one column to the left. Merged cell blocks and cell references in

View File

@ -95,7 +95,7 @@ type
implementation implementation
uses uses
uriparser, lazfileutils, fpsutils; uriparser, lazfileutils, fpsutils, fpsregfileformats;
const const
HyperlinkSheet = 'Hyperlinks'; HyperlinkSheet = 'Hyperlinks';
@ -176,8 +176,8 @@ begin
end; end;
MyWorkBook.WriteToFile(TempFile, AFormat, true); MyWorkBook.WriteToFile(TempFile, AFormat, true);
// To see the file also in the test folder uncomment the next line // To see the file in the test folder uncomment the next line
// MyWorkBook.WriteToFile(Format('hyperlink_Test_%d_%d%s', [ATestMode, AToolTipMode, GetFileFormatExt(AFormat)]), AFormat, true); // MyWorkBook.WriteToFile(Format('hyperlink_Test_%d_%d%s', [ATestMode, AToolTipMode, GetSpreadFormatExt(ord(AFormat))]), AFormat, true);
finally finally
MyWorkbook.Free; MyWorkbook.Free;