diff --git a/components/fpspreadsheet/fpsopendocument.pas b/components/fpspreadsheet/fpsopendocument.pas index 96f56d102..116233f3f 100755 --- a/components/fpspreadsheet/fpsopendocument.pas +++ b/components/fpspreadsheet/fpsopendocument.pas @@ -179,9 +179,14 @@ type // Routines to write parts of files procedure WriteAutomaticStyles(AStream: TStream); + procedure WriteCellRow(AStream: TStream; ASheet: TsWorksheet; + ARowIndex, ALastColIndex: Integer); procedure WriteCellStyles(AStream: TStream); procedure WriteColStyles(AStream: TStream); 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 WriteMasterStyles(AStream: TStream); procedure WriteNamedExpressions(AStream: TStream; ASheet: TsWorksheet); @@ -220,8 +225,12 @@ type procedure AddBuiltinNumFormats; override; procedure CreateStreams; procedure DestroyStreams; - procedure GetHeaderFooterImageName(APageLayout: TsPageLayout; out AHeader, AFooter: String); - procedure GetHeaderFooterImagePosStr(APagelayout: TsPageLayout; out AHeader, AFooter: String); + procedure GetHeaderFooterImageName(APageLayout: TsPageLayout; + 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 ListAllColumnStyles; procedure ListAllHeaderFooterFonts; @@ -1750,25 +1759,14 @@ end; procedure TsSpreadOpenDocReader.ReadBlank(ARow, ACol: Cardinal; AStyleIndex: Integer; ACellNode: TDOMNode); var -// styleName: String; cell: PCell; -// lCell: TCell; begin + Unused(ACellNode); + // No need to store a record for an empty, unformatted cell if AStyleIndex = 0 then 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 begin InitCell(ARow, ACol, FVirtualCell); @@ -1777,7 +1775,6 @@ begin cell := FWorksheet.AddCell(ARow, ACol); FWorkSheet.WriteBlank(cell); ApplyStyleToCell(cell, AStyleIndex); -// FWorksheet.CopyFormat(@lCell, cell); if FIsVirtualMode then Workbook.OnReadCellData(Workbook, ARow, ACol, cell); @@ -2662,6 +2659,8 @@ begin end; AddToCellText(spanText); end; + 'text:line-break': + AddToCellText(FPS_LINE_ENDING); end; subnode := subnode.NextSibling; end; @@ -4438,6 +4437,7 @@ var found: Boolean; colstyle: TColumnStyleData; w: Double; + col: PCol; begin { At first, add the default column width } colStyle := TColumnStyleData.Create; @@ -4445,9 +4445,36 @@ begin colStyle.ColWidth := FWorkbook.ConvertUnits(12, suChars, FWorkbook.Units); 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 begin 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 begin w := sheet.GetColWidth(c, FWorkbook.Units); @@ -4468,6 +4495,7 @@ begin FColumnStyleList.Add(colStyle); end; end; + } end; (* { fpspreadsheet's column width is the count of '0' characters of the @@ -5095,9 +5123,6 @@ var lastCol: Integer; c, k: Integer; w: Double; - fmt: Integer; -// w, w_mm: Double; -// widthMultiplier: Double; styleName: String; colsRepeated: Integer; colsRepeatedStr: String; @@ -5466,26 +5491,14 @@ end; procedure TsSpreadOpenDocWriter.WriteRowsAndCells(AStream: TStream; ASheet: TsWorksheet); var - r, rr: Cardinal; // row index in sheet - c, cc: Cardinal; // column index in sheet - row: PRow; // sheet row record - cell: PCell; // current cell - styleName: String; - k: Integer; - h, h1: Double; - colsRepeated: Cardinal; - rowsRepeated: Cardinal; - colsRepeatedStr: String; - rowsRepeatedStr: String; + r: Integer; + rowsRepeated: Integer; firstCol, firstRow, lastCol, lastRow: Cardinal; - firstRepeatedPrintRow, lastRepeatedPrintRow: Cardinal; - rowStyleData: TRowStyleData; - emptyRowsAbove: Boolean; + firstRepeatedPrintRow, lastRepeatedPrintRow: Integer; headerRows: Boolean; begin // some abbreviations... GetSheetDimensions(ASheet, firstRow, lastRow, firstCol, lastCol); - emptyRowsAbove := firstRow > 0; headerRows := false; firstRepeatedPrintRow := ASheet.PageLayout.RepeatedRows.FirstIndex; @@ -5495,6 +5508,38 @@ begin then lastRepeatedPrintRow := firstRepeatedPrintRow; + r := 0; + while r <= Integer(lastRow) do + begin + if (r = firstRepeatedPrintRow) then begin + AppendToStream(AStream, ''); + 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, ''); + 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 r := firstRow; while (r <= lastRow) do @@ -5532,7 +5577,7 @@ begin h := ASheet.ReadDefaultRowHeight(FWorkbook.Units); 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 begin rowsRepeated := r; @@ -5613,31 +5658,59 @@ begin continue; end; - // Empty cell? Need to count how many to add "table:number-columns-repeated" colsRepeated := 1; - if cell = nil then + if cell <> nil then + WriteCellToStream(AStream, cell) + else begin - cc := c + 1; - while (cc <= lastCol) do - begin - cell := ASheet.FindCell(r, cc); - if cell <> nil then - break; - inc(cc) - end; - if FHasRowFormats and (cc > lastcol) then - colsRepeated := FLimitations.MaxColCount - c else - colsRepeated := cc - c; - colsRepeatedStr := IfThen(colsRepeated = 1, '', - Format(' table:number-columns-repeated="%d"', [colsRepeated])); row := ASheet.FindRow(r); - if (row <> nil) and (row^.FormatIndex > 0) then - stylename := Format(' table:style-name="ce%d"', [row^.FormatIndex]) else - stylename := ''; - AppendToStream(AStream, Format( - '', [colsRepeatedStr, stylename])); - end else - WriteCellToStream(AStream, cell); + 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( + '', + [col^.FormatIndex])) + else + begin + // Empty cell? Need to count how often to add "table:number-columns-repeated" + cc := c + 1; + while (cc <= lastCol) do + begin + col := nil; + cell := ASheet.FindCell(r, cc); + if cell <> nil then + 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) + end; + if FHasRowFormats and (cc > lastcol) then + colsRepeated := FLimitations.MaxColCount - c + else + colsRepeated := cc - c; + colsRepeatedStr := IfThen(colsRepeated = 1, '', + Format(' table:number-columns-repeated="%d"', [colsRepeated])); + row := ASheet.FindRow(r); + if (row <> nil) and (row^.FormatIndex > 0) then + stylename := Format(' table:style-name="ce%d"', [row^.FormatIndex]) else + stylename := ''; + AppendToStream(AStream, Format( + '', [colsRepeatedStr, stylename])); + if (col <> nil) then //and ((row = nil) or (row^.FormatIndex = 0)) then + begin + AppendToStream(AStream, Format( + '', [col^.FormatIndex])); + end; + if (col <> nil) and (cc = lastcol) then + break; + end; + end; inc(c, colsRepeated); end; @@ -5654,6 +5727,330 @@ begin // Next row inc(r, rowsRepeated); 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( + '', + [colsRepeated]); + end; + cellStr := cellStr + Format( + '', + [col^.FormatIndex]); + c := col^.Col + 1; + end; + inc(k); + end; + + colsRepeated := IfThen(FHasRowFormats, FLimitations.MaxColCount, lastcol) - c; + if colsRepeated > 0 then + cellStr := cellStr + Format( + '', + [colsRepeated]); + + rowsRepeated := FLimitations.MaxRowCount - r; + AppendToStream(AStream, Format( + '' + + '%s' + + '', [ + 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( + '', [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( + '', [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, + ''); + 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( + '', [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( + '', + [row^.FormatIndex, colsRepeated])) + else + AppendToStream(AStream, Format( + '', + [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( + '', + [row^.FormatIndex, colsRepeated])) + else + AppendToStream(AStream, Format( + '', + [colsRepeated])); + end; + + // Write closing row tag. + AppendToStream(AStream, + ''); +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( + '', + [stylename, ARowsRepeated])) + else + AppendToStream(AStream, Format( + '', + [styleName])); + + // Empty cells left of the first column + colsRepeated := AFirstColIndex; + if colsRepeated > 0 then + AppendToStream(AStream, Format( + '', [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( + '', [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( + '', + [row^.FormatIndex, colsRepeated])) + else + AppendToStream(AStream, Format( + '', + [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( + '', + [row^.FormatIndex, colsRepeated])) + else + AppendToStream(AStream, Format( + '', + [colsRepeated])); + end; + + // Write out closing tag for this row + AppendToStream(AStream, + ''); +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; { Write the style nodes for rows ("ro1", "ro2", ...); they contain only @@ -6933,7 +7330,7 @@ var wideStr, txt: WideString; ch: WideChar; - function NewLine(var idx: Integer): Boolean; + function IsNewLine(var idx: Integer): Boolean; begin if (wideStr[idx] = #13) or (wideStr[idx] = #10) then begin @@ -7029,9 +7426,28 @@ begin begin // No hyperlink, normal text only if Length(ACell^.RichTextParams) = 0 then + begin // Standard text formatting - totaltxt := '' + totaltxt + '' - else + (* + { ods writes "" 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 := ''; + while idx <= len do + begin + ch := widestr[idx]; + totaltxt := totaltxt + IfThen(IsNewLine(idx), '', ch); + inc(idx); + end; + totaltxt := totaltxt + ''; + *) + totaltxt := '' + totaltxt + '' ; // has and for line breaks + end else begin // "Rich-text" formatting wideStr := UTF8Decode(AValue); // Convert to unicode @@ -7046,7 +7462,7 @@ begin while (idx <= len) and (idx < rtParam.FirstIndex) do begin ch := wideStr[idx]; - if NewLine(idx) then + if IsNewLine(idx) then AppendTxt(true, '') else txt := txt + ch; @@ -7069,7 +7485,7 @@ begin while (idx <= len) and (idx <= endidx) do begin ch := wideStr[idx]; - if NewLine(idx) then + if IsNewLine(idx) then AppendTxt(true, fntName) else txt := txt + ch; diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 8a1cdb89b..87a5857d5 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -409,9 +409,12 @@ type function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex'; { Data manipulation methods - For Rows and Cols } + function AddCol(ACol: Cardinal): PCol; function AddRow(ARow: Cardinal): PRow; function CalcAutoRowHeight(ARow: Cardinal): Single; function CalcRowHeight(ARow: Cardinal): Single; + function FindFirstCol: PCol; + function FindFirstRow: PRow; function FindRow(ARow: Cardinal): PRow; function FindCol(ACol: Cardinal): PCol; function GetCellCountInRow(ARow: Cardinal): Cardinal; @@ -427,6 +430,7 @@ type function GetColWidth(ACol: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.'; function HasColFormats: Boolean; function HasRowFormats: Boolean; + function IsEmptyRow(ARow: Cardinal): Boolean; procedure DeleteCol(ACol: Cardinal); procedure DeleteRow(ARow: Cardinal); procedure InsertCol(ACol: Cardinal); @@ -4650,6 +4654,7 @@ begin exit; end; + // Check for a fraction string if TryFractionStrToFloat(AValue, number, ismixed, maxdig) then begin WriteNumber(ACell, number); @@ -4662,6 +4667,29 @@ begin exit; 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 begin if number < 1.0 then // this is a time alone @@ -4691,25 +4719,6 @@ begin exit; 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); WriteText(ACell, plain, rtParams); end; @@ -6355,6 +6364,30 @@ begin Result := GetRowHeight(ARow, FWorkbook.Units); 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 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 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. -------------------------------------------------------------------------------} function TsWorksheet.AddRow(ARow: Cardinal): PRow; @@ -6422,10 +6455,12 @@ begin FillChar(Result^, SizeOf(TRow), #0); Result^.Row := ARow; FRows.Add(Result); - if FLastRowIndex = 0 then - FLastRowIndex := GetLastRowIndex(true) - else - FLastRowIndex := Max(FLastRowIndex, ARow); + if FFirstRowIndex = UNASSIGNED_ROW_COL_INDEX + then FFirstRowIndex := GetFirstRowIndex(true) + else FFirstRowIndex := Min(FFirstRowIndex, ARow); + if FLastRowIndex = 0 + then FLastRowIndex := GetLastRowIndex(true) + else FLastRowIndex := Max(FLastRowIndex, ARow); end; {@@ ---------------------------------------------------------------------------- @@ -6439,18 +6474,30 @@ end; function TsWorksheet.GetCol(ACol: Cardinal): PCol; begin Result := FindCol(ACol); - if (Result = nil) then begin - Result := GetMem(SizeOf(TCol)); - FillChar(Result^, SizeOf(TCol), #0); - Result^.Col := ACol; - FCols.Add(Result); - if FFirstColIndex = UNASSIGNED_ROW_COL_INDEX - then FFirstColIndex := GetFirstColIndex(true) - else FFirstColIndex := Min(FFirstColIndex, ACol); - if FLastColIndex = UNASSIGNED_ROW_COL_INDEX - then FLastColIndex := GetLastColIndex(true) - else FLastColIndex := Max(FLastColIndex, ACol); - end; + 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)); + FillChar(Result^, SizeOf(TCol), #0); + Result^.Col := ACol; + FCols.Add(Result); + if FFirstColIndex = UNASSIGNED_ROW_COL_INDEX + then FFirstColIndex := GetFirstColIndex(true) + else FFirstColIndex := Min(FFirstColIndex, ACol); + if FLastColIndex = UNASSIGNED_ROW_COL_INDEX + then FLastColIndex := GetLastColIndex(true) + else FLastColIndex := Max(FLastColIndex, ACol); end; {@@ ---------------------------------------------------------------------------- @@ -6542,10 +6589,10 @@ begin else begin col := FindCol(ACol); - if col <> nil then - Result := col^.Width + if (col = nil) or (col^.ColWidthType = cwtDefault) then + Result := FDefaultColWidth else - Result := FDefaultColWidth; + Result := col^.Width; Result := FWorkbook.ConvertUnits(Result, FWorkbook.Units, AUnits); end; end; @@ -6656,6 +6703,14 @@ begin Result := false; 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 are moved one column to the left. Merged cell blocks and cell references in diff --git a/components/fpspreadsheet/tests/hyperlinktests.pas b/components/fpspreadsheet/tests/hyperlinktests.pas index 5baf2e450..91d93228c 100644 --- a/components/fpspreadsheet/tests/hyperlinktests.pas +++ b/components/fpspreadsheet/tests/hyperlinktests.pas @@ -95,7 +95,7 @@ type implementation uses - uriparser, lazfileutils, fpsutils; + uriparser, lazfileutils, fpsutils, fpsregfileformats; const HyperlinkSheet = 'Hyperlinks'; @@ -176,8 +176,8 @@ begin end; MyWorkBook.WriteToFile(TempFile, AFormat, true); - // To see the file also in the test folder uncomment the next line - // MyWorkBook.WriteToFile(Format('hyperlink_Test_%d_%d%s', [ATestMode, AToolTipMode, GetFileFormatExt(AFormat)]), AFormat, true); + // To see the file in the test folder uncomment the next line + // MyWorkBook.WriteToFile(Format('hyperlink_Test_%d_%d%s', [ATestMode, AToolTipMode, GetSpreadFormatExt(ord(AFormat))]), AFormat, true); finally MyWorkbook.Free;