{@@ ----------------------------------------------------------------------------
Unit fpspreadsheet implements spreadsheet documents and their
properties and methods.
AUTHORS: Felipe Monteiro de Carvalho, Reinier Olislagers, Werner Pamler
LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus
distribution, for details about the license.
-------------------------------------------------------------------------------}
unit fpspreadsheet;
{$ifdef fpc}
{$mode delphi}{$H+}
// {$mode objpas}{$H+}
{$endif}
{$include ..\fps.inc}
interface
uses
{$ifdef UNIX}{$ifndef DARWIN}{$ifndef FPS_DONT_USE_CLOCALE}
clocale,
{$endif}{$endif}{$endif}
Classes, SysUtils, fpimage, avglvltree, lconvencoding,
fpsTypes, fpsExprParser, fpsClasses, fpsNumFormat, fpsPageLayout,
fpsImages, fpsConditionalFormat;
type
{ Forward declarations }
TsWorksheet = class;
TsWorkbook = class;
{ TsWorksheet }
TsNotifyEvent = procedure (Sender: TObject) of object;
{@@ This event fires whenever a cell value or cell formatting changes. It is
handled by TsWorkbookSource to update the listening visual controls. }
TsCellEvent = procedure (Sender: TObject; ARow, ACol: Cardinal) of object;
{@@ This event fires whenever a column width or column format changes. It is
TsColEvent = procedure (Sender: TObject; ACol: Cardinal) of object;
{@@ This event fires whenever a row height or row format changes. It is
handled by TsWorkbookSource to update the listening visual controls }
TsRowEvent = procedure (Sender: TObject; ARow: Cardinal) of object;
{@@ This event can be used to override the built-in comparing function which
is called when cells are sorted. }
TsCellCompareEvent = procedure (Sender: TObject; ACell1, ACell2: PCell;
var AResult: Integer) of object;
TsCellFullCompareEvent = procedure (Sender: TObject; ACell1, ACell2: PCell;
ASortKey: TsSortKey; var AResult: Integer) of object;
{@@ Event fired when writing a file in virtual mode. The event handler has to
pass data ("AValue") and formatting style to be copied from a template
cell ("AStyleCell") to the writer }
TsWorksheetWriteCellDataEvent = procedure(Sender: TsWorksheet; ARow, ACol: Cardinal;
var AValue: variant; var AStyleCell: PCell) of object;
{@@ The worksheet contains a list of cells and provides a variety of methods
to read or write data to the cells, or to change their formatting. }
TsWorksheet = class(TsBasicWorksheet)
private
FWorkbook: TsWorkbook;
FCells: TsCells;
FComments: TsComments;
FMergedCells: TsMergedCells;
FHyperlinks: TsHyperlinks;
FFormulas: TsFormulas;
FImages: TFPList;
FRows, FCols: TIndexedAVLTree; // This lists contain only rows or cols with styles different from default
FActiveCellRow: Cardinal;
FActiveCellCol: Cardinal;
FTopRow: Cardinal;
FLeftCol: Cardinal;
FSelection: TsCellRangeArray;
FLeftPaneWidth: Integer;
FTopPaneHeight: Integer;
FFirstRowIndex: Cardinal;
FFirstColIndex: Cardinal;
FLastRowIndex: Cardinal;
FLastColIndex: Cardinal;
FDefaultColWidth: Single; // in "characters". Excel uses the width of char "0" in 1st font
FDefaultRowHeight: Single; // in "character heights", i.e. line count
FSortParams: TsSortParams; // Parameters of the current sorting operation
FBiDiMode: TsBiDiMode;
FCryptoInfo: TsCryptoInfo;
FPageLayout: TsPageLayout;
FVirtualColCount: Cardinal;
FVirtualRowCount: Cardinal;
FZoomFactor: Double;
FTabColor: TsColor;
FOnChangeCell: TsCellEvent;
FOnChangeFont: TsCellEvent;
FOnChangeCol: TsColEvent;
FOnChangeRow: TsRowEvent;
FOnZoom: TsNotifyEvent;
FOnCompareCells: TsCellCompareEvent;
FOnFullCompareCells: TsCellFullCompareEvent;
FOnSelectCell: TsCellEvent;
FOnWriteCellData: TsWorksheetWriteCellDataEvent;
{ Setter/Getter }
function GetDefaultColWidth: Single;
function GetDefaultRowHeight: Single;
function GetFormatSettings: TFormatSettings;
function GetIndex: Integer;
procedure SetBiDiMode(AValue: TsBiDiMode);
procedure SetDefaultColWidth(AValue: Single);
procedure SetDefaultRowHeight(AValue: Single);
procedure SetIndex(AValue: Integer);
procedure SetTabColor(AValue: TsColor);
procedure SetVirtualColCount(AValue: Cardinal);
procedure SetVirtualRowCount(AValue: Cardinal);
procedure SetZoomFactor(AValue: Double);
protected
function CellUsedInFormula(ARow, ACol: Cardinal): Boolean;
// Remove and delete cells
procedure DeleteRowOrCol(AIndex: Integer; IsRow: Boolean);
procedure InsertRowOrCol(AIndex: Integer; IsRow: Boolean);
function RemoveCell(ARow, ACol: Cardinal): PCell;
procedure RemoveAndFreeCell(ARow, ACol: Cardinal);
// Sorting
function DoCompareCells(AColRow1, AColRow2: Cardinal): Integer;
procedure DoExchangeColRow(AIsColumn: Boolean; AIndex, WithIndex: Cardinal;
AFromIndex, AToIndex: Cardinal);
procedure ExchangeCells(ARow1, ACol1, ARow2, ACol2: Cardinal);
// inherited setters/getters
procedure SetName(const AName: String); override;
public
{ Base methods }
constructor Create;
destructor Destroy; override;
{ Utils }
class function CellInRange(ARow, ACol: Cardinal; ARange: TsCellRange): Boolean;
class function CellPosToText(ARow, ACol: Cardinal): string;
// procedure RemoveAllCells;
procedure UpdateCaches;
{ Reading of values }
function ReadAsText(ARow, ACol: Cardinal): string; overload;
function ReadAsText(ACell: PCell): string; overload;
function ReadAsText(ACell: PCell; AFormatSettings: TFormatSettings): string; overload;
function ReadAsUTF8Text(ARow, ACol: Cardinal): string; overload; deprecated 'Use ReadAsText';
function ReadAsUTF8Text(ACell: PCell): string; overload; deprecated 'Use ReadAsText';
function ReadAsUTF8Text(ACell: PCell; AFormatSettings: TFormatSettings): string; overload; deprecated 'Use ReadAsText';
function ReadAsNumber(ARow, ACol: Cardinal): Double; overload;
function ReadAsNumber(ACell: PCell): Double; overload;
function ReadAsDateTime(ARow, ACol: Cardinal; out AResult: TDateTime): Boolean; overload;
function ReadAsDateTime(ACell: PCell; out AResult: TDateTime): Boolean; overload;
function ReadFormulaAsString(ACell: PCell; ALocalized: Boolean = false): String;
function ReadNumericValue(ACell: PCell; out AValue: Double): Boolean;
{ Reading of cell attributes }
function GetDisplayedDecimals(ACell: PCell): Byte;
function GetNumberFormatAttributes(ACell: PCell; out ADecimals: Byte;
out ACurrencySymbol: String): Boolean;
function GetEffectiveCellFormatIndex(ARow, ACol: Cardinal): Integer; overload;
function GetEffectiveCellFormatIndex(ACell: PCell): Integer; overload;
function GetPointerToEffectiveCellFormat(ARow, ACol: Cardinal): PsCellFormat; //overload;
// function GetPointerToEffectiveCellFormat(ACell: PCell): PsCellFormat; overload;
function ReadUsedFormatting(ACell: PCell): TsUsedFormattingFields;
function ReadBackground(ACell: PCell): TsFillPattern;
function ReadBackgroundColor(ACell: PCell): TsColor; overload;
function ReadBackgroundColor(AFormatIndex: Integer): TsColor; overload;
function ReadCellBorders(ACell: PCell): TsCellBorders;
function ReadCellBorderStyle(ACell: PCell; ABorder: TsCellBorder): TsCellBorderStyle;
function ReadCellBorderStyles(ACell: PCell): TsCellBorderStyles;
function ReadCellFont(ACell: PCell): TsFont;
function ReadCellFontIndex(ACell: PCell): Integer;
function ReadCellFormat(ACell: PCell): TsCellFormat;
function ReadHorAlignment(ACell: PCell): TsHorAlignment;
procedure ReadNumFormat(ACell: PCell; out ANumFormat: TsNumberFormat;
out ANumFormatStr: String);
function ReadTextRotation(ACell: PCell): TsTextRotation;
function ReadVertAlignment(ACell: PCell): TsVertAlignment;
function ReadWordwrap(ACell: PCell): boolean;
function ReadBiDiMode(ACell: PCell): TsBiDiMode;
function ReadCellProtection(ACell: PCell): TsCellProtections;
function IsEmpty: Boolean;
{ Writing of values }
function WriteBlank(ARow, ACol: Cardinal; KeepFormula: Boolean = false): PCell; overload;
procedure WriteBlank(ACell: PCell; KeepFormula: Boolean = false); overload;
function WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean): PCell; overload;
procedure WriteBoolValue(ACell: PCell; AValue: Boolean); overload;
function WriteCellValueAsString(ARow, ACol: Cardinal; AValue: String): PCell; overload;
function WriteCellValueAsString(ARow, ACol: Cardinal; AValue: String;
const AFormatSettings: TFormatSettings): PCell; overload;
procedure WriteCellValueAsString(ACell: PCell; AValue: String); overload;
procedure WriteCellValueAsString(ACell: PCell; AValue: String;
const AFormatSettings: TFormatSettings); overload;
function WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
ANumFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = 2;
ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1;
ANegCurrFormat: Integer = -1): PCell; overload;
procedure WriteCurrency(ACell: PCell; AValue: Double;
ANumFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = -1;
ANegCurrFormat: Integer = -1); overload;
ANumFormat: TsNumberFormat; ANumFormatString: String): PCell; overload;
ANumFormat: TsNumberFormat; ANumFormatString: String); overload;
function WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime): PCell; overload;
procedure WriteDateTime(ACell: PCell; AValue: TDateTime); overload;
function WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
ANumFormat: TsNumberFormat; ANumFormatStr: String = ''): PCell; overload;
procedure WriteDateTime(ACell: PCell; AValue: TDateTime;
ANumFormat: TsNumberFormat; ANumFormatStr: String = ''); overload;
ANumFormatStr: String): PCell; overload;
ANumFormatStr: String); overload;
function WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue): PCell; overload;
procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload;
function WriteFormula(ARow, ACol: Cardinal; AFormula: String;
ALocalized: Boolean = false; R1C1Mode: Boolean = false): PCell; overload;
procedure WriteFormula(ACell: PCell; AFormula: String;
ALocalized: Boolean = false; R1C1Mode: Boolean = false); overload;
function WriteNumber(ARow, ACol: Cardinal; ANumber: double): PCell; overload;
procedure WriteNumber(ACell: PCell; ANumber: Double); overload;
function WriteNumber(ARow, ACol: Cardinal; ANumber: double;
ANumFormat: TsNumberFormat; ADecimals: Byte = 2;
AMinIntDigits: Integer = 1): PCell; overload;
procedure WriteNumber(ACell: PCell; ANumber: Double;
AMinIntDigits: Integer = 1); overload;
function WriteRPNFormula(ARow, ACol: Cardinal;
AFormula: TsRPNFormula): PCell; overload;
procedure WriteRPNFormula(ACell: PCell;
ARPNFormula: TsRPNFormula); overload;
function WriteText(ARow, ACol: Cardinal; AText: String;
ARichTextParams: TsRichTextParams = nil): PCell; overload;
procedure WriteText(ACell: PCell; AText: String;
ARichTextparams: TsRichTextParams = nil); overload;
function WriteTextAsHTML(ARow, ACol: Cardinal; AText: String): PCell; overload;
procedure WriteTextAsHTML(ACell: PCell; AText: String); overload;
function WriteUTF8Text(ARow, ACol: Cardinal; AText: String;
ARichTextParams: TsRichTextParams = nil): PCell; overload; deprecated 'Use WriteText';
procedure WriteUTF8Text(ACell: PCell; AText: String;
ARichTextparams: TsRichTextParams = nil); overload; deprecated 'Use WriteText';
procedure DeleteRichTextParams(ACell: PCell);
{ Writing of cell attributes }
function ChangeBackground(AFormatIndex: Integer; AStyle: TsFillStyle;
APatternColor: TsColor = scTransparent;
ABackgroundColor: TsColor = scTransparent) : Integer;
function WriteBackground(ARow, ACol: Cardinal; AStyle: TsFillStyle;
ABackgroundColor: TsColor = scTransparent): PCell; overload;
procedure WriteBackground(ACell: PCell; AStyle: TsFillStyle;
ABackgroundColor: TsColor = scTransparent); overload;
function WriteBackgroundColor(ARow, ACol: Cardinal; AColor: TsColor): PCell; overload;
procedure WriteBackgroundColor(ACell: PCell; AColor: TsColor); overload;
function WriteBorderColor(ARow, ACol: Cardinal; ABorder: TsCellBorder;
AColor: TsColor): PCell; overload;
procedure WriteBorderColor(ACell: PCell; ABorder: TsCellBorder;
AColor: TsColor); overload;
function WriteBorderLineStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder;
ALineStyle: TsLineStyle): PCell; overload;
procedure WriteBorderLineStyle(ACell: PCell; ABorder: TsCellBorder;
ALineStyle: TsLineStyle); overload;
function WriteBorders(ARow, ACol: Cardinal;
ABorders: TsCellBorders): PCell; overload;
procedure WriteBorders(ACell: PCell; ABorders: TsCellBorders); overload;
{
procedure WriteBorders(ALeft, ATop, ARight, ABottom: Integer;
ABorders: TsCellBorders; ALeftStyle, ATopStyle, ARightStyle, ABottomStyle,
AInnerHorStyle, AInnerVertStyle: TsCellBorderStyle);
}
function WriteBorderStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder;
AStyle: TsCellBorderStyle): PCell; overload;
procedure WriteBorderStyle(ACell: PCell; ABorder: TsCellBorder;
AStyle: TsCellBorderStyle); overload;
ALineStyle: TsLineStyle; AColor: TsColor): PCell; overload;
ALineStyle: TsLineStyle; AColor: TsColor); overload;
function WriteBorderStyles(ARow, ACol: Cardinal;
const AStyles: TsCellBorderStyles): PCell; overload;
procedure WriteBorderStyles(ACell: PCell;
const AStyles: TsCellBorderStyles); overload;
procedure WriteCellFormat(ACell: PCell; const ACellFormat: TsCellFormat);
procedure WriteCellFormatIndex(ACell: PCell; AIndex: Integer);
function WriteDateTimeFormat(ARow, ACol: Cardinal; ANumFormat: TsNumberFormat;
const ANumFormatString: String = ''): PCell; overload;
procedure WriteDateTimeFormat(ACell: PCell; ANumFormat: TsNumberFormat;
const ANumFormatString: String = ''); overload;
function WriteDecimals(ARow, ACol: Cardinal; ADecimals: byte): PCell; overload;
procedure WriteDecimals(ACell: PCell; ADecimals: Byte); overload;
function WriteFont(ARow, ACol: Cardinal; const AFontName: String;
AFontSize: Single; AFontStyle: TsFontStyles; AFontColor: TsColor;
APosition: TsFontPosition = fpNormal): Integer; overload;
function WriteFont(ACell: PCell; const AFontName: String;
function WriteFont(ARow, ACol: Cardinal; AFontIndex: Integer): PCell; overload;
procedure WriteFont(ACell: PCell; AFontIndex: Integer); overload;
function WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer; overload;
function WriteFontColor(ACell: PCell; AFontColor: TsColor): Integer; overload;
function WriteFontName(ARow, ACol: Cardinal; AFontName: String): Integer; overload;
function WriteFontName(ACell: PCell; AFontName: String): Integer; overload;
function WriteFontSize(ARow, ACol: Cardinal; ASize: Single): Integer; overload;
function WriteFontSize(ACell: PCell; ASize: Single): Integer; overload;
function WriteFontStyle(ARow, ACol: Cardinal; AStyle: TsFontStyles): Integer; overload;
function WriteFontStyle(ACell: PCell; AStyle: TsFontStyles): Integer; overload;
function WriteHorAlignment(ARow, ACol: Cardinal; AValue: TsHorAlignment): PCell; overload;
procedure WriteHorAlignment(ACell: PCell; AValue: TsHorAlignment); overload;
function WriteNumberFormat(ARow, ACol: Cardinal; ANumFormat: TsNumberFormat;
procedure WriteNumberFormat(ACell: PCell; ANumFormat: TsNumberFormat;
ADecimals: Integer; ACurrencySymbol: String = ''; APosCurrFormat: Integer = -1;
ADecimals: Integer; ACurrencySymbol: String = '';
APosCurrFormat: Integer = -1; ANegCurrFormat: Integer = -1); overload;
function WriteFractionFormat(ARow, ACol: Cardinal; AMixedFraction: Boolean;
ANumeratorDigits, ADenominatorDigits: Integer): PCell; overload;
procedure WriteFractionFormat(ACell: PCell; AMixedFraction: Boolean;
ANumeratorDigits, ADenominatorDigits: Integer); overload;
function WriteTextRotation(ARow, ACol: Cardinal; ARotation: TsTextRotation): PCell; overload;
procedure WriteTextRotation(ACell: PCell; ARotation: TsTextRotation); overload;
function WriteUsedFormatting(ARow, ACol: Cardinal;
AUsedFormatting: TsUsedFormattingFields): PCell; overload;
procedure WriteUsedFormatting(ACell: PCell;
AUsedFormatting: TsUsedFormattingFields); overload;
function WriteVertAlignment(ARow, ACol: Cardinal; AValue: TsVertAlignment): PCell; overload;
procedure WriteVertAlignment(ACell: PCell; AValue: TsVertAlignment); overload;
function WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean): PCell; overload;
procedure WriteWordwrap(ACell: PCell; AValue: boolean); overload;
function WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell; overload;
procedure WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode); overload;
function WriteCellProtection(ARow, ACol: Cardinal;
AValue: TsCellProtections): PCell; overload;
procedure WriteCellProtection(ACell: PCell;
AValue: TsCellProtections); overload;
{ Conditional formatting }
// cell-related comparisons
function WriteConditionalCellFormat(ARange: TsCellRange; ACondition: TsCFCondition;
ACellFormatIndex: Integer): Integer; overload;
AParam: Variant; ACellFormatIndex: Integer): Integer; overload;
AParam1, AParam2: Variant; ACellFormatIndex: Integer): Integer; overload;
// color range
function WriteColorRange(ARange: TsCellRange; AStartColor: TsColor = scRed;
ACenterColor: TsColor = scYellow; AEndColor: TsColor = scBlue): Integer; overload;
function WriteColorRange(ARange: TsCellRange;
AStartColor: TsColor; AStartKind: TsCFColorRangeValueKind; AStartValue: Double;
ACenterColor: TsColor; ACenterKind: TsCFColorRangeValueKind; ACenterValue: Double;
AEndColor: TsColor; AEndKind: TsCFColorRangeValueKind; AEndValue: Double): Integer; overload;
// data bars
function WriteDataBars(ARange: TsCellRange): Integer;
{ Formulas }
function BuildRPNFormula(ACell: PCell; ADestCell: PCell = nil): TsRPNFormula;
procedure CalcFormula(AFormula: PsFormula);
procedure CalcFormulas;
procedure CalcSheet;
function ConvertFormulaDialect(ACell: PCell; ADialect: TsFormulaDialect): String;
function ConvertRPNFormulaToStringFormula(const AFormula: TsRPNFormula): String;
function GetFormula(ACell: PCell): PsFormula;
{ Data manipulation methods - For Cells }
procedure CopyCell(AFromCell, AToCell: PCell); overload;
function CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal;
AFromWorksheet: TsWorksheet = nil): PCell; overload;
procedure CopyFormat(AFromCell, AToCell: PCell); overload;
procedure CopyFormat(AFormatCell: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyFormula(AFromCell, AToCell: PCell); overload;
procedure CopyFormula(AFormulaCell: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyValue(AFromCell, AToCell: PCell); overload;
procedure CopyValue(AValueCell: PCell; AToRow, AToCol: Cardinal); overload;
procedure CopyCol(AFromCol, AToCol: Cardinal; AFromWorksheet: TsWorksheet = nil);
procedure CopyRow(AFromRow, AToRow: Cardinal; AFromWorksheet: TsWorksheet = nil);
procedure Clear;
procedure DeleteCell(ACell: PCell);
procedure EraseCell(ACell: PCell; AKeepFormat: Boolean = false);
function AddCell(ARow, ACol: Cardinal): PCell;
function FindCell(ARow, ACol: Cardinal): PCell; overload;
function FindCell(AddressStr: String): PCell; overload;
function GetCell(ARow, ACol: Cardinal): PCell; overload;
function GetCell(AddressStr: String): PCell; overload;
function GetCellCount: Cardinal;
function FindNextCellInCol(ARow, ACol: Cardinal): PCell;
function FindNextCellInRow(ARow, ACol: Cardinal): PCell;
function FindPrevCellInCol(ARow, ACol: Cardinal): PCell;
function FindPrevCellInRow(ARow, ACol: Cardinal): PCell;
function GetFirstColIndex(AForceCalculation: Boolean = false): Cardinal;
function GetLastColIndex(AForceCalculation: Boolean = false): Cardinal;
function GetLastColNumber: Cardinal; deprecated 'Use GetLastColIndex';
function GetLastOccupiedColIndex: Cardinal;
function GetFirstRowIndex(AForceCalculation: Boolean = false): Cardinal;
function GetLastOccupiedRowIndex: Cardinal;
function GetLastRowIndex(AForceCalculation: Boolean = false): Cardinal;
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;
function GetCellCountInCol(ACol: Cardinal): Cardinal;
function GetRow(ARow: Cardinal): PRow;
function GetRowFormatIndex(ARow: Cardinal): Integer;
function GetRowHeight(ARow: Cardinal; AUnits: TsSizeUnits): Single; overload;
function GetRowHeight(ARow: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.';
function GetRowHeightType(ARow: Cardinal): TsRowHeightType;
function GetCol(ACol: Cardinal): PCol;
function GetColFormatIndex(ACol: Cardinal): Integer;
function GetColWidth(ACol: Cardinal; AUnits: TsSizeUnits): Single; overload;
function GetColWidth(ACol: Cardinal): Single; overload; deprecated 'Use version with parameter AUnits.';
function GetColWidthType(ACol: Cardinal): TsColWidthType;
function HasColFormats: Boolean;
function HasRowFormats: Boolean;
function IsDefaultCol(ACol: PCol): Boolean;
function IsDefaultRow(ARow: PRow): Boolean;
function ColHidden(ACol: Cardinal): Boolean;
function RowHidden(ARow: Cardinal): Boolean;
procedure HideCol(ACol: Cardinal);
procedure HideRow(ARow: Cardinal);
procedure ShowCol(ACol: Cardinal);
procedure ShowRow(ARow: Cardinal);
function IsEmptyRow(ARow: Cardinal): Boolean;
procedure DeleteCol(ACol: Cardinal);
procedure DeleteRow(ARow: Cardinal);
procedure InsertCol(ACol: Cardinal);
procedure InsertRow(ARow: Cardinal);
procedure MoveCol(AFromCol, AToCol: Cardinal);
procedure MoveRow(AFromRow, AToRow: Cardinal);
function ReadDefaultColWidth(AUnits: TsSizeUnits): Single;
function ReadDefaultRowHeight(AUnits: TsSizeUnits): Single;
function ReadColFont(ACol: PCol): TsFont;
function ReadRowFont(ARow: PRow): TsFont;
procedure RemoveAllRows;
procedure RemoveAllCols;
procedure RemoveCol(ACol: Cardinal);
procedure RemoveRow(ARow: Cardinal);
procedure WriteDefaultColWidth(AValue: Single; AUnits: TsSizeUnits);
procedure WriteDefaultRowHeight(AValue: Single; AUnits: TsSizeUnits);
procedure WriteRowInfo(ARow: Cardinal; AData: TRow);
procedure WriteRowFormatIndex(ARow: Cardinal; AFormatIndex: Integer);
procedure WriteRowHeight(ARow: Cardinal; AHeight: Single; AUnits: TsSizeUnits;
ARowHeightType: TsRowHeightType = rhtCustom); overload;
procedure WriteRowHeight(ARow: Cardinal; AHeight: Single;
ARowHeightType: TsRowHeightType = rhtCustom); overload; deprecated 'Use version with parameter AUnits';
procedure WriteColInfo(ACol: Cardinal; AData: TCol);
procedure WriteColFormatIndex(ACol: Cardinal; AFormatIndex: Integer);
procedure WriteColWidth(ACol: Cardinal; AWidth: Single; AUnits: TsSizeUnits;
AColWidthType: TsColWidthType = cwtCustom); overload;
procedure WriteColWidth(ACol: Cardinal; AWidth: Single;
AColWidthType: TsColWidthType = cwtCustom); overload; deprecated 'Use version with parameter AUnits';
procedure AddPageBreakToCol(ACol: Cardinal);
procedure AddPageBreakToRow(ARow: Cardinal);
function IsPageBreakCol(ACol: Cardinal): Boolean;
function IsPageBreakRow(ARow: Cardinal): Boolean;
procedure RemovePageBreakFromCol(ACol: Cardinal);
procedure RemovePageBreakFromRow(ARow: Cardinal);
function DefaultCompareCells(ACell1, ACell2: PCell; ASortKey: TsSortKey): Integer;
procedure Sort(const ASortParams: TsSortParams;
ARowFrom, AColFrom, ARowTo, AColTo: Cardinal); overload;
procedure Sort(ASortParams: TsSortParams; ARange: String); overload;
// Selected cell and ranges
procedure SelectCell(ARow, ACol: Cardinal);
procedure ClearSelection;
procedure DeleteSelection;
procedure EraseSelection(AKeepFormat: Boolean = false);
function GetSelection: TsCellRangeArray;
function GetSelectionAsString: String;
function GetSelectionCount: Integer;
function GetSelectionRangeIndexOfActiveCell: Integer;
procedure SetSelection(const ASelection: TsCellRangeArray);
procedure ScrollTo(ANewTopRow, ANewLeftCol: Cardinal);
// Comments
function FindComment(ACell: PCell): PsComment;
function HasComment(ACell: PCell): Boolean;
function ReadComment(ARow, ACol: Cardinal): String; overload;
function ReadComment(ACell: PCell): string; overload;
procedure RemoveComment(ACell: PCell);
function WriteComment(ARow, ACol: Cardinal; AText: String): PCell; overload;
procedure WriteComment(ACell: PCell; AText: String); overload;
// Hyperlinks
function FindHyperlink(ACell: PCell): PsHyperlink;
function ReadHyperlink(ACell: PCell): TsHyperlink;
procedure RemoveHyperlink(ACell: PCell);
function ValidHyperlink(AValue: String; out AErrMsg: String): Boolean;
function WriteHyperlink(ARow, ACol: Cardinal; ATarget: String;
ATooltip: String = ''): PCell; overload;
procedure WriteHyperlink(ACell: PCell; ATarget: String;
ATooltip: String = ''); overload;
{ Merged cells }
function FindMergeBase(ACell: PCell): PCell;
function FindMergedRange(ACell: PCell; out ARow1, ACol1, ARow2, ACol2: Cardinal): Boolean;
procedure MergeCells(ARow1, ACol1, ARow2, ACol2: Cardinal); overload;
procedure MergeCells(ARange: String); overload;
function InSameMergedRange(ACell1, ACell2: PCell): Boolean;
function IsMergeBase(ACell: PCell): Boolean;
function IsMerged(ACell: PCell): Boolean;
procedure UnmergeCells(ARow, ACol: Cardinal); overload;
procedure UnmergeCells(ARange: String); overload;
procedure DeleteFormula(ACell: PCell);
function ReadFormula(ARow, ACol: Cardinal): String; overload;
function ReadFormula(ACell: PCell): String; overload;
procedure UseFormulaInCell(ACell: PCell; AFormula: PsFormula);
{ Embedded images }
procedure CalcImageCell(AIndex: Integer; x, y, AWidth, AHeight: Double;
out ARow, ACol: Cardinal; out ARowOffs, AColOffs, AScaleX, AScaleY: Double);
procedure CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
out ARow1, ACol1, ARow2, ACol2: Cardinal;
out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;
out x, y, AWidth, AHeight: Double);
function GetImage(AIndex: Integer): TsImage;
function GetImageCount: Integer;
function GetPointerToImage(AIndex: Integer): PsImage;
procedure RemoveAllImages;
procedure RemoveImage(AIndex: Integer);
function WriteImage(ARow, ACol: Cardinal; AFileName: String;
AOffsetX: Double = 0.0; AOffsetY: Double = 0.0;
AScaleX: Double = 1.0; AScaleY: Double = 1.0): Integer; overload;
function WriteImage(ARow, ACol: Cardinal; AStream: TStream;
AOffsetX: Double = 0.0; AOffsetY: Double = 0.0; AScaleX: Double = 1.0;
AScaleY: Double = 1.0; ASize: Int64 = -1): Integer; overload;
function WriteImage(ARow, ACol: Cardinal; AImageIndex: Integer;
AScaleY: Double = 1.0): Integer; overload;
procedure AddHyperlinkToImage(AImageIndex: Integer; ATarget: String;
AToolTip: String = '');
{ Protection }
procedure Protect(AEnable: Boolean);
{ Hidden }
procedure Hide;
function IsHidden: Boolean; inline;
procedure Show;
{ Notification of changed cells, rows or columns }
procedure ChangedCell(ARow, ACol: Cardinal);
procedure ChangedCol(ACol: Cardinal);
procedure ChangedFont(ARow, ACol: Cardinal);
procedure ChangedRow(ARow: Cardinal);
{ Properties }
{@@ List of cells of the worksheet. Only cells with contents or with formatting
are listed }
property Cells: TsCells read FCells;
{@@ List of all column records of the worksheet having a non-standard column width }
property Cols: TIndexedAVLTree read FCols;
{@@ Information how the worksheet is encrypted }
property CryptoInfo: TsCryptoInfo read FCryptoInfo write FCryptoInfo;
{@@ List of all comment records }
property Comments: TsComments read FComments;
{@@ List of merged cells (contains TsCellRange records) }
property MergedCells: TsMergedCells read FMergedCells;
{@@ List of hyperlink information records }
property Hyperlinks: TsHyperlinks read FHyperlinks;
{@@ List of all formulas used in the sheet }
property Formulas: TsFormulas read FFormulas;
{@@ FormatSettings for localization of some formatting strings }
property FormatSettings: TFormatSettings read GetFormatSettings;
{@@ Index of the worksheet in the workbook }
property Index: Integer read GetIndex write SetIndex;
{@@ Parameters to be used for printing by the Office applications }
property PageLayout: TsPageLayout read FPageLayout write FPageLayout;
{@@ List of all row records of the worksheet having a non-standard row height }
property Rows: TIndexedAVLTree read FRows;
{@@ Color of the tab in the visual control - currently ignored }
property TabColor: TsColor read FTabColor write SetTabColor default scNotDefined;
{@@ Workbook to which the worksheet belongs }
property Workbook: TsWorkbook read FWorkbook;
{@@ The default column width given in "character units" (width of the
character "0" in the default font) }
property DefaultColWidth: Single read GetDefaultColWidth write SetDefaultColWidth;
deprecated 'Use Read/WriteDefaultColWidth';
{@@ The default row height is given in "line count" (height of the default font }
property DefaultRowHeight: Single read GetDefaultRowHeight write SetDefaultRowHeight;
{@@ In VirtualMode, the value of VirtualColCount signals how many colums
will be transferred to the worksheet. }
property VirtualColCount: cardinal read FVirtualColCount write SetVirtualColCount;
{@@ The value VirtualRowCount indicates how many rows will be transferred
to the worksheet in VirtualMode. }
property VirtualRowCount: cardinal read FVirtualRowCount write SetVirtualRowCount;
// These are properties to interface to TsWorksheetGrid
property BiDiMode: TsBiDiMode read FBiDiMode write SetBiDiMode;
{@@ Column index of the selected cell of this worksheet }
property ActiveCellCol: Cardinal read FActiveCellCol;
{@@ Row index of the selected cell of this worksheet }
property ActiveCellRow: Cardinal read FActiveCellRow;
{@@ Index of the left-most visible column in the grid - used by WorksheetGrid}
property LeftCol: Cardinal read FLeftCol;
{@@ Index of the top-most visible row in the grid - used by WorksheetGrid }
property TopRow: Cardinal read FTopRow;
{@@ Number of frozen columns which do not scroll }
property LeftPaneWidth: Integer read FLeftPaneWidth write FLeftPaneWidth;
{@@ Number of frozen rows which do not scroll }
property TopPaneHeight: Integer read FTopPaneHeight write FTopPaneHeight;
{@@ Zoom factor }
property ZoomFactor: Double read FZoomFactor write SetZoomFactor;
{@@ Event fired when cell contents or formatting changes }
property OnChangeCell: TsCellEvent read FOnChangeCell write FOnChangeCell;
{@@ Event fired when column height or formatting changes }
property OnChangeCol: TsColEvent read FOnChangeCol write FOnChangeCol;
{@@ Event fired when the font size in a cell changes }
property OnChangeFont: TsCellEvent read FOnChangeFont write FOnChangeFont;
{@@ Event fired when a row height or row formatting has changed }
property OnChangeRow: TsRowEvent read FOnChangeRow write FOnChangeRow;
{@@ Event to override cell comparison for sorting }
property OnCompareCells: TsCellCompareEvent
read FOnCompareCells write FOnCompareCells; deprecated 'Use OnFullCompareCells instead';
property OnFullCompareCells: TsCellFullCompareEvent read FOnFullCompareCells write FOnFullCompareCells;
{@@ Event fired when a cell is "selected". }
property OnSelectCell: TsCellEvent read FOnSelectCell write FOnSelectCell;
{@@ This event allows to provide external cell data for writing to file,
standard cells are ignored. Intended for converting large database files
to a spreadsheet format. Requires Option boVirtualMode to be set. }
property OnWriteCellData: TsWorksheetWriteCellDataEvent read FOnWriteCellData write FOnWriteCellData;
{@@ Event triggered when the worksheet is zoomed }
property OnZoom: TsNotifyEvent read FOnZoom write FOnZoom;
end;
{@@ Event fired when reading a file in virtual mode. Read data are provided in
the "ADataCell" (which is not added to the worksheet in virtual mode). }
TsWorkbookReadCellDataEvent = procedure(Sender: TObject; ARow, ACol: Cardinal;
const ADataCell: PCell) of object;
{@@ Event procedure containing a specific worksheet }
TsWorksheetEvent = procedure (Sender: TObject; ASheet: TsWorksheet) of object;
{@@ Event procedure containing a specific workbook }
TsWorkbookEvent = procedure (Sender: TsWorkbook) of object;
{@@ Event procedure called when a worksheet is removed. ASheetIndex = -1 --> all sheets }
TsRemoveWorksheetEvent = procedure (Sender: TObject; ASheetIndex: Integer) of object;
{@@ FSome action has an effect on existing formulas which must be corrected. }
TsFormulaCorrection = (fcWorksheetRenamed, fcWorksheetDeleted);
{ TsWorkbook }
{@@ The workbook contains the worksheets and provides methods for reading from
and writing to file. }
TsWorkbook = class(TsBasicWorkbook)
{ Internal data }
FWorksheets: TFPList;
FBuiltinFontCount: Integer;
FReadWriteFlag: TsReadWriteFlag;
FCalculationLock: Integer;
FDeleteFormulaLock: Integer;
FNotificationLock: Integer;
FRebuildFormulaLock: Integer;
FActiveWorksheet: TsWorksheet;
FOnOpenWorkbook: TNotifyEvent;
FOnCalcWorkbook: TsWorkbookEvent;
FOnChangeWorksheet: TsWorksheetEvent;
FOnRenameWorksheet: TsWorksheetEvent;
FOnAddWorksheet: TsWorksheetEvent;
FOnRemoveWorksheet: TsRemoveWorksheetEvent;
FOnRemovingWorksheet: TsWorksheetEvent;
FOnSelectWorksheet: TsWorksheetEvent;
FOnReadCellData: TsWorkbookReadCellDataEvent;
FSearchEngine: TObject;
{FrevisionsCrypto: TsCryptoInfo;} // Commented out because it needs revision handling
{ Callback procedures }
procedure RebuildFormulasCallback(Data, Arg: Pointer);
procedure RemoveWorksheetsCallback(Data, Arg: pointer);
FFontList: TFPList;
FNumFormatList: TFPList;
FCellFormatList: TsCellFormatList;
FConditionalFormatList: TsConditionalFormatList;
FEmbeddedObjList: TFPList;
{ Internal methods }
class procedure GetFormatFromFileHeader(const AFileName: TFileName;
out AFormatIDs: TsSpreadFormatIDArray); overload;
class procedure GetFormatFromFileHeader(AStream: TStream;
procedure PrepareBeforeReading;
procedure PrepareBeforeSaving;
function FixFormula(AFormula: PsFormula; ACorrection: TsFormulaCorrection;
AData: Pointer; AParam: PtrInt): Boolean;
procedure MoveSheet(AFromIndex, AToIndex: Integer);
procedure ReadFromFile(AFileName: string; AFormatID: TsSpreadFormatID;
APassword: String = ''; AParams: TsStreamParams = []); overload;
procedure ReadFromFile(AFileName: string; AFormat: TsSpreadsheetFormat;
AParams: TsStreamParams = []); overload;
procedure ReadFromFile(AFileName: string; APassword: String = '';
procedure ReadFromFileIgnoringExtension(AFileName: string;
APassword: String = ''; AParams: TsStreamParams = []);
procedure ReadFromStream(AStream: TStream; AFormatID: TsSpreadFormatID;
procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
procedure WriteToFile(const AFileName: string; const AFormatID: TsSpreadFormatID;
const AOverwriteExisting: Boolean = False; AParams: TsStreamParams = []); overload;
procedure WriteToFile(const AFileName: string; const AFormat: TsSpreadsheetFormat;
procedure WriteToFile(const AFileName: String;
procedure WriteToStream(AStream: TStream; AFormatID: TsSpreadFormatID;
procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
{ Worksheet list handling methods }
function AddWorksheet(AName: string;
ReplaceDuplicateName: Boolean = false): TsWorksheet;
function CopyWorksheetFrom(AWorksheet: TsWorksheet;
ReplaceDuplicateName: Boolean): TsWorksheet;
function GetFirstWorksheet: TsWorksheet;
function GetLastWorksheet: TsWorksheet;
function GetNextWorksheet(AWorksheet: TsWorksheet): TsWorksheet;
function GetPreviousWorksheet(AWorksheet: TsWorksheet): TsWorksheet;
function GetWorksheetByIndex(AIndex: Integer): TsWorksheet;
function GetWorksheetByName(AName: String): TsWorksheet;
function GetWorksheetCount: Integer;
function GetVisibleWorksheetCount: Integer;
function GetWorksheetIndex(AWorksheet: TsBasicWorksheet): Integer; overload;
function GetWorksheetIndex(const AWorksheetName: String): Integer; overload;
procedure RemoveAllWorksheets;
procedure RemoveAllEmptyWorksheets;
procedure RemoveWorksheet(AWorksheet: TsWorksheet);
procedure SelectWorksheet(AWorksheet: TsWorksheet);
function ValidWorksheetName(var AName: String;
ReplaceDuplicateName: Boolean = false): Boolean;
{ String-to-cell/range conversion }
function TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
out ARow,ACol: Cardinal; AListSeparator: Char = #0): Boolean;
function TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
out ARange: TsCellRange; AListSeparator: Char = #0): Boolean;
function TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
out ARanges: TsCellRangeArray; AListSeparator: Char = #0): Boolean;
{ Cell format handling }
function AddCellFormat(const AValue: TsCellFormat): Integer;
function GetCellFormat(AIndex: Integer): TsCellFormat;
function GetCellFormatAsString(AIndex: Integer): String;
function GetNumCellFormats: Integer;
function GetPointerToCellFormat(AIndex: Integer): PsCellFormat;
procedure RemoveAllCellFormats(AKeepDefaultFormat: Boolean);
function GetConditionalFormat(AIndex: Integer): TsConditionalFormat;
function GetNumConditionalFormats: Integer;
{ Font handling }
function AddFont(const AFontName: String; ASize: Single; AStyle: TsFontStyles;
AColor: TsColor; APosition: TsFontPosition = fpNormal): Integer; overload;
function AddFont(const AFont: TsFont): Integer; overload;
function CloneFont(const AFontIndex: Integer): TsFont;
procedure DeleteFont(const AFontIndex: Integer);
function FindFont(const AFontName: String; ASize: Single; AStyle: TsFontStyles;
AColor: TsColor; APosition: TsFontPosition = fpNormal): Integer;
function GetBuiltinFontCount: Integer;
function GetDefaultFont: TsFont;
function GetDefaultFontSize: Single;
function GetFont(AIndex: Integer): TsFont;
function GetFontAsString(AIndex: Integer): String;
function GetFontCount: Integer;
function GetHyperlinkFont: TsFont;
procedure InitFonts;
procedure RemoveAllFonts;
procedure ReplaceFont(AFontIndex: Integer; AFontName: String;
ASize: Single; AStyle: TsFontStyles; AColor: TsColor;
APosition: TsFontPosition = fpNormal);
procedure SetDefaultFont(const AFontName: String; ASize: Single);
{ Number format handling }
function AddNumberFormat(AFormatStr: String): Integer;
function GetNumberFormat(AIndex: Integer): TsNumFormatParams;
function GetNumberFormatCount: Integer;
procedure RemoveAllNumberFormats;
function FixFormulas(ACorrection: TsFormulaCorrection; AData: Pointer;
AParam: PtrInt): boolean;
procedure RebuildFormulas;
procedure LockFormulas;
procedure UnlockFormulas;
{ Clipboard }
procedure CopyToClipboardStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
AParams: TsStreamParams = []);
procedure PasteFromClipboardStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
AOperation: TsCopyOperation; AParams: TsStreamParams = [];
ATransposed: Boolean = false);
{ Embedded objects }
function AddEmbeddedObj(const AFileName: String): Integer; overload;
function AddEmbeddedObj(AStream: TStream;
const AName: String = ''; ASize: Int64 = -1): Integer; overload;
function FindEmbeddedObj(const AFileName: String): Integer;
function GetEmbeddedObj(AIndex: Integer): TsEmbeddedObj;
function GetEmbeddedObjCount: Integer;
function HasEmbeddedSheetImages: Boolean;
procedure RemoveAllEmbeddedObj;
{ Utilities }
function ConvertUnits(AValue: Double; AFromUnits, AToUnits: TsSizeUnits): Double;
procedure GetLastRowColIndex(out ALastRow, ALastCol: Cardinal);
{ Notification }
procedure ChangedWorksheet(AWorksheet: TsWorksheet);
procedure DisableNotifications;
procedure EnableNotifications;
function NotificationsEnabled: Boolean;
{@@ Identifies the "active" worksheet (only for visual controls)}
property ActiveWorksheet: TsWorksheet read FActiveWorksheet write SelectWorksheet;
{property RevisionsCrypto: TsCryptoInfo read FRevisionsCrypto write FRevisionsCrypto;}
{@@ This event fires whenever a new worksheet is added }
property OnAddWorksheet: TsWorksheetEvent read FOnAddWorksheet write FOnAddWorksheet;
{@@ This event fires whenever a worksheet is changed }
property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet;
{@@ This event fires whenever a workbook is loaded }
property OnOpenWorkbook: TNotifyEvent read FOnOpenWorkbook write FOnOpenWorkbook;
{@@ This event fires whenever a worksheet is renamed }
property OnRenameWorksheet: TsWorksheetEvent read FOnRenameWorksheet write FOnRenameWorksheet;
{@@ This event fires AFTER a worksheet has been deleted }
property OnRemoveWorksheet: TsRemoveWorksheetEvent read FOnRemoveWorksheet write FOnRemoveWorksheet;
{@@ This event fires BEFORE a worksheet is deleted }
property OnRemovingWorksheet: TsWorksheetEvent read FOnRemovingWorksheet write FOnRemovingWorksheet;
{@@ This event fires when a worksheet is made "active"}
property OnSelectWorksheet: TsWorksheetEvent read FOnSelectWorksheet write FOnSelectWorksheet;
{@@ This event accepts cell data while reading a spreadsheet file. Data are
not encorporated in a spreadsheet, they are just passed through to the
event handler for processing. Requires option boVirtualMode to be set. }
property OnReadCellData: TsWorkbookReadCellDataEvent read FOnReadCellData write FOnReadCellData;
{@@ This event is fired when the workbook is recalculated. It allows to
replace the calculation strategy. }
property OnCalcWorkbook: TsWorkbookEvent read FOnCalcWorkbook write FOnCalcWorkbook;
procedure CopyCellFormat(AFromCell, AToCell: PCell);
procedure CopyColFormat(AFromCol, AToCol: PCol; AFromSheet, AToSheet: TsWorksheet);
procedure CopyRowFormat(AFromRow, AToRow: PRow; AFromSheet, AToSheet: TsWorksheet);
implementation
Math, StrUtils, DateUtils, TypInfo, lazutf8, lazFileUtils, URIParser,
{%H-}fpsPatches, fpsStrings, fpsUtils, fpsHTMLUtils,
fpsReaderWriter, fpsCurrency;
(*
const
{ These are reserved system colors by Microsoft
0x0040 - Default foreground color - window text color in the sheet display.
0x0041 - Default background color - window background color in the sheet
display and is the default background color for a cell.
0x004D - Default chart foreground color - window text color in the
chart display.
0x004E - Default chart background color - window background color in the
0x004F - Chart neutral color which is black, an RGB value of (0,0,0).
0x0051 - ToolTip text color - automatic font color for comments.
0x7FFF - Font automatic color - window text color. }
// Color indexes of reserved system colors
DEF_FOREGROUND_COLOR = $0040;
DEF_BACKGROUND_COLOR = $0041;
DEF_CHART_FOREGROUND_COLOR = $004D;
DEF_CHART_BACKGROUND_COLOR = $004E;
DEF_CHART_NEUTRAL_COLOR = $004F;
DEF_TOOLTIP_TEXT_COLOR = $0051;
DEF_FONT_AUTOMATIC_COLOR = $7FFF;
// Color rgb values of reserved system colors
DEF_FOREGROUND_COLORVALUE = $000000;
DEF_BACKGROUND_COLORVALUE = $FFFFFF;
DEF_CHART_FOREGROUND_COLORVALUE = $000000;
DEF_CHART_BACKGROUND_COLORVALUE = $FFFFFF;
DEF_CHART_NEUTRAL_COLORVALUE = $FFFFFF;
DEF_TOOLTIP_TEXT_COLORVALUE = $000000;
DEF_FONT_AUTOMATIC_COLORVALUE = $000000;
*)
Convenience method which creates the correct reader object for a given
spreadsheet format.
@param AWorkbook Workbook to be written
@param AFormatID Identifier of the file format which is assumed when reading
a document into the workbook. An exception is raised when
the document has a different format.
@param AParams Optional parameters to control stream access. If contains
the element spClipboard the reader knows that access is to
the clipboard, and it can read a special clipboard version
of the data.
@return An instance of a TsBasicSpreadReader descendent which is able to
read the given file format.
function CreateSpreadReader(AWorkbook: TsWorkbook; AFormatID: TsSpreadFormatID;
AParams: TsStreamParams = []): TsBasicSpreadReader;
var
readerClass: TsSpreadReaderClass;
begin
Result := nil;
Unused(AParams);
readerClass := GetSpreadReaderClass(AFormatID);
if readerClass <> nil
then Result := readerClass.Create(AWorkbook);
if Result = nil then
raise EFPSpreadsheetReader.Create(rsUnsupportedReadFormat);
Convenience method which creates the correct writer object for a given
@param AFormatID Identifier of the file format which is used for writing the
workbook
the element spClipboard then the writer can write a
dedicated clipboard version of the stream if required.
@return An instance of a TsBasicSpreadWriter descendant which is able to
write the given file format.
function CreateSpreadWriter(AWorkbook: TsWorkbook; AFormatID: TsSpreadFormatID;
AParams: TsStreamParams = []): TsBasicSpreadWriter;
writerClass: TsSpreadWriterClass;
writerClass := GetSpreadWriterClass(AFormatID);
if writerClass <> nil then
Result := writerClass.Create(AWorkbook);
raise EFPSpreadsheetWriter.Create(rsUnsupportedWriteFormat);
Copies the format of a cell to another one.
@param AFromCell Cell from which the format is to be copied
@param AToCell Cell to which the format is to be copied
sourceSheet, destSheet: TsWorksheet;
fmt: TsCellFormat;
numFmtParams: TsNumFormatParams;
nfs: String;
font: TsFont;
Assert(AFromCell <> nil);
Assert(AToCell <> nil);
sourceSheet := TsWorksheet(AFromCell^.Worksheet);
destSheet := TsWorksheet(AToCell^.Worksheet);
if (sourceSheet=nil) or (destSheet=nil) or (sourceSheet.Workbook = destSheet.Workbook) then
AToCell^.FormatIndex := AFromCell^.FormatIndex
else
fmt := sourceSheet.ReadCellFormat(AFromCell);
if (uffFont in fmt.UsedFormattingFields) then
font := sourceSheet.ReadCellFont(AFromCell);
fmt.FontIndex := destSheet.Workbook.FindFont(font.FontName, font.Size, font.Style, font.Color);
if fmt.FontIndex = -1 then
fmt.FontIndex := destSheet.Workbook.AddFont(font.FontName, font.Size, font.Style, font.Color);
if (uffNumberformat in fmt.UsedFormattingFields) then
numFmtParams := sourceSheet.Workbook.GetNumberFormat(fmt.NumberFormatIndex);
if numFmtParams <> nil then
nfs := numFmtParams.NumFormatStr;
fmt.NumberFormatIndex := destSheet.Workbook.AddNumberFormat(nfs);
destSheet.WriteCellFormat(AToCell, fmt);
if (AFromSheet = nil) or (AToSheet = nil) or (AFromSheet.Workbook = AToSheet.Workbook) then
// Both columns in the same sheet --> the format index is valid
AToCol^.FormatIndex := AFromCol^.FormatIndex
// Both columns in different worksheets. We must create a new format record
// in the destination sheet from the format used by the source column
// and store the new format index in the column record of the dest col.
fmt := AFromSheet.Workbook.GetCellFormat(AFromCol^.FormatIndex);
font := AFromSheet.Workbook.GetFont(fmt.FontIndex);
fmt.FontIndex := AToSheet.Workbook.FindFont(font.FontName, font.Size, font.Style, font.Color);
fmt.FontIndex := AToSheet.Workbook.AddFont(font.FontName, font.Size, font.Style, font.Color);
numFmtParams := AFromSheet.Workbook.GetNumberFormat(fmt.NumberFormatIndex);
fmt.NumberFormatIndex := AToSheet.Workbook.AddNumberFormat(nfs);
AToCol^.FormatIndex := AToSheet.Workbook.AddCellFormat(fmt);
// Both rows are in the same sheet --> the format index is valid
AToRow^.FormatIndex := AFromRow^.FormatIndex
// Both rows are in different worksheets. We must create a new format record
// in the destination sheet from the format used by the source row
// and store the new format index in the row record of the dest row.
fmt := AFromSheet.Workbook.GetCellFormat(AFromRow^.FormatIndex);
AToRow^.FormatIndex := AToSheet.Workbook.AddCellFormat(fmt);
function CompareCells(Item1, Item2: Pointer): Integer;
result := LongInt(PCell(Item1)^.Row) - PCell(Item2)^.Row;
if Result = 0 then
Result := LongInt(PCell(Item1)^.Col) - PCell(Item2)^.Col;
function CompareRows(Item1, Item2: Pointer): Integer;
Result := LongInt(PRow(Item1)^.Row) - PRow(Item2)^.Row;
function CompareCols(Item1, Item2: Pointer): Integer;
Result := LongInt(PCol(Item1)^.Col) - PCol(Item2)^.Col;
function CompareMergedCells(Item1, Item2: Pointer): Integer;
Result := LongInt(PsCellRange(Item1)^.Row1) - PsCellRange(Item2)^.Row1;
Result := LongInt(PsCellRange(Item1)^.Col1) - PsCellRange(Item2)^.Col1;
{==============================================================================}
Constructor of the TsWorksheet class.
constructor TsWorksheet.Create;
inherited Create;
FCells := TsCells.Create(self);
FRows := TIndexedAVLTree.Create(@CompareRows);
FCols := TIndexedAVLTree.Create(@CompareCols);
FComments := TsComments.Create;
FMergedCells := TsMergedCells.Create;
FHyperlinks := TsHyperlinks.Create;
FFormulas := TsFormulas.Create;
FImages := TFPList.Create;
FPageLayout := TsPageLayout.Create(self);
FDefaultColWidth := ptsToMM(72); // Excel: about 72 pts
FDefaultRowHeight := ptsToMM(15); // Excel: 15pts
FZoomFactor := 1.0;
FTabColor := scNotDefined;
FFirstRowIndex := UNASSIGNED_ROW_COL_INDEX;
FFirstColIndex := UNASSIGNED_ROW_COL_INDEX;
FLastRowIndex := UNASSIGNED_ROW_COL_INDEX;
FLastColIndex := UNASSIGNED_ROW_COL_INDEX;
FActiveCellRow := UNASSIGNED_ROW_COL_INDEX;
FActiveCellCol := UNASSIGNED_ROW_COL_INDEX;
InitCryptoInfo(FCryptoInfo);
FOptions := [soShowGridLines, soShowHeaders, soAutoDetectCellType];
Destructor of the TsWorksheet class.
Releases all memory, but does not delete from the workbook's worksheetList !!!
NOTE: Don't call directly. Always use Workbook.RemoveWorksheet to remove a
worksheet from a workbook.
destructor TsWorksheet.Destroy;
RemoveAllImages;
RemoveAllRows;
RemoveAllCols;
FPageLayout.Free;
FCells.Free;
FRows.Free;
FCols.Free;
FComments.Free;
FMergedCells.Free;
FHyperlinks.Free;
FFormulas.Free;
FImages.Free;
inherited Destroy;
Helper function which constructs an rpn formula from the cell's string
formula. This is needed, for example, when writing a formula to xls biff
file format.
The formula is stored in ACell.
If ADestCell is not nil then the relative references are adjusted as seen
from ADestCell. This means that this function returns the formula that
would be created if ACell is copied to the location of ADestCell.
Needed for copying formulas and for splitting shared formulas.
function TsWorksheet.BuildRPNFormula(ACell: PCell;
ADestCell: PCell = nil): TsRPNFormula;
formula: PsFormula;
if (ACell = nil) or (not HasFormula(ACell)) then begin
SetLength(Result, 0);
exit;
formula := FFormulas.FindFormula(ACell^.Row, ACell^.Col);
if formula = nil then begin
if ADestCell <> nil then begin
formula^.Parser.PrepareCopyMode(ACell, ADestCell);
Result := formula^.Parser.RPNFormula;
formula^.Parser.PrepareCopyMode(nil, nil);
end else
Calculates the provided formula
Should not be called by itself because the result may depend on other formulas
which may have not yet been calculated. It is better to call CalcFormulas
instead.
@param AFormula Formula to be calculated. The formula belongs to the
cell specified by the formula's Row and Col parameters.
procedure TsWorksheet.CalcFormula(AFormula: PsFormula);
lCell, lCellRef: PCell;
parser: TsExpressionParser = nil;
res: TsExpressionResult;
p: Integer;
link, txt: String;
if (boIgnoreFormulas in Workbook.Options) or (AFormula = nil) then
if (AFormula^.Text = '') and (AFormula^.Parser = nil) then
raise ECalcEngine.Create('CalcFormula: no formula specified.');
AFormula^.CalcState := csCalculating;
if AFormula^.Parser = nil then begin
parser := TsSpreadsheetParser.Create(self);
try
parser.Expression[fdExcelA1] := AFormula^.Text;
AFormula^.Parser := parser;
except
on E:ECalcEngine do begin
Workbook.AddErrorMsg(E.Message);
res := ErrorResult(errIllegalRef);
if AFormula^.Parser <> nil then
res := AFormula^.Parser.Evaluate;
if AFormula^.Text = '' then
AFormula^.Text := AFormula^.Parser.Expression[fdExcelA1];
on E: ECalcEngine do
// Find or create the formula cell
lCell := GetCell(AFormula^.Row, AFormula^.Col);
FWorkbook.LockFormulas;
// Assign formula result
case res.ResultType of
rtEmpty : WriteBlank(lCell, true);
rtError : WriteErrorValue(lCell, res.ResError);
rtInteger : WriteNumber(lCell, res.ResInteger);
rtFloat : WriteNumber(lCell, res.ResFloat);
rtDateTime : WriteDateTime(lCell, res.ResDateTime);
rtString : WriteText(lCell, res.ResString);
rtHyperlink : begin
link := ArgToString(res);
p := pos(HYPERLINK_SEPARATOR, link);
if p > 0 then
txt := Copy(link, p+Length(HYPERLINK_SEPARATOR), Length(link));
link := Copy(link, 1, p-1);
txt := link;
WriteHyperlink(lCell, link);
WriteText(lCell, txt);
rtBoolean : WriteBoolValue(lCell, res.ResBoolean);
rtCell : begin
lCellRef := (res.Worksheet as TsWorksheet).FindCell(res.ResRow, res.ResCol);
if lCellRef <> nil then
case lCellRef^.ContentType of
cctNumber : WriteNumber(lCell, lCellRef^.NumberValue);
cctDateTime : WriteDateTime(lCell, lCellRef^.DateTimeValue);
cctUTF8String: WriteText(lCell, lCellRef^.UTF8StringValue);
cctBool : WriteBoolValue(lCell, lCellRef^.Boolvalue);
cctError : WriteErrorValue(lCell, lCellRef^.ErrorValue);
cctEmpty : WriteBlank(lCell, true);
end
WriteBlank(lCell, true);
finally
FWorkbook.UnlockFormulas;
// Restore the formula. Could have been erased by WriteBlank or WriteText('')
AFormula^.CalcState := csCalculated;
Calculates all formulas of the workbook
Must be used when the formulas in the workbook contain references to other
sheets.
If this is not the case the faster "CalcSheet" can be used.
procedure TsWorksheet.CalcFormulas;
Workbook.CalcFormulas;
// To do: Determine whether the worksheet has in- and out-going links
// to others sheets. If not call the faster "CalcSheet".
Calculates all formulas of the worksheet
Since formulas may reference not-yet-calculated cells, this occurs in
two steps:
1. All formulas are marked as "not calculated".
2. Formulas are calculated. If formulas in referenced are found as being
"not calculated" they are calculated and then tagged as "calculated".
This results in an iterative calculation procedure. In the end, all formulas
are calculated.
NOTE: IF THE WORKSHEET CONTAINS CELLS WHICH LINK TO OTHER WORKSHEETS THEN
THIS CALCULATION MAY NOT BE CORRECT. USE THE METHOD CalcFormulas OF THE
WORKBOOK INSTEAD !!!
procedure TsWorksheet.CalcSheet;
if (boIgnoreFormulas in Workbook.Options) then
{ prevent infinite loop due to triggerung of formula recalculation whenever
a cell changes during execution of CalcFormulas }
inc(FWorkbook.FCalculationLock);
// State 1 - mark all formulas as "not calculated"
for formula in FFormulas do
formula^.CalcState := csNotCalculated;
// State 2 - calculate formulas. If a formula required during calculation
// is found as not-yet-calculated, then it is calculated immediately.
CalcFormula(formula);
dec(FWorkbook.FCalculationLock);
Checks whether a cell given by its row and column indexes belongs to a
specified rectangular cell range.
class function TsWorksheet.CellInRange(ARow, ACol: Cardinal;
ARange: TsCellRange): Boolean;
Result := (ARow >= ARange.Row1) and (ARow <= ARange.Row2) and
(ACol >= ARange.Col1) and (ACol <= ARange.Col2);
Converts a FPSpreadsheet cell position, which is Row, Col in numbers
and zero based - e.g. 0,0 - to a textual representation which is [Col][Row],
where the Col is in letters and the row is in 1-based numbers - e.g. A1
class function TsWorksheet.CellPosToText(ARow, ACol: Cardinal): string;
Result := GetCellString(ARow, ACol, [rfRelCol, rfRelRow]);
Checks entire worksheet, whether this cell is used in any formula.
@param ARow Row index of the cell considered
@param ACol Column index of the cell considered
@return TRUE if the cell is used in a formula, FALSE if not
function TsWorksheet.CellUsedInFormula(ARow, ACol: Cardinal): Boolean;
cell: PCell;
fe: TsFormulaElement;
i: Integer;
rpnFormula: TsRPNFormula;
for cell in FCells do
if HasFormula(cell) then begin
if (cell^.Row = ARow) and (cell^.Col = ACol) then
Result := true;
rpnFormula := BuildRPNFormula(cell);
for i := 0 to Length(rpnFormula)-1 do
fe := rpnFormula[i];
case fe.ElementKind of
fekCell, fekCellRef:
if (fe.Row = ARow) and (fe.Col = ACol) then
fekCellRange:
if (fe.Row <= ARow) and (ARow <= fe.Row2) and
(fe.Col <= ACol) and (ACol <= fe.Col2) then
SetLength(rpnFormula, 0);
Result := false;
Checks whether a cell contains a comment and returns a pointer to the
comment data.
@param ACell Pointer to the cell
@return Pointer to the TsComment record (nil, if the cell does not have a
comment)
function TsWorksheet.FindComment(ACell: PCell): PsComment;
if HasComment(ACell) then
Result := PsComment(FComments.FindByRowCol(ACell^.Row, ACell^.Col))
Checks whether a specific cell contains a comment
function TsWorksheet.HasComment(ACell: PCell): Boolean;
Result := (ACell <> nil) and (cfHasComment in ACell^.Flags);
Returns the comment text attached to a specific cell
@param ARow (0-based) index to the row
@param ACol (0-based) index to the column
@return Text assigned to the cell as a comment
function TsWorksheet.ReadComment(ARow, ACol: Cardinal): String;
comment: PsComment;
Result := '';
comment := PsComment(FComments.FindByRowCol(ARow, ACol));
if comment <> nil then
Result := comment^.Text;
function TsWorksheet.ReadComment(ACell: PCell): String;
comment := FindComment(ACell);
Adds a comment to a specific cell
@param ARow (0-based) row index of the cell
@param ACol (0-based) column index of the cell
@param AText Comment text
@return Pointer to the cell containing the comment
function TsWorksheet.WriteComment(ARow, ACol: Cardinal; AText: String): PCell;
Result := GetCell(ARow, ACol);
WriteComment(Result, AText);
procedure TsWorksheet.WriteComment(ACell: PCell; AText: String);
if ACell = nil then
// Remove the comment if an empty string is passed
if AText = '' then
RemoveComment(ACell);
// Add new comment record
FComments.AddComment(ACell^.Row, ACell^.Col, AText);
Include(ACell^.Flags, cfHasComment);
ChangedCell(ACell^.Row, ACell^.Col);
{ Hyperlinks }
Checks whether the specified cell contains a hyperlink and returns a pointer
to the hyperlink data.
@return Pointer to the TsHyperlink record, or NIL if the cell does not contain
a hyperlink.
function TsWorksheet.FindHyperlink(ACell: PCell): PsHyperlink;
if HasHyperlink(ACell) then
Result := PsHyperlink(FHyperlinks.FindByRowCol(ACell^.Row, ACell^.Col))
Reads the hyperlink information of a specified cell.
@param ACell Pointer to the cell considered
@returns Record with the hyperlink data assigned to the cell.
If the cell is not a hyperlink the result field Kind is hkNone.
function TsWorksheet.ReadHyperlink(ACell: PCell): TsHyperlink;
hyperlink: PsHyperlink;
hyperlink := FindHyperlink(ACell);
if hyperlink <> nil then
Result := hyperlink^
Result.Row := ACell^.Row;
Result.Col := ACell^.Col;
Result.Target := '';
Result.Tooltip := '';
Removes a hyperlink from the specified cell. Releaes memory occupied by
the associated TsHyperlink record. Cell content type is converted to
cctUTF8String.
procedure TsWorksheet.RemoveHyperlink(ACell: PCell);
FHyperlinks.DeleteHyperlink(ACell^.Row, ACell^.Col);
Exclude(ACell^.Flags, cfHyperlink);
Checks whether the passed string represents a valid hyperlink target
@param AValue String to be checked. Must be either a fully qualified URI,
a local relative (!) file name, or a # followed by a cell
address in the current workbook
@param AErrMsg Error message in case that the string is not correct.
@returns TRUE if the string is correct, FALSE otherwise
function TsWorksheet.ValidHyperlink(AValue: String; out AErrMsg: String): Boolean;
u: TUri;
sheet: TsWorksheet;
r, c: Cardinal;
AErrMsg := '';
if AValue = '' then
AErrMsg := rsEmptyHyperlink;
if (AValue[1] = '#') then
Delete(AValue, 1, 1);
if not FWorkbook.TryStrToCell(AValue, sheet, r, c) then
AErrMsg := Format(rsNoValidHyperlinkInternal, ['#'+AValue]);
u := ParseURI(AValue);
if SameText(u.Protocol, 'mailto') then
Result := true; // To do: Check email address here...
if SameText(u.Protocol, 'file') then
if FilenameIsAbsolute(u.Path + u.Document) then
AErrMsg := Format(rsLocalfileHyperlinkAbs, [AValue]);
Assigns a hyperlink to the cell at the specified row and column
Cell content is not affected by the presence of a hyperlink.
@param ATarget Hyperlink address given as a fully qualitifed URI for
external links, or as a # followed by a cell address
for internal links.
@param ATooltip Text for popup tooltip hint used by Excel
@returns Pointer to the cell with the hyperlink
function TsWorksheet.WriteHyperlink(ARow, ACol: Cardinal; ATarget: String;
ATooltip: String = ''): PCell;
WriteHyperlink(Result, ATarget, ATooltip);
Assigns a hyperlink to the specified cell.
for internal links. Local files can be specified also
by their name relative to the workbook.
An existing hyperlink is removed if ATarget is empty.
procedure TsWorksheet.WriteHyperlink(ACell: PCell; ATarget: String;
ATooltip: String = '');
function GetDisplayText(ATarget: String): String;
target, bm: String;
SplitHyperlink(ATarget, target, bm);
if pos('file:', lowercase(ATarget))=1 then
URIToFilename(target, Result);
ForcePathDelims(Result);
if bm <> '' then Result := Result + '#' + bm;
if target = '' then
Result := bm
Result := ATarget;
noCellText: Boolean = false;
fmt := ReadCellFormat(ACell);
// Empty target string removes the hyperlink. Resets the font from hyperlink
// to default font.
if ATarget = '' then begin
RemoveHyperlink(ACell);
if fmt.FontIndex = HYPERLINK_FONTINDEX then
WriteFont(ACell, DEFAULT_FONTINDEX);
// Detect whether the cell already has a hyperlink, but has no other content.
noCellText := (ACell^.ContentType = cctUTF8String) and
(GetDisplayText(ReadHyperlink(ACell).Target) = ReadAsText(ACell));
// Attach the hyperlink to the cell
FHyperlinks.AddHyperlink(ACell^.Row, ACell^.Col, ATarget, ATooltip);
Include(ACell^.Flags, cfHyperlink);
// If there is no other cell content use the target as cell label string.
if (ACell^.ContentType = cctEmpty) or noCellText then
ACell^.ContentType := cctUTF8String;
ACell^.UTF8StringValue := GetDisplayText(ATarget);
// Select the hyperlink font.
if fmt.FontIndex = DEFAULT_FONTINDEX then
fmt.FontIndex := HYPERLINK_FONTINDEX;
Include(fmt.UsedFormattingFields, uffFont);
ACell^.FormatIndex := FWorkbook.AddCellFormat(fmt);
Is called whenever a cell value or formatting has changed. Fires an event
"OnChangeCell". This is handled by TsWorksheetGrid to update the grid cell.
@param ARow Row index of the cell which has been changed
@param ACol Column index of the cell which has been changed
procedure TsWorksheet.ChangedCell(ARow, ACol: Cardinal);
if FWorkbook.FReadWriteFlag = rwfRead then
if (FWorkbook.FCalculationLock = 0) and (boAutoCalc in FWorkbook.Options) then
// if CellUsedInFormula(ARow, ACol) then
CalcFormulas;
if FWorkbook.NotificationsEnabled and Assigned(FOnChangeCell) then
FOnChangeCell(Self, ARow, ACol);
Is called whenever a column width or column format has changed. Fires an event
"OnChangedCol" which is handled by TsWorkbookSource
@param ACol Index of the column which as changed
procedure TsWorksheet.ChangedCol(ACol: Cardinal);
if FWorkbook.NotificationsEnabled and Assigned(FOnChangeCol) then
FOnChangeCol(Self, ACol);
Is called whenever a row height or row format has changed. Fires an event
"OnChangedRow" which is handled by TsWorkbookSource
@param ARow Index of the row which as changed
procedure TsWorksheet.ChangedRow(ARow: Cardinal);
if FWorkbook.NotificationsEnabled and Assigned(FOnChangeRow) then
FOnChangeRow(Self, ARow);
Is called whenever a font height changes. Fires an even "OnChangeFont"
which is handled by TsWorksheetGrid to update the row heights.
@param ARow Row index of the cell for which the font height has changed
@param ACol Column index of the cell for which the font height has changed.
procedure TsWorksheet.ChangedFont(ARow, ACol: Cardinal);
if (FWorkbook.FReadWriteFlag = rwfRead) or not FWorkbook.NotificationsEnabled then
if Assigned(FOnChangeFont) then
FOnChangeFont(Self, ARow, ACol);
Copies a cell to a cell at another location. The new cell has the same values
and the same formatting. It differs in formula (adapted relative references)
and col/row indexes.
Both cells can be in different worksheets.
@param FromCell Pointer to the source cell which will be copied
@param ToCell Pointer to the destination cell
procedure TsWorksheet.CopyCell(AFromCell, AToCell: PCell);
toRow, toCol: LongInt;
row1, col1, row2, col2: Cardinal;
fnt: TsFont;
fntIndex: Integer;
srcSheet, destSheet: TsWorksheet;
if (AFromCell = nil) or (AToCell = nil) then
// Short-cut for source and destination worksheets
srcSheet := TsWorksheet(AFromcell^.Worksheet);
// Remember the row and column indexes of the destination cell.
toRow := AToCell^.Row;
toCol := AToCell^.Col;
// Avoid misplaced notifications during the copy operations when things could
// not yet be in place.
FWorkbook.DisableNotifications;
// Copy cell values and flags
AToCell^ := AFromCell^;
// Restore row and column indexes overwritten by the previous instruction
AToCell^.Row := toRow;
AToCell^.Col := toCol;
AToCell^.Worksheet := destSheet; // restore overwritten destination worksheet
// was: self;
// Fix relative references in formulas
// This also fires the OnChange event.
CopyFormula(AFromCell, AToCell);
// Copy cell format
CopyCellFormat(AFromCell, AToCell);
// Merged?
if srcSheet.IsMergeBase(AFromCell) then
srcSheet.FindMergedRange(AFromCell, row1, col1, row2, col2);
MergeCells(toRow, toCol, toRow + LongInt(row2) - LongInt(row1), toCol + LongInt(col2) - LongInt(col1));
// Copy comment
if srcSheet.HasComment(AFromCell) then
WriteComment(AToCell, ReadComment(AFromCell));
// Copy hyperlink
hyperlink := srcSheet.FindHyperlink(AFromCell);
WriteHyperlink(AToCell, hyperlink^.Target, hyperlink^.Tooltip);
// Copy rich text parameters
if (AFromCell^.ContentType = cctUTF8String) and (Length(AFromCell^.RichTextParams) > 0) then
SetLength(AToCell^.RichTextParams, Length(AFromCell^.RichTextParams));
// Make sure that fonts exist at destination
for i := 0 to High(AFromCell^.RichTextParams) do
AToCell^.RichTextParams[i] := AFromCell^.RichTextParams[i];
fnt := srcSheet.Workbook.GetFont(AFromCell^.RichTextParams[i].FontIndex);
fntIndex := Workbook.FindFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color, fnt.Position);
if fntIndex = -1 then
fntIndex := Workbook.AddFont(fnt.FontName, fnt.Size, fnt.Style, fnt.Color, fnt.Position);
AToCell^.RichTextParams[i].FontIndex := fntIndex;
FWorkbook.EnableNotifications;
// Notify visual controls of changes
ChangedCell(AToCell^.Row, AToCell^.Col);
// Notify visual controls of possibly changed row heights.
ChangedFont(AToCell^.Row, AToCell^.Col);
Copies a cell. The source cell can be located in a different worksheet, while
the destination cell must be in the same worksheet which calls the methode.
@param AFromRow Row index of the source cell
@param AFromCol Column index of the source cell
@param AToRow Row index of the destination cell
@param AToCol Column index of the destination cell
@param AFromWorksheet Worksheet containing the source cell. Self, if omitted.
@return Created new destination cell
function TsWorksheet.CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal;
AFromWorksheet: TsWorksheet = nil): PCell;
srcCell, destCell: PCell;
if AFromWorksheet = nil then
AFromWorksheet := self;
srcCell := AFromWorksheet.FindCell(AFromRow, AFromCol);
destCell := GetCell(AToRow, AToCol);
CopyCell(srcCell, destCell);
ChangedCell(AToRow, AToCol);
ChangedFont(AToRow, AToCol);
Result := destCell;
Copies all format parameters from the format cell to another cell.
@param AFromCell Pointer to source cell
@param AToCell Pointer to destination cell
procedure TsWorksheet.CopyFormat(AFromCell, AToCell: PCell);
Copies all format parameters from a given cell to another cell identified
by its row/column indexes.
@param AFormatCell Pointer to the source cell from which the format is copied.
procedure TsWorksheet.CopyFormat(AFormatCell: PCell; AToRow, AToCol: Cardinal);
CopyFormat(AFormatCell, GetCell(AToRow, AToCol));
Copies the formula of a specified cell to another cell. Adapts relative
cell references to the new cell.
@param AFromCell Pointer to the source cell from which the formula is to be
copied
@param AToCell Pointer to the destination cell
procedure TsWorksheet.CopyFormula(AFromCell, AToCell: PCell);
srcBook, destBook: TsWorkbook;
referencedSheet: TsWorksheet;
sheetName: String;
srcFormula, destFormula: PsFormula;
rpn: TsRPNFormula;
elem: TsFormulaElement;
srcSheet := TsWorksheet(AFromCell^.Worksheet);
srcBook := TsWorkbook(srcSheet.Workbook);
destBook := TsWorkbook(destSheet.Workbook);
destSheet.DeleteFormula(AToCell);
if not HasFormula(AFromCell) then
srcFormula := srcSheet.Formulas.FindFormula(AFromCell^.Row, AFromCell^.Col);
destFormula := destSheet.Formulas.AddFormula(AToCell^.Row, AToCell^.Col);
destFormula.Parser := TsSpreadsheetParser.Create(destSheet);
srcFormula^.Parser.PrepareCopyMode(AFromCell, AToCell);
rpn := srcFormula^.Parser.RPNFormula;
// Make sure that referenced sheets exist in destination workbook
for i:=0 to High(rpn) do begin
elem := rpn[i];
if elem.ElementKind in [fekCell3D, fekCellRef3d, fekCellRange3d] then begin
sheetName := srcBook.GetWorksheetByIndex(elem.Sheet).Name;
referencedSheet := destBook.GetWorksheetByName(sheetName);
if referencedSheet = nil then
referencedSheet := destBook.AddWorksheet(sheetName);
rpn[i].Sheet := destBook.GetWorksheetIndex(referencedSheet);
if (elem.Sheet = elem.Sheet2) or (elem.Sheet2 = -1) then
continue;
sheetName := srcBook.GetWorksheetByIndex(elem.Sheet2).Name;
rpn[i].Sheet2 := destBook.GetWorksheetIndex(referencedSheet);
destFormula^.Parser.RPNFormula := rpn;
destFormula^.Text := destFormula^.Parser.Expression[fdExcelA1];
UseFormulaInCell(AToCell, destFormula);
srcFormula^.Parser.PrepareCopyMode(nil, nil);
Copies the formula of a specified cell to another cell given by its row and
column index. Relative cell references are adapted to the new cell.
@param AFormatCell Pointer to the source cell containing the formula to be
procedure TsWorksheet.CopyFormula(AFormulaCell: PCell; AToRow, AToCol: Cardinal);
CopyFormula(AFormulaCell, GetCell(AToRow, AToCol));
Copies the value of a specified cell to another cell (without copying
formulas or formats)
@param AFromCell Pointer to the source cell providing the value to be copied
procedure TsWorksheet.CopyValue(AFromCell, AToCell: PCell);
if (AToCell = nil) then // AFromCell is allowed to be empty
if AFromCell <> nil then begin
AToCell^.ContentType := AFromCell^.ContentType;
AToCell^.NumberValue := AFromCell^.NumberValue;
AToCell^.DateTimeValue := AFromCell^.DateTimeValue;
AToCell^.BoolValue := AFromCell^.BoolValue;
AToCell^.ErrorValue := AFromCell^.ErrorValue;
AToCell^.UTF8StringValue := AFromCell^.UTF8StringValue;
AToCell^.ContentType := cctEmpty;
// Note: As confirmed with Excel, the formula is not to be copied here.
// But that of the destination cell must be erased.
DeleteFormula(AToCell);
Copies the value of a specified cell to another cell given by its row and
column index
@param AValueCell Pointer to the cell containing the value to be copied
procedure TsWorksheet.CopyValue(AValueCell: PCell; AToRow, AToCol: Cardinal);
CopyValue(AValueCell, GetCell(AToRow, AToCol));
Copies a column record to another location. The new column has the same
colwidth and the same formatting.
@param AFromCol Index of the column to be copied
@param AToCol Index of the destination column
procedure TsWorksheet.CopyCol(AFromCol, AToCol: Cardinal;
AFromWorksheet: TsWorksheet = nil);
srcCol, destCol: PCol;
srcCol := AFromWorksheet.FindCol(AFromCol);
destCol := FindCol(AToCol);
// Overwrite destination column with empty column record ?
if (srcCol = nil) then
if destCol <> nil then
DeleteCol(AToCol);
// Create new or use existing column record
destCol := GetCol(AToCol);
// Copy contents of column record...
destCol^ := srcCol^;
// ... and restore column index lost in previous step
destCol^.Col := AToCol;
// ... and copy the format record - it may have be missing at destination
CopyColFormat(srcCol, destCol, AFromWorksheet, self);
ChangedCol(destCol^.Col);
Copies a row record to another location. The new row has the same
row heightand the same formatting.
@param AFromRow Index of the row to be copied
@param AToTow Index of the destination row
procedure TsWorksheet.CopyRow(AFromRow, AToRow: Cardinal;
AFromWorksheet: TsWorksheet);
srcRow, destRow: PRow;
srcRow := AFromWorksheet.FindRow(AFromRow);
destRow := FindRow(AToRow);
// Overwrite destination row with empty row record?
if (srcRow = nil) then
if destRow <> nil then
DeleteRow(AToRow);
// Create new or use existing row record
destRow := GetRow(AToRow);
// Copy contents of row record...
destRow^ := srcRow^;
// ... and restore row index lost in previous step
destRow^.Row := AToRow;
CopyRowFormat(srcRow, destRow, AFromWorksheet, self);
procedure TsWorksheet.Clear;
FCells.Clear;
FComments.Clear;
FHyperlinks.Clear;
FMergedCells.Clear;
ChangedCell(0, 0);
Deletes a specified cell. If the cell belongs to a merged block its content
and formatting is erased. Otherwise the cell is destroyed and its memory is
released.
procedure TsWorksheet.DeleteCell(ACell: PCell);
{$warning TODO: Shift cells to the right/below !!! ??? }
// Does cell have a comment? --> remove it
WriteComment(ACell, '');
// Does cell have a hyperlink? --> remove it
WriteHyperlink(ACell, '');
// Does cell have a formula? --> remove it
if HasFormula(ACell) then
WriteFormula(ACell, '');
// To do: Check if the cell is referencec by a formula. In this case we have
// a #REF! error.
// Cell is part of a merged block? --> Erase content, formatting etc.
if IsMerged(ACell) then
EraseCell(ACell);
r := ACell^.Row;
c := ACell^.Col;
// Destroy the cell, and remove it from the tree
RemoveAndFreeCell(ACell^.Row, ACell^.Col);
ChangedCell(r, c);
Erases content and formatting of a cell. The cell still occupies memory.
@param ACell Pointer to cell to be erased.
procedure TsWorksheet.EraseCell(ACell: PCell; AKeepFormat: Boolean = false);
if ACell <> nil then begin
// Unmerge range if the cell is the base of a merged block
if IsMergeBase(ACell) then
UnmergeCells(r, c);
// Remove the comment if the cell has one
// Removes a hyperlink it the cell has one
// Removes the formula if the cell has one
DeleteFormula(ACell);
if AKeepFormat then
ACell^.ContentType := cctEmpty
// Erase all cell content
InitCell(nil, r, c, ACell^);
Exchanges two cells
@param ARow1 Row index of the first cell
@param ACol1 Column index of the first cell
@param ARow2 Row index of the second cell
@param ACol2 Column index of the second cell
@note This method does not take care of merged cells and does not
check for this situation. Therefore, the method is not public!
procedure TsWorksheet.ExchangeCells(ARow1, ACol1, ARow2, ACol2: Cardinal);
FCells.Exchange(ARow1, ACol1, ARow2, ACol2);
FComments.Exchange(ARow1, ACol1, ARow2, ACol2);
FHyperlinks.Exchange(ARow1, ACol1, ARow2, ACol2);
Adds a new cell at a specified row and column index to the Cells list.
NOTE: It is not checked if there exists already another cell at this location.
This case must be avoided. USE CAREFULLY WITHOUT FindCell
(e.g., during reading into empty worksheets).
function TsWorksheet.AddCell(ARow, ACol: Cardinal): PCell;
fmtIndex: Integer;
Result := Cells.AddCell(ARow, ACol);
fmtIndex := GetRowFormatIndex(ARow);
if fmtIndex = 0 then
fmtIndex := GetColFormatIndex(ACol);
Result^.FormatIndex := fmtIndex;
if FFirstColIndex = UNASSIGNED_ROW_COL_INDEX then
FFirstColIndex := GetFirstColIndex(true) else
FFirstColIndex := Min(FFirstColIndex, ACol);
if FFirstRowIndex = UNASSIGNED_ROW_COL_INDEX then
FFirstRowIndex := GetFirstRowIndex(true) else
FFirstRowIndex := Min(FFirstRowIndex, ARow);
if FLastColIndex = UNASSIGNED_ROW_COL_INDEX then
FLastColIndex := GetLastColIndex(true) else
FLastColIndex := Max(FLastColIndex, ACol);
if FLastRowIndex = UNASSIGNED_ROW_COL_INDEX then
FLastRowIndex := GetLastRowIndex(true) else
FLastRowIndex := Max(FLastRowIndex, ARow);
Tries to locate a Cell in the list of already written Cells
@param ARow The row of the cell
@param ACol The column of the cell
@return Pointer to the cell if found, or nil if not found
@see TCell
function TsWorksheet.FindCell(ARow, ACol: Cardinal): PCell;
Result := PCell(FCells.FindByRowCol(ARow, ACol));
Tries to locate a cell in the list of already written cells
@param AddressStr Address of the cell in Excel A1 notation
function TsWorksheet.FindCell(AddressStr: String): PCell;
if ParseCellString(AddressStr, r, c) then
Result := FindCell(r, c)
function TsWorksheet.FindNextCellInCol(ARow, ACol: Cardinal): PCell;
last: Cardinal;
last := GetLastRowIndex;
if ARow = last then
Result := nil
repeat
inc(ARow);
Result := FindCell(ARow, ACol);
until (Result <> nil) or (ARow = last);
function TsWorksheet.FindNextCellInRow(ARow, ACol: Cardinal): PCell;
last := GetLastColIndex;
if ACol = last then
Repeat
inc(ACol);
Result := Findcell(ARow, ACol);
until (Result <> nil) or (ACol = last);
function TsWorksheet.FindPrevCellInCol(ARow, ACol: Cardinal): PCell;
if ARow = 0 then
dec(ARow);
until (Result <> nil) or (ARow = 0);
function TsWorksheet.FindPrevCellInRow(ARow, ACol: Cardinal): PCell;
if ACol = 0 then
dec(ACol);
until (Result <> nil) or (ACol = 0);
Obtains an allocated cell at the desired location.
If the cell already exists, a pointer to it will be returned.
If not, then new memory for the cell will be allocated, a pointer to it
will be returned and it will be added to the list of cells.
@param ARow Row index of the cell
@param ACol Column index of the cell
@return A pointer to the cell at the desired location.
function TsWorksheet.GetCell(ARow, ACol: Cardinal): PCell;
Result := Cells.FindCell(ARow, ACol);
Result := AddCell(ARow, ACol);
If the Cell already exists, a pointer to it will be returned.
@param AddressStr Address of the cell in Excel A1 notation (an exception is
raised in case on an invalid cell address).
function TsWorksheet.GetCell(AddressStr: String): PCell;
Result := GetCell(r, c)
raise EFPSpreadsheet.CreateFmt(rsNoValidCellAddress, [AddressStr]);
Returns the number of cells in the worksheet with contents.
@return The number of cells with contents in the worksheet
function TsWorksheet.GetCellCount: Cardinal;
Result := FCells.Count;
Determines the number of decimals displayed for the number in the cell
@param ACell Pointer to the cell under investigation
@return Number of decimals places used in the string display of the cell.
function TsWorksheet.GetDisplayedDecimals(ACell: PCell): Byte;
i, p: Integer;
s: String;
Result := 0;
if (ACell <> nil) and (ACell^.ContentType = cctNumber) then
s := ReadAsText(ACell);
p := pos(Workbook.FormatSettings.DecimalSeparator, s);
i := p+1;
while (i <= Length(s)) and (s[i] in ['0'..'9']) do inc(i);
Result := i - (p+1);
Determines some number format attributes (decimal places, currency symbol) of
a cell
@param ADecimals Number of decimal places that can be extracted from
the formatting string, e.g. in case of '0.000' this
would be 3.
@param ACurrencySymbol String representing the currency symbol extracted from
the formatting string.
@return true if the the format string could be analyzed successfully, false if not
function TsWorksheet.GetNumberFormatAttributes(ACell: PCell; out ADecimals: byte;
parser: TsNumFormatParser;
nf: TsNumberFormat;
if ACell <> nil then
ReadNumFormat(ACell, nf, nfs);
parser := TsNumFormatParser.Create(nfs, FWorkbook.FormatSettings);
if parser.Status = psOK then
nf := parser.NumFormat;
if (nf = nfGeneral) and (ACell^.ContentType = cctNumber) then
ADecimals := GetDisplayedDecimals(ACell);
ACurrencySymbol := '';
if IsDateTimeFormat(nf) then
ADecimals := 2;
ACurrencySymbol := '?';
ADecimals := parser.Decimals;
ACurrencySymbol := parser.CurrencySymbol;
parser.Free;
Returns the 0-based index of the first column with a cell with contents.
If no cells have contents, zero will be returned, which is also a valid value.
Use GetCellCount to verify if there is at least one cell with contents in the
worksheet.
@param AForceCalculation The index of the first column is continuously updated
whenever a new cell is created. If AForceCalculation
is true all cells are scanned to determine the index
of the first column.
@see GetCellCount
function TsWorksheet.GetFirstColIndex(AForceCalculation: Boolean = false): Cardinal;
if AForceCalculation then
Result := UNASSIGNED_ROW_COL_INDEX;
Result := Math.Min(Result, cell^.Col);
// In addition, there may be column records defining the column width even
// without content
for i:=0 to FCols.Count-1 do
if FCols[i] <> nil then
Result := Math.Min(Result, PCol(FCols[i])^.Col);
// Store the result
FFirstColIndex := Result;
Result := FFirstColIndex;
if Result = UNASSIGNED_ROW_COL_INDEX then
Result := GetFirstColIndex(true);
Returns the 0-based index of the last column containing a cell with a
column record (due to content or formatting), or containing a Col record.
If no cells have contents or there are no column records, zero will be
returned, which is also a valid value.
@param AForceCalculation The index of the last column is continuously updated
of the last column.
@see GetLastOccupiedColIndex
function TsWorksheet.GetLastColIndex(AForceCalculation: Boolean = false): Cardinal;
if AForceCalculation or (FLastColIndex = UNASSIGNED_ROW_COL_INDEX) then
// Traverse the tree from lowest to highest.
// Since tree primary sort order is on row highest col could exist anywhere.
Result := GetLastOccupiedColIndex;
// without cells
Result := Math.Max(Result, PCol(FCols[i])^.Col);
FLastColIndex := Result;
Result := FLastColIndex;
Deprecated, use GetLastColIndex instead
@see GetLastColIndex
function TsWorksheet.GetLastColNumber: Cardinal;
Result := GetLastColIndex;
Returns the 0-based index of the last column with a cell with contents.
function TsWorksheet.GetLastOccupiedColIndex: Cardinal;
// Since tree's primary sort order is on row, highest col could exist anywhere.
Result := Math.Max(Result, cell^.Col);
Returns the 0-based index of the first row with a cell with data or formatting.
If no cells have contents, -1 will be returned.
@param AForceCalculation The index of the first row is continuously updated
of the first row.
function TsWorksheet.GetFirstRowIndex(AForceCalculation: Boolean = false): Cardinal;
cell := FCells.GetFirstCell;
if cell <> nil then Result := cell^.Row;
// In addition, there may be row records even for rows without cells.
for i:=0 to FRows.Count-1 do
if FRows[i] <> nil then
Result := Math.Min(Result, PRow(FRows[i])^.Row);
// Store result
FFirstRowIndex := Result;
Result := FFirstRowIndex;
Result := GetFirstRowIndex(true);
Returns the 0-based index of the last row with a cell with contents or with
a ROW record.
@param AForceCalculation The index of the last row is continuously updated
of the last row.
@see GetLastOccupiedRowIndex
function TsWorksheet.GetLastRowIndex(AForceCalculation: Boolean = false): Cardinal;
if AForceCalculation or (FLastRowIndex = UNASSIGNED_ROW_COL_INDEX) then
// Index of highest row with at least one existing cell
Result := GetLastOccupiedRowIndex;
// In addition, there may be row records even for empty rows.
Result := Math.Max(Result, PRow(FRows[i])^.Row);
FLastRowIndex := Result;
Result := FLastRowIndex
Returns the 0-based index of the last row with a cell with contents.
@see GetLastRowIndex
function TsWorksheet.GetLastOccupiedRowIndex: Cardinal;
cell := FCells.GetLastCell;
if Assigned(cell) then
Result := cell^.Row;
function TsWorksheet.GetLastRowNumber: Cardinal;
Result := GetLastRowIndex;
Reads the contents of a cell and returns an user readable text
representing the contents of the cell.
The resulting string is UTF-8 encoded.
@return The text representation of the cell
function TsWorksheet.ReadAsText(ARow, ACol: Cardinal): string;
cell := FindCell(ARow, ACol);
if cell <> nil then Result := ReadAsText(cell) else Result := '';
{ avoid creating a blenk cell if the cell does not exist
Result := ReadAsText(GetCell(ARow, ACol)); }
function TsWorksheet.ReadAsUTF8Text(ARow, ACol: Cardinal): string;
Result := ReadAsText(ARow, ACol);
function TsWorksheet.ReadAsText(ACell: PCell): string;
Result := ReadAsText(ACell, FWorkbook.FormatSettings);
function TsWorksheet.ReadAsUTF8Text(ACell: PCell): string;
@param AFormatSettings Format settings to be used for string conversion
of numbers and date/times.
function TsWorksheet.ReadAsText(ACell: PCell;
AFormatSettings: TFormatSettings): string;
fmt: PsCellFormat;
numFmt: TsNumFormatParams;
Exit;
fmt := Workbook.GetPointerToCellFormat(ACell^.FormatIndex);
numFmt := Workbook.GetNumberFormat(fmt^.NumberFormatIndex);
with ACell^ do
case ContentType of
cctUTF8String:
Result := UTF8StringValue;
cctNumber:
Result := ConvertFloatToStr(NumberValue, numFmt, AFormatSettings);
cctDateTime:
if Assigned(numFmt) then
Result := ConvertFloatToStr(DateTimeValue, numFmt, AFormatSettings)
if not IsNaN(DateTimeValue) then
if frac(DateTimeValue) = 0 then // date only
nf := nfShortDate
if trunc(DateTimeValue) = 0 then // time only
nf := nfLongTime
nf := nfShortDateTime;
nfs := BuildDateTimeFormatString(nf, AFormatSettings);
Result := FormatDateTime(nfs, DateTimeValue, AFormatSettings);
cctBool:
Result := StrUtils.IfThen(BoolValue, STR_TRUE, STR_FALSE);
cctError:
Result := GetErrorValueStr(TsErrorValue(ErrorValue));
if Result = '' then // blank --> display hyperlink target if available
if hyperlink <> nil then Result := hyperlink^.Target;
function TsWorksheet.ReadAsUTF8Text(ACell: PCell;
Result := ReadAsText(ACell, AFormatSettings);
Returns the value of a cell as a number.
If the cell contains a date/time value its serial value is returned
(as FPC TDateTime).
If the cell contains a text value it is attempted to convert it to a number.
If the cell is empty or its contents cannot be represented as a number the
value 0.0 is returned.
@return Floating-point value representing the cell contents, or 0.0 if cell
does not exist or its contents cannot be converted to a number.
function TsWorksheet.ReadAsNumber(ARow, ACol: Cardinal): Double;
Result := ReadAsNumber(FindCell(ARow, ACol));
value NaN is returned.
@return Floating-point value representing the cell contents, or NaN if cell
function TsWorksheet.ReadAsNumber(ACell: PCell): Double;
Result := NaN;
case ACell^.ContentType of
Result := ACell^.DateTimeValue; //this is in FPC TDateTime format, not Excel
Result := ACell^.NumberValue;
if not TryStrToFloat(ACell^.UTF8StringValue, Result, FWorkbook.FormatSettings)
then Result := NaN;
if ACell^.BoolValue then Result := 1.0 else Result := 0.0;
Reads the contents of a cell and returns the date/time value of the cell.
@param AResult Date/time value of the cell (or 0.0, if no date/time cell)
@return True if the cell is a datetime value, false otherwise
function TsWorksheet.ReadAsDateTime(ARow, ACol: Cardinal;
out AResult: TDateTime): Boolean;
Result := ReadAsDateTime(FindCell(ARow, ACol), AResult);
function TsWorksheet.ReadAsDateTime(ACell: PCell;
if (ACell = nil) or (ACell^.ContentType <> cctDateTime) then
AResult := 0;
Result := False;
AResult := ACell^.DateTimeValue;
Result := True;
If a cell contains a formula (string formula or RPN formula) the formula
is returned as a string in Excel syntax.
@param ALocalized If true, the formula is returned with decimal and list
separators accoding to the workbook's FormatSettings.
Otherwise it uses dot and comma, respectively.
@return Formula string in Excel syntax (does not contain a leading "=")
function TsWorksheet.ReadFormulaAsString(ACell: PCell;
ALocalized: Boolean = false): String;
if HasFormula(ACell) then begin
if ALocalized then
Result := formula^.Parser.Expression[fdLocalized]
Result := formula^.Parser.Expression[fdExcelA1];
Returns to numeric equivalent of the cell contents. This is the NumberValue
of a number cell, the DateTimeValue of a date/time cell, the ordinal BoolValue
of a boolean cell, or the string converted to a number of a string cell.
All other cases return NaN.
@param ACell Cell to be considered
@param AValue (output) extracted numeric value
@return True if conversion to number is successful, otherwise false
function TsWorksheet.ReadNumericValue(ACell: PCell; out AValue: Double): Boolean;
AValue := NaN;
AValue := ACell^.NumberValue;
AValue := ACell^.DateTimeValue;
AValue := ord(ACell^.BoolValue);
if (ACell^.ContentType <> cctUTF8String) or
not TryStrToFloat(ACell^.UTF8StringValue, AValue) or
not TryStrToDateTime(ACell^.UTF8StringValue, AValue)
then
function TsWorksheet.ConvertFormulaDialect(ACell: PCell;
ADialect: TsFormulaDialect): String;
if (ACell = nil) or (not HasFormula(ACell)) then
if ADialect = fdExcelR1C1 then
Result := formula^.Parser.R1C1Expression[ACell]
Result := formula^.Parser.Expression[ADialect];
Converts an RPN formula (as read from an xls biff file, for example) to a
string formula.
@param AFormula Array of rpn formula tokens
@return Formula string in Excel syntax (without leading "=")
function TsWorksheet.ConvertRPNFormulaToStringFormula(const AFormula: TsRPNFormula): String;
parser: TsSpreadsheetParser;
parser.RPNFormula := AFormula;
Result := parser.Expression[fdExcelA1];
Returns a pointer to the formula record assigned to a cell, or nil if the
cell has no formula
function TsWorksheet.GetFormula(ACell: PCell): PsFormula;
Result := FFormulas.FindFormula(ACell);
Returns the index of the effective cell format to be used at the specified
cell.
"Effective" cell format means: At first, look for the cell format.
If it is default, look for the row format. If it is default, look for
the column format. (see "excelfileformat", p. 89)
function TsWorksheet.GetEffectiveCellFormatIndex(ARow, ACol: Cardinal): Integer;
if (cell <> nil) then
Result := GetEffectiveCellFormatIndex(cell)
// Result := cell^.FormatIndex
// Col and row formats are needed explicitely only in case of empty cells.
// Because if a cells exists the col/row format already has been copied
// to the cell.
Result := GetRowFormatIndex(ARow);
Result := GetColFormatIndex(ACol);
function TsWorksheet.GetEffectiveCellFormatIndex(ACell: PCell): Integer;
Result := ACell^.FormatIndex;
Result := GetRowFormatIndex(ACell^.Row);
Result := GetColFormatIndex(ACell^.Col);
Returns a pointer to the effective cell format to be used at the cell in
ARow and ACol.
function TsWorksheet.GetPointerToEffectiveCellFormat(ARow, ACol: Cardinal): PsCellFormat;
fmtIndex := cell^.FormatIndex
Result := FWorkbook.GetPointerToCellFormat(fmtIndex);
Mainly like GetPointerToEffectiveCellFormat(ARow, ACol), but avoids looking
for the cell if ACell <> nil
function TsWorksheet.GetPointerToEffectiveCellFormat(ACell: PCell): PsCellFormat;
if (ACell <> nil) then
fmtIndex := ACell^.FormatIndex
fmtIndex := 0;
end; *)
Reads the set of used formatting fields of a cell.
Each cell contains a set of "used formatting fields". Formatting is applied
only if the corresponding element is contained in the set.
@return Set of elements used in formatting the cell
function TsWorksheet.ReadUsedFormatting(ACell: PCell): TsUsedFormattingFields;
Result := [];
fmt := FWorkbook.GetPointerToCellFormat(ACell^.FormatIndex);
Result := fmt^.UsedFormattingFields;
Returns the background fill pattern and colors of a cell.
@return TsFillPattern record (or EMPTY_FILL, if the cell does not have a
filled background
function TsWorksheet.ReadBackground(ACell: PCell): TsFillPattern;
fmt : PsCellFormat;
Result := EMPTY_FILL;
if (uffBackground in fmt^.UsedFormattingFields) then
Result := fmt^.Background;
Returns the background color of a cell as rbg value
@return Value containing the rgb bytes in little-endian order
function TsWorksheet.ReadBackgroundColor(ACell: PCell): TsColor;
Result := scTransparent;
Result := ReadBackgroundColor(ACell^.FormatIndex);
Returns the background color stored at the specified index in the format
list of the workkbok.
@param AFormatIndex Index of the format record
function TsWorksheet.ReadBackgroundColor(AFormatIndex: Integer): TsColor;
if AFormatIndex > -1 then begin
fmt := Workbook.GetPointerToCellFormat(AFormatIndex);
if fmt^.Background.Style = fsSolidFill then
Result := fmt^.Background.FgColor
Result := fmt^.Background.BgColor;
Determines which borders are drawn around a specific cell
function TsWorksheet.ReadCellBorders(ACell: PCell): TsCellBorders;
if (uffBorder in fmt^.UsedFormattingFields) then
Result := fmt^.Border;
Determines which the style of a particular cell border
function TsWorksheet.ReadCellBorderStyle(ACell: PCell;
ABorder: TsCelLBorder): TsCellBorderStyle;
Result := DEFAULT_BORDERSTYLES[ABorder];
Result := fmt^.BorderStyles[ABorder];
Determines which all border styles of a given cell
function TsWorksheet.ReadCellBorderStyles(ACell: PCell): TsCellBorderStyles;
Result := DEFAULT_BORDERSTYLES;
Result := Fmt^.BorderStyles;
Determines the font used by a specified cell. Returns the workbook's default
font if the cell does not exist.
function TsWorksheet.ReadCellFont(ACell: PCell): TsFont;
Result := Workbook.GetFont(fmt^.FontIndex);
Result := Workbook.GetDefaultFont;
Determines the index of the font used by a specified cell, referring to the
workbooks font list. Returns 0 (the default font index) if the cell does not
exist.
function TsWorksheet.ReadCellFontIndex(ACell: PCell): Integer;
Result := DEFAULT_FONTINDEX;
Result := fmt^.FontIndex;
Returns the format record that is assigned to a specified cell
function TsWorksheet.ReadCellFormat(ACell: PCell): TsCellFormat;
Result := Workbook.GetCellFormat(ACell^.FormatIndex);
Determines the font used in a specified column record.
Returns the workbook's default font if the column record does not exist.
function TsWorksheet.ReadColFont(ACol: PCol): TsFont;
if ACol <> nil then begin
fmt := Workbook.GetPointerToCellFormat(ACol^.FormatIndex);
Determines the font used in a specified row record.
Returns the workbook's default font if the row record does not exist.
function TsWorksheet.ReadRowFont(ARow: PRow): TsFont;
if ARow <> nil then
fmt := Workbook.GetPointerToCellFormat(ARow^.FormatIndex);
Returns the horizontal alignment of a specific cell
function TsWorksheet.ReadHorAlignment(ACell: PCell): TsHorAlignment;
Result := haDefault;
if (uffHorAlign in fmt^.UsedFormattingFields) then
Result := fmt^.HorAlignment;
Returns the number format type and format string used in a specific cell
procedure TsWorksheet.ReadNumFormat(ACell: PCell; out ANumFormat: TsNumberFormat;
ANumFormat := nfGeneral;
ANumFormatStr := '';
if (uffNumberFormat in fmt^.UsedFormattingFields) then
if numFmt <> nil then
ANumFormat := numFmt.NumFormat;
ANumFormatStr := numFmt.NumFormatStr;
Returns the text orientation of a specific cell
function TsWorksheet.ReadTextRotation(ACell: PCell): TsTextRotation;
Result := trHorizontal;
if (uffTextRotation in fmt^.UsedFormattingFields) then
Result := fmt^.TextRotation;
Returns the vertical alignment of a specific cell
function TsWorksheet.ReadVertAlignment(ACell: PCell): TsVertAlignment;
Result := vaDefault;
if (uffVertAlign in fmt^.UsedFormattingFields) then
Result := fmt^.VertAlignment;
Returns whether a specific cell support word-wrapping.
function TsWorksheet.ReadWordwrap(ACell: PCell): boolean;
Result := uffWordwrap in fmt^.UsedFormattingFields;
Returns the BiDi mode of the cell (right-to-left or left-to-right)
function TsWorksheet.ReadBiDiMode(ACell: PCell): TsBiDiMode;
Result := bdDefault;
if (uffBiDi in fmt^.UsedFormattingFields) then
Result := fmt^.BiDiMode;
Returns the protection flags of the cell.
NOTE: These flags are active only if sheet protection is active, i.e.
soProtected in Worksheet.Options.
function TsWorksheet.ReadCellProtection(ACell: PCell): TsCellProtections;
Result := DEFAULT_CELL_PROTECTION;
if fmt <> nil then
Result := fmt^.Protection;
Returns true if the worksheet does not contain any cell, column or row records
function TsWorksheet.IsEmpty: Boolean;
for cell in Cells do
if cell^.ContentType <> cctEmpty then
if (Rows.Count > 0) or (Cols.Count > 0) then
Finds the upper left cell of a merged block to which a specified cell belongs.
This is the "merge base". Returns nil if the cell is not merged.
@param ACell Cell under investigation
@return A pointer to the cell in the upper left corner of the merged block
to which ACell belongs.
If ACell is isolated then the function returns nil.
function TsWorksheet.FindMergeBase(ACell: PCell): PCell;
rng: PsCellRange;
rng := FMergedCells.FindRangeWithCell(ACell^.Row, ACell^.Col);
if rng <> nil then
Result := FindCell(rng^.Row1, rng^.Col1);
Merges adjacent individual cells to a larger single cell
@param ARow1 Row index of the upper left corner of the cell range
@param ACol1 Column index of the upper left corner of the cell range
@param ARow2 Row index of the lower right corner of the cell range
@param ACol2 Column index of the lower right corner of the cell range
procedure TsWorksheet.MergeCells(ARow1, ACol1, ARow2, ACol2: Cardinal);
// A single cell cannot be merged
if (ARow1 = ARow2) and (ACol1 = ACol2) then
// Is cell ARow1/ACol1 already the base of a merged range? ...
rng := PsCellRange(FMergedCells.FindByRowCol(ARow1, ACol1));
// ... no: --> Add a new merged range
if rng = nil then
FMergedCells.AddRange(ARow1, ACol1, ARow2, ACol2)
// ... yes: --> modify the merged range accordingly
// unmark previously merged range
for cell in Cells.GetRangeEnumerator(rng^.Row1, rng^.Col1, rng^.Row2, rng^.Col2) do
Exclude(cell^.Flags, cfMerged);
// Define new limits of merged range
rng^.Row2 := ARow2;
rng^.Col2 := ACol2;
// Mark all cells in the range as "merged"
for r := ARow1 to ARow2 do
for c := ACol1 to ACol2 do
cell := GetCell(r, c); // if not existent create new cell
Include(cell^.Flags, cfMerged);
ChangedCell(ARow1, ACol1);
@param ARange Cell range string given in Excel notation (e.g: A1:D5).
A non-range string (e.g. A1) is not allowed.
procedure TsWorksheet.MergeCells(ARange: String);
r1, r2, c1, c2: Cardinal;
if ParseCellRangeString(ARange, r1, c1, r2, c2) then
MergeCells(r1, c1, r2, c2);
Disconnects merged cells to make them individual cells again.
Input parameter is a cell which belongs to the range to be unmerged.
@param ARow Row index of a cell considered to belong to the cell block
@param ACol Column index of a cell considered to belong to the cell block
procedure TsWorksheet.UnmergeCells(ARow, ACol: Cardinal);
rng := FMergedCells.FindRangeWithCell(ARow, ACol);
// Remove the "merged" flag from the cells in the merged range to make them
// isolated again...
// ... and delete the range
FMergedCells.DeleteRange(rng^.Row1, rng^.Col1);
ChangedCell(ARow, ACol);
@param ARange Cell (range) string given in Excel notation (e.g: A1, or A1:D5)
In case of a range string, only the upper left corner cell is
considered. It must belong to the merged range of cells to be
unmerged.
procedure TsWorksheet.UnmergeCells(ARange: String);
rng: TsCellRange;
if Workbook.TryStrToCellRange(ARange, sheet, rng) then
UnmergeCells(rng.Row1, rng.Col1);
Determines the merged cell block to which a particular cell belongs
@param ACell Pointer to the cell being investigated
@param ARow1 (output) Top row index of the merged block
@param ACol1 (outout) Left column index of the merged block
@param ARow2 (output) Bottom row index of the merged block
@param ACol2 (output) Right column index of the merged block
@return True if the cell belongs to a merged block, False if not, or if the
cell does not exist at all.
function TsWorksheet.FindMergedRange(ACell: PCell;
out ARow1, ACol1, ARow2, ACol2: Cardinal): Boolean;
ARow1 := rng^.Row1;
ACol1 := rng^.Col1;
ARow2 := rng^.Row2;
ACol2 := rng^.Col2;
Checks whether the two specified cells belong to the same merged cell block.
@param ACell1 Pointer to the first cell
@param ACell2 Pointer to the second cell
@reult TRUE if both cells belong to the same merged cell block
FALSE if the cells are not merged or are in different blocks
function TsWorksheet.InSameMergedRange(ACell1, ACell2: PCell): Boolean;
Result := IsMerged(ACell1) and IsMerged(ACell2) and
(FindMergeBase(ACell1) = FindMergeBase(ACell2));
Returns true if the specified cell is the base of a merged cell range, i.e.
the upper left corner of that range.
@param ACell Pointer to the cell being considered
@return True if the cell is the upper left corner of a merged range
False if not
function TsWorksheet.IsMergeBase(ACell: PCell): Boolean;
Result := (ACell <> nil) and (ACell = FindMergeBase(ACell));
Returns TRUE if the specified cell belongs to a merged block
@param ACell Pointer to the cell of interest
@return TRUE if the cell belongs to a merged block, FALSE if not.
function TsWorksheet.IsMerged(ACell: PCell): Boolean;
Result := (ACell <> nil) and (cfMerged in ACell^.Flags);
Deletes the formula assigned to the specified cell
procedure TsWorksheet.DeleteFormula(ACell: PCell);
if HasFormula(ACell) and (FWorkbook.FDeleteFormulaLock = 0) then begin
FFormulas.DeleteFormula(ACell);
ACell^.Flags := ACell^.Flags - [cfHasFormula, cf3dFormula];
Reads the formula assigned to a cell in the specified row and column
function TsWorksheet.ReadFormula(ARow, ACol: Cardinal): String;
Result := ReadFormula(cell)
Reads the formula assigned to a specified cell
function TsWorksheet.ReadFormula(ACell: PCell): String;
formula := Formulas.FindFormula(ACell);
if formula = nil then
Result := formula^.Text;
if (Result = '') and (formula^.Parser <> nil) then
Uses a formula in the specified a cell
procedure TsWorksheet.UseFormulaInCell(ACell: PCell; AFormula: PsFormula);
Assert(ACell <> nil);
if AFormula <> nil then
AFormula^.Col := ACell^.Col;
AFormula^.Row := ACell^.Row;
ACell^.ContentType := cctFormula;
ACell^.Flags := ACell^.Flags + [cfHasFormula];
if (AFormula^.Parser <> nil) and AFormula^.Parser.Has3DLinks then
ACell^.Flags := ACell^.Flags + [cf3dFormula];
Returns the parameters of the image stored in the internal image list at
the specified index.
@param AIndex Index of the image to be retrieved
@return TsImage record with all image parameters.
function TsWorksheet.GetImage(AIndex: Integer): TsImage;
img: PsImage;
img := PsImage(FImages[AIndex]);
Result := img^;
function TsWorksheet.GetPointerToImage(AIndex: Integer): PsImage;
Result := PsImage(FImages[AIndex]);
Returns the count of images that are embedded into this sheet.
function TsWorksheet.GetImageCount: Integer;
Result := FImages.Count;
Calculates the position of the image with given index relative to the cell
containing the top/left corner of the image.
@@param x worksheet-relative coordinate of the left image edge, in workbook units
@@param y worksheet-relative coordinate of the top image edge, in workbook units
@@param ARow Index of the row containing the top/left corner of the image
@@param ACol Index of the column containing the top/left corner of the image
@@param ARowOffset Distance, in workbook units, between top cell and image borders
@@param AColOffset Distance, in workbook units, between left cell and image borders
@@param AScaleX Scaling factor for the image width
@@param AScaleY Scaling factor for the image height
procedure TsWorksheet.CalcImageCell(AIndex: Integer; x, y, AWidth, AHeight: Double;
// All lengths are in workbook units!
colW, rowH, sum: Double;
embobj: TsEmbeddedObj;
ACol := 0;
sum := 0;
colW := GetColWidth(0, FWorkbook.Units);
while (sum + colW < x) do begin
sum := sum + colW;
colW := GetColWidth(ACol, FWorkbook.Units);
AColOffs := x - sum;
ARow := 0;
rowH := CalcRowHeight(0);
while (sum + rowH < y) do begin
sum := sum + rowH;
rowH := CalcRowHeight(ARow);
ARowOffs := y - sum;
embObj := FWorkbook.GetEmbeddedObj(AIndex);
AScaleX := AWidth / embObj.ImageWidth;
AScaleY := AHeight / embObj.ImageHeight;
Calculates image extent
@param AIndex Index of the image into the worksheet's image list
@param UsePixels if TRUE then pixels are used for calculation - this improves
the display of the images in Excel
@param ARow1 Index of the row containing the top edge of the image
@param ACol1 Index of the column containing the left edege of the image
@param ARow2 Index of the row containing the right edge of the image
@param ACol2 Index of the column containing the bottom edge of the image
@param ARowOffs1 Distance between the top edge of image and row 1
@param AColOffs1 Distance between the left edge of image and column 1
@param ARowOffs2 Distance between the bottom edge of image and top of row 2
@param AColOffs2 Distance between the right edge of image and left of col 2
@param x Absolute coordinate of left edge of image
@param y Absolute coordinate of top edge of image
@param AWidth Width of the image
@param AHeight Height of the image
All dimensions are in workbook units
procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;
out x,y, AWidth, AHeight: Double);
img: TsImage;
obj: TsEmbeddedObj;
colW, rowH: Double;
totH: Double;
r, c: Integer;
w_px, h_px: Integer;
totH_px, rowH_px: Integer;
totW_px, colW_px: Integer;
ppi: Integer;
u: TsSizeUnits;
// Abbreviations
ppi := ScreenPixelsPerInch;
u := FWorkbook.Units;
img := GetImage(AIndex);
ARow1 := img.Row;
ACol1 := img.Col;
ARowOffs1 := img.OffsetX; // in workbook units
AColOffs1 := img.OffsetY; // in workbook units
obj := FWorkbook.GetEmbeddedObj(img.Index);
AWidth := obj.ImageWidth * img.ScaleX; // in workbook units
AHeight := obj.ImageHeight * img.ScaleY; // in workbook units
// Find x coordinate of left image edge, in workbook units
x := AColOffs1;
for c := 0 to ACol1-1 do
colW := GetColWidth(c, u);
x := x + colW;
// Find y coordinate of top image edge, in workbook units.
y := ARowOffs1;
for r := 0 to ARow1 - 1 do
rowH := CalcRowHeight(r);
y := y + rowH;
if UsePixels then
// Use pixels for calculation. Better for Excel, maybe due to rounding error?
// If we don't know the ppi of the screen the calculation is not exact!
w_px := ptsToPx(FWorkbook.ConvertUnits(AWidth, u, suPoints), ppi);
h_px := ptsToPx(FWorkbook.ConvertUnits(AHeight, u, suPoints), ppi);
// Find cell with right image edge. Find horizontal within-cell-offsets
totW_px := -ptsToPx(FWorkbook.ConvertUnits(AColOffs1, u, suPoints), ppi);
ACol2 := ACol1;
while (totW_px < w_px) do
colW := GetColWidth(ACol2, u);
colW_px := ptsToPx(FWorkbook.ConvertUnits(colW, u, suPoints), ppi);
totW_px := totW_px + colW_px;
if totW_px > w_px then
AColOffs2 := FWorkbook.ConvertUnits(pxToPts(colW_px - (totW_px - w_px), ppi), suPoints, u);
break;
inc(ACol2);
// Find cell with bottom image edge. Find vertical within-cell-offset.
totH_px := -ptsToPx(FWorkbook.ConvertUnits(ARowOffs1, u, suPoints), ppi);
ARow2 := ARow1;
while (totH_px < h_px) do
rowH := CalcRowHeight(ARow2);
rowH_px := ptsToPx(FWorkbook.ConvertUnits(rowH, u, suPoints), ppi);
totH_px := totH_px + rowH_px;
if totH_px > h_px then
ARowOffs2 := FWorkbook.ConvertUnits(pxToPts(rowH_px - (totH_px - h_px), ppi), suPoints, u);
inc(ARow2);
else // Use workbook units for calculation
totH := -ARowOffs1;
while (totH < AHeight) do
totH := totH + rowH;
if totH >= AHeight then
ARowOffs2 := rowH - (totH - AHeight);
Adds an embedded image to the worksheet
@param ARow Index of the row at which the image begins (top edge)
@param ACol Index of the column at which the image begins (left edge)
@param AFileName Name of the image file
@param AOffsetX The image is offset horizontally from the left edge of
the anchor cell. May reach into another cell.
Value is in workbook units.
@param AOffsetY The image is offset vertically from the top edge of the
anchor cell. May reach into another cell.
@param AScaleX Horizontal scaling factor of the image
@param AScaleY Vertical scaling factor of the image
@return Index into the internal image list.
function TsWorksheet.WriteImage(ARow, ACol: Cardinal; AFileName: String;
AScaleX: Double = 1.0; AScaleY: Double = 1.0): Integer;
idx: Integer;
// Does the image already exist?
idx := Workbook.FindEmbeddedObj(AFileName);
// No? Open and store in embedded object list.
if idx = -1 then
idx := Workbook.AddEmbeddedObj(AFileName);
// An error has occured? Error is already logged. Just exit.
// Everything ok here...
Result := WriteImage(ARow, ACol, idx, AOffsetX, AOffsetY, AScaleX, AScaleY);
Adds an embedded image to the worksheet. The image passed in a stream.
@param AStream Stream which contains the image data
@param ASize Number ob bytes to be read from the input stream.
function TsWorksheet.WriteImage(ARow, ACol: Cardinal; AStream: TStream;
AScaleX: Double = 1.0; AScaleY: Double = 1.0;
ASize: Int64 = -1): Integer;
// Copy the stream to a new item in embedded object list.
idx := Workbook.AddEmbeddedObj(AStream, '', ASize);
function TsWorksheet.WriteImage(ARow, ACol: Cardinal; AImageIndex: Integer;
New(img);
InitImageRecord(img^, ARow, ACol, AOffsetX, AOffsetY, AScaleX, AScaleY);
img^.Index := AImageIndex;
Result := FImages.Add(img);
{@@ Assigns a hyperlink to an image. The image is specified by its index in the
internal image list}
procedure TsWorksheet.AddHyperlinkToImage(AImageIndex: Integer; ATarget: String;
img := GetPointerToImage(AImageIndex);
if Assigned(img) then begin
img^.HyperlinkTarget := ATarget;
img^.HyperlinkToolTip := AToolTip;
Removes an image from the internal image list.
The image is identified by its index.
The image stream (stored by the workbook) is retained.
procedure TsWorksheet.RemoveImage(AIndex: Integer);
if (img <> nil) then begin
if (img^.Picture <> nil) then img^.Picture.Free;
img^.HyperlinkTarget := '';
img^.HyperlinkToolTip := '';
Dispose(img);
FImages.Delete(AIndex);
Removes all image from the internal image list.
The image streams (stored by the workbook), however, are retained because
images may also be used as header/footer images.
procedure TsWorksheet.RemoveAllImages;
for i := FImages.Count-1 downto 0 do
RemoveImage(i);
Removes the comment from a cell and releases the memory occupied by the node.
procedure TsWorksheet.RemoveComment(ACell: PCell);
FComments.DeleteComment(ACell^.Row, ACell^.Col);
Exclude(ACell^.Flags, cfHasComment);
Removes a cell from its tree container. DOES NOT RELEASE ITS MEMORY!
@param ARow Row index of the cell to be removed
@param ACol Column index of the cell to be removed
@return Pointer to the cell removed
function TsWorksheet.RemoveCell(ARow, ACol: Cardinal): PCell;
if Result <> nil then FCells.Remove(Result);
Removes a cell and releases its memory. If a comment is attached to the
cell then it is removed and releaded as well.
Just for internal usage since it does not modify the other cells affected.
And it does not change other records depending on the cell (comments,
merged ranges etc).
procedure TsWorksheet.RemoveAndFreeCell(ARow, ACol: Cardinal);
FCells.DeleteCell(ARow, ACol);
procedure TsWorksheet.SetBiDiMode(AValue: TsBiDiMode);
if AValue = FBiDiMode then
FBiDiMode := AValue;
FWorkbook.ChangedWorksheet(Self);
Enables (or disables) protection of the worksheet. Details of protection are
specified in the set of Sheetprotection options
procedure TsWorksheet.Protect(AEnable: Boolean);
if AEnable then
Include(FOptions, soProtected) else
Exclude(FOptions, soProtected);
FWorkbook.ChangedWorksheet(self);
Hides the worksheet. Makes sure that the last worksheet cannot be hidden.
Notifies visual controls
procedure TsWorksheet.Hide;
idx, n: Integer;
if IsHidden then
if FWorkbook.GetVisibleWorksheetCount = 1 then
Options := Options + [soHidden];
if (FWorkbook.ActiveWorksheet = self) then begin
n := FWorkbook.GetWorksheetCount;
idx := FWorkbook.GetWorksheetIndex(self) + 1;
if idx < n then begin
sheet := FWorkbook.GetWorksheetByIndex(idx);
while Assigned(sheet) and sheet.IsHidden do begin
inc(idx);
if sheet <> nil then begin
FWorkbook.SelectWorksheet(sheet);
idx := FWorkbook.GetWorkSheetIndex(self) - 1;
if idx >= 0 then begin
dec(idx);
Shows the worksheet if is was previously hidden
Useful for visual controls
procedure TsWorksheet.Show;
if not (soHidden in Options) then
Options := Options - [soHidden];
Returns TRUE if the worksheet is hidden
function TsWorksheet.IsHidden: Boolean;
Result := soHidden in Options;
Setter for the worksheet name property. Checks if the name is valid, and
exits without any change if not. Creates an event OnChangeWorksheet.
procedure TsWorksheet.SetName(const AName: String);
if AName = FName then
if (FWorkbook <> nil) then //and FWorkbook.ValidWorksheetName(AName) then
FName := AName;
if FWorkbook.FReadWriteFlag = rwfNormal then begin
FWorkbook.RebuildFormulas;
if (FWorkbook.FNotificationLock = 0) and Assigned(FWorkbook.FOnRenameWorksheet) then
FWorkbook.FOnRenameWorksheet(FWorkbook, self);
Compare function for sorting of rows and columns called directly by Sort()
The compare algorithm starts with the first key parameters. If cells are
found to be "equal" the next parameter is set is used until a difference is
found, or all parameters are used.
@param ARow1 Row index of the first cell to be compared
@param ACol1 Column index of the first cell to be compared
@param ARow2 Row index of the second cell to be compared
@parem ACol2 Column index of the second cell to be compared
@param ASortOptions Sorting options: case-insensitive and/or descending
@return -1 if the first cell is "smaller", i.e. is sorted in front of the
second one
+1 if the first cell is "larger", i.e. is behind the second one
0 if both cells are equal
------------------------------------------------------------------------------- }
function TsWorksheet.DoCompareCells(AColRow1, AColRow2: Cardinal): Integer;
cell1, cell2: PCell; // Pointers to the cells to be compared
key: Integer;
key := 0;
while (Result = 0) and (key <= High(FSortParams.Keys)) do
if FSortParams.SortByCols then
cell1 := FindCell(AColRow1, FSortParams.Keys[key].ColRowIndex);
cell2 := FindCell(AColRow2, FSortParams.Keys[key].ColRowIndex);
cell1 := FindCell(FSortParams.Keys[key].ColRowIndex, AColRow1);
cell2 := FindCell(FSortParams.Keys[key].ColRowIndex, AColRow2);
if Assigned(FOnFullCompareCells) then
FOnFullCompareCells(Self, cell1, cell2, FSortParams.Keys[Key], Result)
else if Assigned(FOnCompareCells) then
FOnCompareCells(Self, cell1, cell2, Result)
Result := DefaultCompareCells(cell1, cell2, FSortParams.Keys[key]);
inc(key);
Compare function for sorting of rows and columns. Called by DoCompareCells.
@param ACell1 Pointer to the first cell of the comparison
@param ACell2 Pointer to the second cell of the comparison
@param ASortKey Sorting criteria: sorted column/row, descending,
case-insensitive, numbers first, etc.
@return -1 if the first cell is "smaller"
+1 if the first cell is "larger",
0 if both cells are "equal"
Date/time and boolean cells are sorted like number cells according
to their number value
Label cells are sorted as UTF8 strings.
In case of mixed cell content types the order is determined by
the parameter Priority of the SortParams.
Empty cells are always at the end (in both ascending and descending
order)
function TsWorksheet.DefaultCompareCells(ACell1, ACell2: PCell;
ASortKey: TsSortKey): Integer;
// Sort priority in Excel:
// numbers < alpha < blank (ascending)
// alpha < numbers < blank (descending)
number1, number2: Double;
if (ACell1 = nil) or (ACell1^.ContentType = cctEmpty)
then begin
if (ACell2 <> nil) and (ACell2^.ContentType <> cctEmpty) then
Result := +1; // Empty cells go to the end
Exit; // Avoid SortOrder to bring the empty cell to the top!
if (ACell2 = nil) or (ACell2^.ContentType = cctEmpty) then
Result := -1; // Empty cells go to the end
exit; // Avoid SortOrder to bring the empty cell to the top!
if (ACell1^.ContentType = cctUTF8String) then begin
if (ACell2^.ContentType = cctUTF8String) then
if ssoCaseInsensitive in ASortKey.Options then
Result := AnsiCompareText(ACell1^.UTF8StringValue, ACell2^.UTF8StringValue)
Result := AnsiCompareStr(ACell1^.UTF8StringValue, ACell2^.UTF8StringValue);
if ssoAlphaBeforeNum in ASortKey.Options then
Result := -1
Result := 1;
Result := +1
Result := -1;
ReadNumericValue(ACell1, number1);
ReadNumericValue(ACell2, number2);
Result := CompareValue(number1, number2);
if ssoDescending in ASortKey.Options then
Result := -Result;
Exchanges columns or rows, depending on value of "AIsColumn"
@param AIsColumn if true the exchange is done for columns, otherwise for rows
@param AIndex Index of the column (if AIsColumn is true) or the row
(if AIsColumn is false) which is to be exchanged with the
one having index "WidthIndex"
@param WithIndex Index of the column (if AIsColumn is true) or the row
(if AIsColumn is false) with which "AIndex" is to be
replaced.
@param AFromIndex First row (if AIsColumn is true) or column (if AIsColumn
is false) which is affected by the exchange
@param AToIndex Last row (if AIsColumn is true) or column (if AsColumn is
false) which is affected by the exchange
procedure TsWorksheet.DoExchangeColRow(AIsColumn: Boolean;
AIndex, WithIndex: Cardinal; AFromIndex, AToIndex: Cardinal);
if AIsColumn then
for r := AFromIndex to AToIndex do
ExchangeCells(r, AIndex, r, WithIndex)
for c := AFromIndex to AToIndex do
ExchangeCells(AIndex, c, WithIndex, c);
Sorts a range of cells defined by the cell rectangle from ARowFrom/AColFrom
to ARowTo/AColTo according to the parameters specified in ASortParams
@param ASortParams Set of parameters to define sorting along rows or colums,
the sorting key column or row indexes, and the sorting
directions
@param ARange Cell range to be sorted, in Excel notation, such as 'A1:C8'
procedure TsWorksheet.Sort(ASortParams: TsSortParams; ARange: String);
r1,c1, r2,c2: Cardinal;
Sort(ASortParams, r1, c1, r2, c2)
raise EFPSpreadsheet.CreateFmt(rsNoValidCellRangeAddress, [ARange]);
@param ARowFrom Top row of the range to be sorted
@param AColFrom Left column of the range to be sorted
@param ARowTo Last row of the range to be sorted
@param AColTo Right column of the range to be sorted
procedure TsWorksheet.Sort(const ASortParams: TsSortParams;
ARowFrom, AColFrom, ARowTo, AColTo: Cardinal);
// code "borrowed" from grids.pas and adapted to multi-key sorting
procedure QuickSort(L,R: Integer);
I,J: Integer;
P: Integer;
I := L;
J := R;
P := (L + R) div 2;
if ASortParams.SortByCols then
while DoCompareCells(P, I) > 0 do inc(I);
while DoCompareCells(P, J) < 0 do dec(J);
if I <= J then
if I <> J then
if DoCompareCells(I, J) <> 0 then
DoExchangeColRow(not ASortParams.SortByCols, J,I, AColFrom, AColTo);
DoExchangeColRow(not ASortParams.SortByCols, J,I, ARowFrom, ARowTo);
if P = I then
P := J
if P = J then
P := I;
inc(I);
dec(J);
until I > J;
if L < J then
QuickSort(L, J);
L := I;
until I >= R;
function ContainsMergedCells: boolean;
result := false;
for cell in Cells.GetRangeEnumerator(ARowFrom, AColFrom, ARowTo, AColTo) do
if IsMerged(cell) then
exit(true);
if ContainsMergedCells then
raise EFPSpreadsheet.Create(rsCannotSortMerged);
FSortParams := ASortParams;
QuickSort(ARowFrom, ARowTo)
QuickSort(AColFrom, AColTo);
ChangedCell(ARowFrom, AColFrom);
Marks a specified cell as "selected". Only needed by the visual controls.
procedure TsWorksheet.SelectCell(ARow, ACol: Cardinal);
// Avoid selecting a non-base cell of a merged block.
cell := FindMergeBase(cell);
ACol := cell^.Col;
FActiveCellRow := ARow;
FActiveCellCol := ACol;
if FWorkbook.NotificationsEnabled and Assigned(FOnSelectCell) then
FOnSelectCell(Self, ARow, ACol);
Clears the list of seleccted cell ranges
Only needed by the visual controls.
procedure TsWorksheet.ClearSelection;
SetLength(FSelection, 0);
Deletes all selected cells (delete = make them empty)
procedure TsWorksheet.DeleteSelection;
for i:=0 to High(FSelection) do
for r := FSelection[i].Row1 to FSelection[i].Row2 do
for c := FSelection[i].Col1 to FSelection[i].Col2 do
cell := FindCell(r, c);
DeleteCell(cell);
ClearSelection;
Erases all selected cells (erase = keep cell, but delete content)
If AKeepFormat is true the cell format is left unchanged.
procedure TsWorksheet.EraseSelection(AKeepFormat: Boolean = false);
EraseCell(cell, AKeepFormat);
Returns the list of selected cell ranges
function TsWorksheet.GetSelection: TsCellRangeArray;
SetLength(Result, Length(FSelection));
Result[i] := FSelection[i];
Returns all selection ranges as an Excel string
function TsWorksheet.GetSelectionAsString: String;
RELATIVE = [rfRelRow, rfRelCol, rfRelRow2, rfRelCol2];
L: TStringList;
L := TStringList.Create;
for i:=0 to Length(FSelection)-1 do
with FSelection[i] do
L.Add(GetCellRangeString(Row1, Col1, Row2, Col2, RELATIVE, true));
L.Delimiter := DefaultFormatSettings.ListSeparator;
L.StrictDelimiter := true;
Result := L.DelimitedText;
L.Free;
Returns the number of selected cell ranges
function TsWorksheet.GetSelectionCount: Integer;
Result := Length(FSelection);
Returns the index of the selected block which contains the active cell
function TsWorksheet.GetSelectionRangeIndexOfActiveCell: Integer;
sel: TsCellRange;
for Result := 0 to High(FSelection) do
sel := FSelection[Result];
if (FActiveCellRow >= sel.Row1) and (FActiveCellRow <= sel.Row2) and
(FActiveCellCol >= sel.Col1) and (FActiveCellCol <= sel.Col2) then exit;
Marks an array of cell ranges as "selected". Only needed for visual controls
procedure TsWorksheet.SetSelection(const ASelection: TsCellRangeArray);
SetLength(FSelection, Length(ASelection));
FSelection[i] := ASelection[i];
Uses the passed parameters a TopRow and LeftCol. These are used by the
TsWorksheetGrid to scroll the visible grid such that the corresponding cell
is at the top/left.
procedure TsWorksheet.ScrollTo(ANewTopRow, ANewLeftCol: Cardinal);
FTopRow := ANewTopRow;
FLeftCol := ANewLeftCol;
Helper method to update internal caching variables
procedure TsWorksheet.UpdateCaches;
FFirstColIndex := GetFirstColIndex(true);
FFirstRowIndex := GetFirstRowIndex(true);
FLastColIndex := GetLastColIndex(true);
FLastRowIndex := GetLastRowIndex(true);
Setter method for the count of columns to be written in VirtualMode
procedure TsWorksheet.SetVirtualColCount(AValue: Cardinal);
if FWorkbook.FReadWriteFlag = rwfWrite then exit;
FVirtualColCount := AValue;
Setter method for the count of rows to be written in VirtualMode
procedure TsWorksheet.SetVirtualRowCount(AValue: Cardinal);
FVirtualRowCount := AValue;
Setter method for the zoom factor
procedure TsWorksheet.SetZoomFactor(AValue: Double);
if AValue = FZoomFactor then exit;
FZoomFactor := AValue;
if Assigned(FOnZoom) then FOnZoom(Self);
Writes UTF-8 encoded text to a cell.
On formats that don't support unicode, the text will be converted
to ISO Latin 1.
@param AText The text to be written encoded in utf-8
@param ARichTextParams Array of formatting instructions for characters or
groups of characters (see TsRichTextParam).
@return Pointer to cell created or used
@see TsRichTextParams
@see TsRichTextParam
function TsWorksheet.WriteText(ARow, ACol: Cardinal; AText: String;
ARichTextParams: TsRichTextParams = nil): PCell;
WriteText(Result, AText, ARichTextParams);
function TsWorksheet.WriteUTF8Text(ARow, ACol: Cardinal; AText: String;
@note The cell content will be set to cctEmpty if the string is empty.
procedure TsWorksheet.WriteText(ACell: PCell; AText: String;
ARichTextParams: TsRichTextParams = nil);
hyperlink: TsHyperlink;
if (AText = '') and HasHyperlink(ACell) then
hyperlink := ReadHyperlink(ACell);
AText := hyperlink.Target;
if pos('file:', hyperlink.Target)=1 then
URIToFileName(AText, AText);
ForcePathDelims(AText);
// Delete any pre-existing formula
ACell^.UTF8StringValue := AText;
if (AText = '') then
{ Initially, the cell was destroyed here if AText = '' and the cell is not
formatted, has no comment, no hyperlink, no formula, and is not merged.
This is not good... The calling procedure cannot be notified that
ACell is destroyed here.
See issue #0030049 }
ACell^.ContentType := cctEmpty;
SetLength(ACell^.RichTextParams, Length(ARichTextParams));
if Length(ARichTextParams) > 0 then
for i:=0 to High(ARichTextParams) do
ACell^.RichTextParams[i] := ARichTextParams[i];
procedure TsWorksheet.WriteUTF8Text(ACell: PCell; AText: String;
WriteText(ACell, AText, ARichTextParams);
Writes text containing HTML codes to a cell. Here are the allowed HTML codes:
<b>, <strong> ... bold text
<i>, <em> ........ italic text
<u>, <ins> ....... underlined text
<s>, <del> ....... strike-out text
<sub> ............ subscript
<sup> ............ superscript
<font tags> ...... full font selection. "tags" can be:
face="..." ... font name
size="..." ... font size, in pt, em, px, % (add units!)
color="..." .. font color (e.g. red, or #FF0000).
@param AText The text containing the html codes
function TsWorksheet.WriteTextAsHTML(ARow, ACol: Cardinal; AText: String): PCell;
WriteTextAsHTML(Result, AText);
Removes any previously assigned richtext parameters from a specific cell.
This action fully restores the font of the cell.
procedure TsWorksheet.DeleteRichTextParams(ACell: PCell);
if (ACell <> nil) and (Length(ACell^.RichTextParams) > 0) then
SetLength(ACell^.RichTextParams, 0);
procedure TsWorksheet.WriteTextAsHTML(ACell: PCell; AText: String);
plainText: String;
rtParams: TsRichTextParams;
HTMLToRichText(FWorkbook, ReadCellFont(ACell), AText, plainText, rtParams);
WriteText(ACell, plainText, rtParams);
Writes a floating-point number to a cell, does not change the number format
@param ARow Cell row index
@param ACol Cell column index
@param ANumber Number to be written
function TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: Double): PCell;
WriteNumber(Result, ANumber);
procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double);
// Delete any pre-existing formula, but only if FormulaLock is ON.
// Write number to cell
ACell^.ContentType := cctNumber;
ACell^.NumberValue := ANumber;
Writes a floating-point number to a cell
@param ANumFormat Identifier for a built-in number format,
e.g. nfFixed (optional)
@param ADecimals Number of decimal places used for formatting (optional)
@param AMinIntDigits Minimum count of digits before the decimal separator
@see TsNumberFormat
function TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: Double;
AMinIntDigits: Integer = 1): PCell;
WriteNumber(Result, ANumber, ANumFormat, ADecimals, AMinIntDigits);
@param ANumFormat Identifier for a built-in number format, e.g. nfFixed
@param ADecimals Optional number of decimal places used for formatting
If ANumFormat is nfFraction the ADecimals defines the
digits of Numerator and denominator.
procedure TsWorksheet.WriteNumber(ACell: PCell; ANumber: Double;
AMinIntDigits: Integer = 1);
if IsDateTimeFormat(ANumFormat) or IsCurrencyFormat(ANumFormat) then
raise EFPSpreadsheet.Create(rsInvalidNumberFormat);
// Write value to cell
fmt := Workbook.GetCellFormat(ACell^.FormatIndex);
fmt.NumberFormat := ANumFormat;
if ANumFormat <> nfGeneral then begin
Include(fmt.UsedFormattingFields, uffNumberFormat);
if ANumFormat = nfFraction then
if ADecimals = 0 then ADecimals := 1;
nfs := '# ' + DupeString('?', ADecimals) + '/' + DupeString('?', ADecimals);
nfs := BuildNumberFormatString(fmt.NumberFormat, Workbook.FormatSettings, ADecimals, AMinIntDigits);
fmt.NumberFormatIndex := Workbook.AddNumberFormat(nfs);
end else begin
Exclude(fmt.UsedFormattingFields, uffNumberFormat);
fmt.NumberFormatIndex := -1;
ACell^.FormatIndex := Workbook.AddCellFormat(fmt);
Writes a floating point number to the cell and uses a custom number format
specified by the format string.
Note that fpspreadsheet may not be able to detect the formatting when reading
the file.
@param ANumFormat Format identifier (nfCustom)
@param ANumFormatString String of formatting codes (such as 'dd/mmm'
ANumFormat: TsNumberFormat; ANumFormatString: String): PCell;
WriteNumber(Result, ANumber, ANumFormat, ANumFormatString);
@param ANumFormatString String of formatting codes (such as 'dd/mmm' )
ANumFormat: TsNumberFormat; ANumFormatString: String);
parser := TsNumFormatParser.Create(ANumFormatString, FWorkbook.FormatSettings);
// Format string ok?
if parser.Status <> psOK then
raise EFPSpreadsheet.Create(rsNoValidNumberFormatString);
// Make sure that we do not write a date/time value here
if parser.IsDateTimeFormat
then raise EFPSpreadsheet.Create(rsInvalidNumberFormat);
// If format string matches a built-in format use its format identifier,
// All this is considered when calling Builtin_NumFormat of the parser.
fmt.NumberFormatIndex := Workbook.AddNumberFormat(ANumFormatString);
Writes an empty cell
@param KeepFormula Does not erase the formula. Off by default because it
would be very confusing if the formula had a
non-blank result.
@return Pointer to the cell
Note: Empty cells are useful when, for example, a border line extends
along a range of cells including empty cells.
function TsWorksheet.WriteBlank(ARow, ACol: Cardinal;
KeepFormula: Boolean = false): PCell;
WriteBlank(Result, KeepFormula);
@param ACel Pointer to the cell
procedure TsWorksheet.WriteBlank(ACell: PCell; KeepFormula: Boolean = false);
if not KeepFormula then
// NOTE: Erase the formula because if it would return a non-blank result
// this would be very confusing!
WriteText(ACell, '') // '' will be replaced by the hyperlink target.
Writes a boolean cell
@param AValue The boolean value
function TsWorksheet.WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean): PCell;
WriteBoolValue(Result, AValue);
procedure TsWorksheet.WriteBoolValue(ACell: PCell; AValue: Boolean);
ACell^.ContentType := cctBool;
ACell^.BoolValue := AValue;
Writes data defined as a string into a cell. Depending on the structure of the
string, the worksheet tries to guess whether it is a number, a date/time or
a text and calls the corresponding writing method.
@param AValue Value to be written into the cell given as a string. Depending
on the structure of the string, however, the value is written
as a number, a date/time or a text.
function TsWorksheet.WriteCellValueAsString(ARow, ACol: Cardinal;
AValue: String): PCell;
WriteCellValueAsString(Result, AValue);
@param AFormatSettings FormatSettings record used for conversion of strings
with date/time, numbers etc.
AValue: String; const AFormatSettings: TFormatSettings): PCell;
WriteCellValueAsString(Result, AValue, AFormatSettings);
Conversion of strings to values is done by means of the FormatSettings
defined in the workbook.
procedure TsWorksheet.WriteCellValueAsString(ACell: PCell; AValue: String);
WriteCellValueAsString(ACell, AValue, FWorkbook.FormatSettings);
Uses the provided FormatSettings for date/time etc.
procedure TsWorksheet.WriteCellValueAsString(ACell: PCell; AValue: String;
const AFormatSettings: TFormatSettings);
const // isAMPM isLongTime
TIME_FMT: array[boolean, boolean] of TsNumberFormat = (
(nfShortTime, nfLongTime),
(nfShortTimeAM, nfLongTimeAM)
);
isPercent: Boolean;
number: Double;
currSym: String;
maxDig: Integer;
isMixed: Boolean;
isAMPM: Boolean;
isLongTime: Boolean;
plain: String;
ucValue: String;
// Empty cell
WriteText(ACell, '');
// Force text format by putting an apostrophe at the text beginning
if AValue[1] = '''' then
WriteNumberFormat(ACell, nfText);
// Typing an apostrophe in front of the text bypasses format detection and
// takes the text literally.
if AValue[1] = '''' then begin
WriteText(ACell, Copy(AValue, 2, MaxInt));
// Cell format
fmtIndex := GetEffectiveCellFormatIndex(ACell);
fmt := Workbook.GetCellFormat(fmtIndex);
numFmtParams := Workbook.GetNumberFormat(fmt.NumberFormatIndex);
ACell^.FormatIndex := fmtIndex;
// Handle some cases first in which content autodetection is not wanted.
if not (soAutoDetectCellType in FOptions) then begin
// Write text content if the cell has number format nfText
if IsTextFormat(numFmtParams) then begin
WriteText(ACell, AValue);
isPercent := Pos('%', AValue) = Length(AValue);
if isPercent then Delete(AValue, Length(AValue), 1);
// Try to detect the cell content type automatically
if TryStrToCurrency(AValue, number, currSym, AFormatSettings) then
if (soAutoDetectCellType in FOptions) then begin
WriteCurrency(ACell, number, nfCurrencyRed, -1, currSym);
WriteNumber(ACell, number);
// Check for a fraction string
if TryFractionStrToFloat(AValue, number, ismixed, maxdig) then
WriteFractionFormat(ACell, ismixed, maxdig, maxdig);
if IsTextFormat(numFmtParams) then
// Check for a "number" value (floating point, or integer)
if TryStrToFloat(AValue, number, AFormatSettings) then
if isPercent then
WriteNumber(ACell, number/100, nfPercentage)
if IsDateTimeFormat(numFmtParams) then
WriteNumber(ACell, number, nfGeneral)
// Use pre-formatted style
// 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, AFormatSettings) then
if number < 1.0 then // this is a time alone
if not IsTimeFormat(numFmtParams) then
ucValue := Uppercase(AValue);
isAMPM := (pos('AM', ucValue) > 0) or (pos('PM', ucValue) > 0);
isLongTime := IsLongTimeFormat(AValue, AFormatSettings.TimeSeparator);
WriteDateTime(ACell, number, TIME_FMT[isAMPM, isLongTime]);
WriteDateTime(ACell, number);
if frac(number) = 0.0 then // this is a date alone
if pos(' ', AValue) > 0 then
WriteDateTime(ACell, number, nfShortDateTime)
WriteDateTime(ACell, number, nfShortDate);
if not IsDateTimeFormat(fmt.NumberFormat) then
HTMLToRichText(FWorkbook, ReadCellFont(ACell), AValue, plain, rtParams);
WriteText(ACell, plain, rtParams);
Writes a currency value to a given cell. Its number format can be provided
optionally by specifying various parameters.
@param AValue Number value to be written
@param ANumFormat Format identifier, must be nfCurrency, or nfCurrencyRed.
@param ADecimals Number of decimal places
@param APosCurrFormat Code specifying the order of value, currency symbol
and spaces (see pcfXXXX constants)
@param ANegCurrFormat Code specifying the order of value, currency symbol,
spaces, and how negative values are shown
(see ncfXXXX constants)
@param ACurrencySymbol String to be shown as currency, such as '$', or 'EUR'.
In case of '?' the currency symbol defined in the
workbook's FormatSettings is used.
function TsWorksheet.WriteCurrency(ARow, ACol: Cardinal; AValue: Double;
ANegCurrFormat: Integer = -1): PCell;
WriteCurrency(Result, AValue, ANumFormat, ADecimals, ACurrencySymbol,
APosCurrFormat, ANegCurrFormat);
@param ANumFormat Format identifier, must be nfCurrency or nfCurrencyRed.
procedure TsWorksheet.WriteCurrency(ACell: PCell; AValue: Double;
ANegCurrFormat: Integer = -1);
if ADecimals = -1 then
ADecimals := Workbook.FormatSettings.CurrencyDecimals;
if APosCurrFormat = -1 then
APosCurrFormat := Workbook.FormatSettings.CurrencyFormat;
if ANegCurrFormat = -1 then
ANegCurrFormat := Workbook.FormatSettings.NegCurrFormat;
if ACurrencySymbol = '?' then
ACurrencySymbol := Workbook.FormatSettings.CurrencyString;
RegisterCurrency(ACurrencySymbol);
nfs := BuildCurrencyFormatString(
ANumFormat,
Workbook.FormatSettings,
ADecimals,
APosCurrFormat, ANegCurrFormat,
ACurrencySymbol);
WriteCurrency(ACell, AValue, ANumFormat, nfs);
Writes a currency value to a given cell. Its number format is specified by
means of a format string.
@param ANumFormatString String of formatting codes, including currency symbol.
Can contain sections for different formatting of positive
and negative number.
Example: '"EUR" #,##0.00;("EUR" #,##0.00)'
WriteCurrency(Result, AValue, ANumFormat, ANumFormatString);
if not IsCurrencyFormat(ANumFormat) then
raise EFPSpreadsheet.Create('[TsWorksheet.WriteCurrency] ANumFormat can only be nfCurrency or nfCurrencyRed');
if (ACell <> nil) then begin
ACell^.NumberValue := AValue;
fmt := FWorkbook.GetCellFormat(ACell^.FormatIndex);
Writes a date/time value to a cell, does not change number format
@param AValue The date/time/datetime to be written
function TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime): PCell;
WriteDateTime(Result, AValue);
Writes a date/time value to a cell. Does not change number format
procedure TsWorksheet.WriteDateTime(ACell: PCell; AValue: TDateTime);
// Delete pre-existing formula
// Write date to cell
ACell^.ContentType := cctDateTime;
ACell^.DateTimeValue := AValue;
Writes a date/time value to a cell
@param ANumFormat The format specifier, e.g. nfShortDate (optional)
If not specified format is not changed.
@param ANumFormatStr Format string, used only for nfCustom or nfTimeInterval.
Note: at least Excel xls does not recognize a separate datetime cell type:
a datetime is stored as a (floating point) number, and the cell is formatted
as a date (either built-in or a custom format).
function TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime;
ANumFormat: TsNumberFormat; ANumFormatStr: String = ''): PCell;
WriteDateTime(Result, AValue, ANumFormat, ANumFormatStr);
procedure TsWorksheet.WriteDateTime(ACell: PCell; AValue: TDateTime;
ANumFormat: TsNumberFormat; ANumFormatStr: String = '');
// Date/time is actually a number field in Excel.
// To make sure it gets saved correctly, set a date format (instead of General).
// The user can choose another date format if he wants to
if ANumFormat = nfGeneral then begin
if trunc(AValue) = 0 then // time only
ANumFormat := nfLongTime
else if frac(AValue) = 0.0 then // date only
ANumFormat := nfShortDate;
if ANumFormatStr = '' then
ANumFormatStr := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings, ANumFormatStr)
if ANumFormat = nfTimeInterval then
ANumFormatStr := AddIntervalBrackets(ANumFormatStr);
// Check whether the formatstring is for date/times.
if ANumFormatStr <> '' then begin
parser := TsNumFormatParser.Create(ANumFormatStr, Workbook.FormatSettings);
raise EFPSpreadsheet.CreateFmt(rsNoValidNumberFormatString, [ANumFormatStr]);
// Make sure that we do not use a number format for date/times values.
if not parser.IsDateTimeFormat then
raise EFPSpreadsheet.CreateFmt(rsInvalidDateTimeFormat, [ANumFormatStr]);
// Avoid possible duplication of standard formats
if ANumFormat = nfCustom then
ANumFormat := parser.NumFormat;
fmt.NumberFormatStr := ANumFormatStr;
fmt.NumberFormatIndex := Workbook.AddNumberFormat(fmt.NumberFormatStr);
@param ARow The row index of the cell
@param ACol The column index of the cell
@param ANumFormatStr Format string (the format identifier nfCustom is used to
classify the format).
ANumFormatStr: String): PCell;
WriteDateTime(Result, AValue, ANumFormatStr);
ANumFormatStr: String);
WriteDateTime(ACell, AValue, nfCustom, ANumFormatStr);
Adds a date/time format to the formatting of a cell
@param ANumFormat Identifier of the format to be applied (nfXXXX constant)
@param ANumFormatString Optional string of formatting codes. Is only considered
if ANumberFormat is nfCustom.
function TsWorksheet.WriteDateTimeFormat(ARow, ACol: Cardinal;
ANumFormat: TsNumberFormat; const ANumFormatString: String = ''): PCell;
WriteDateTimeFormat(Result, ANumFormat, ANumFormatString);
@param ANumFormat Identifier of the format to be applied (nxXXXX constant)
@param ANumFormatString optional string of formatting codes. Is only considered
procedure TsWorksheet.WriteDateTimeFormat(ACell: PCell;
ANumFormat: TsNumberFormat; const ANumFormatString: String = '');
nfp: TsNumFormatParams;
isTextFmt, wasTextFmt: Boolean;
oldVal: String;
if not ((ANumFormat in [nfGeneral, nfCustom]) or IsDateTimeFormat(ANumFormat)) then
raise EFPSpreadsheet.Create('WriteDateTimeFormat can only be called with date/time formats.');
isTextFmt := false;
wasTextFmt := false;
if (ANumFormat <> nfGeneral) then
nfp := Workbook.GetNumberFormat(fmt.NumberFormatIndex);
wasTextFmt := IsTextFormat(nfp);
oldval := ReadAsText(ACell);
if (ANumFormatString = '') then
nfs := BuildDateTimeFormatString(ANumFormat, Workbook.FormatSettings)
nfs := ANumFormatString;
isTextFmt := (nfs = '@');
fmt.NumberFormatStr := '';
fmt.NumberFormatStr := nfs;
if isTextFmt then
WriteText(ACell, oldval)
if wasTextFmt then
WriteCellValueAsString(ACell, ACell^.UTF8StringValue);
Formats the number in a cell to show a given count of decimal places.
Is ignored for non-decimal formats (such as most date/time formats).
@param ARow Row indows of the cell considered
@param ACol Column indows of the cell considered
@param ADecimals Number of decimal places to be displayed
function TsWorksheet.WriteDecimals(ARow, ACol: Cardinal; ADecimals: Byte): PCell;
WriteDecimals(Result, ADecimals);
procedure TsWorksheet.WriteDecimals(ACell: PCell; ADecimals: Byte);
numFmtStr: String;
if (ACell = nil) or (ACell^.ContentType <> cctNumber) then
numFmt := FWorkbook.GetNumberFormat(fmt.NumberFormatIndex);
numFmtStr := numFmt.NumFormatStr
numFmtStr := '0.00';
parser := TsNumFormatParser.Create(numFmtStr, Workbook.FormatSettings);
parser.Decimals := ADecimals;
numFmtStr := parser.FormatString;
fmt.NumberFormatIndex := Workbook.AddNumberFormat(numFmtStr);
Writes an error value to a cell.
@param AValue The error code value
@see TsErrorValue
function TsWorksheet.WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue): PCell;
WriteErrorValue(Result, AValue);
@param ACol Pointer to the cell to be written
procedure TsWorksheet.WriteErrorValue(ACell: PCell; AValue: TsErrorValue);
ACell^.ContentType := cctError;
ACell^.ErrorValue := AValue;
Writes a formula to a given cell
@param AFormula The formula string to be written. A leading "=" will be removed.
@param ALocalized If true, the formula is expected to have decimal and list
separators of the workbook's FormatSettings. Otherwise
uses dot and comma, respectively.
@param R1C1Mode If true, the formula is expected to contain cell references
in Excel's "R1C1" notation; otherwise "A1" references are
expected.
function TsWorksheet.WriteFormula(ARow, ACol: Cardinal; AFormula: String;
ALocalized: Boolean = false; R1C1Mode: Boolean = false): PCell;
WriteFormula(Result, AFormula, ALocalized, R1C1Mode);
@param AFormula Formula string to be written. A leading '=' will be removed.
If AFormula is '' then an formula already assigned to this
cell is deleted.
procedure TsWorksheet.WriteFormula(ACell: PCell; AFormula: String;
ALocalized: Boolean = false; R1C1Mode: Boolean = false);
if AFormula = '' then begin
if not (boIgnoreFormulas in Workbook.Options) then
// Remove '='; is not stored internally
if (AFormula[1] = '=') then
AFormula := Copy(AFormula, 2, Length(AFormula));
parser.Expression[fdLocalized] := AFormula
if R1C1Mode then
parser.R1C1Expression[ACell] := AFormula
parser.Expression[fdExcelA1] := AFormula;
AFormula := parser.Expression[fdExcelA1];
formula := FFormulas.AddFormula(ACell^.Row, ACell^.Col, AFormula);
on E:Exception do begin
if FWorkbook.FReadWriteFlag = rwfNormal then
raise
else begin
FWorkbook.AddErrorMsg('Formula error in cell "%s!%s": %s', [
FName, GetCellString(ACell^.Row, ACell^.Col), E.Message]
//FFormulas.DeleteFormula(ACell^.Row, ACell^.Col);
if parser.Has3DLinks then
ACell.Flags := ACell.Flags + [cf3dFormula]
ACell.Flags := ACell.Flags - [cf3dFormula];
formula^.Text := AFormula;
formula^.Parser := parser;
// parser will be destroyed by formula
// Set formula flags in cell
// Notify controls of changed cell
Adds a number format to the formatting of a cell
@param ANumFormat Identifier of the format to be applied
@param ACurrencySymbol optional currency symbol in case of nfCurrency
@param APosCurrFormat optional identifier for positive currencies
@param ANegCurrFormat optional identifier for negative currencies
function TsWorksheet.WriteNumberFormat(ARow, ACol: Cardinal;
ANumFormat: TsNumberFormat; ADecimals: Integer; ACurrencySymbol: String = '';
APosCurrFormat: Integer = -1; ANegCurrFormat: Integer = -1): PCell;
WriteNumberFormat(Result, ANumFormat, ADecimals, ACurrencySymbol,
procedure TsWorksheet.WriteNumberFormat(ACell: PCell;
APosCurrFormat: Integer = -1; ANegCurrFormat: Integer = -1);
fmtStr: String;
wasTextFmt: Boolean;
if IsCurrencyFormat(ANumFormat) then
fmtStr := BuildCurrencyFormatString(ANumFormat, Workbook.FormatSettings,
ADecimals, APosCurrFormat, ANegCurrFormat, ACurrencySymbol);
fmtStr := BuildNumberFormatString(ANumFormat,
Workbook.FormatSettings, ADecimals);
fmt.NumberFormatIndex := Workbook.AddNumberFormat(fmtStr);
Formats a number as a fraction
@param ANumFormat Identifier of the format to be applied. Must be
either nfFraction or nfMixedFraction
@param ANumeratorDigts Count of numerator digits
@param ADenominatorDigits Count of denominator digits
function TsWorksheet.WriteFractionFormat(ARow, ACol: Cardinal;
AMixedFraction: Boolean; ANumeratorDigits, ADenominatorDigits: Integer): PCell;
WriteFractionFormat(Result, AMixedFraction, ANumeratorDigits, ADenominatorDigits);
@param ACell Pointer to the cell to be formatted
procedure TsWorksheet.WriteFractionFormat(ACell: PCell;
AMixedFraction: Boolean; ANumeratorDigits, ADenominatorDigits: Integer);
nfs := BuildFractionFormatString(AMixedFraction, ANumeratorDigits, ADenominatorDigits);
WriteNumberFormat(Result, ANumFormat, ANumFormatString);
oldval: String;
fmtStr := BuildNumberFormatString(ANumFormat, Workbook.FormatSettings)
fmtStr := ANumFormatString;
isTextFmt := (fmtstr = '@');
Writes an RPN formula to a cell. An RPN formula is an array of tokens
describing the calculation to be performed.
@param AFormula Array of TsFormulaElements. The array can be created by
using "CreateRPNFormla".
@see TsFormulaElements
@see CreateRPNFormula
function TsWorksheet.WriteRPNFormula(ARow, ACol: Cardinal;
AFormula: TsRPNFormula): PCell;
WriteRPNFormula(Result, AFormula);
describing the calculation to be performed. In addition,the RPN formula is
converted to a string formula.
procedure TsWorksheet.WriteRPNFormula(ACell: PCell; ARPNFormula: TsRPNFormula);
formula := FFormulas.FindFormula(ACell);
formula := FFormulas.AddFormula(ACell^.Row, ACell^.Col);
formula^.Parser := TsSpreadsheetParser.Create(self);
formula^.Parser.RPNFormula := ARPNFormula;
formula^.Text := formula^.Parser.Expression[fdExcelA1];
UseFormulaInCell(ACell, formula);
Adds font specification to the formatting of a cell. Looks in the workbook's
FontList and creates an new entry if the font is not used so far. Returns the
index of the font in the font list.
@param AFontName Name of the font
@param AFontSize Size of the font, in points
@param AFontStyle Set with font style attributes
(don't use those of unit "graphics" !)
@param AFontColor RGB value of the font's color
@param APosition Specifies sub- or superscript text
@return Index of the font in the workbook's font list.
function TsWorksheet.WriteFont(ARow, ACol: Cardinal; const AFontName: String;
APosition: TsFontPosition = fpNormal): Integer;
Result := WriteFont(GetCell(ARow, ACol), AFontName, AFontSize, AFontStyle,
AFontColor, APosition);
@param APosition Specified subscript or superscript text.
function TsWorksheet.WriteFont(ACell: PCell; const AFontName: String;
Result := FWorkbook.FindFont(AFontName, AFontSize, AFontStyle, AFontColor, APosition);
if Result = -1 then
result := FWorkbook.AddFont(AFontName, AFontSize, AFontStyle, AFontColor, APosition);
fmt.FontIndex := Result;
ChangedFont(ACell^.Row, ACell^.Col);
Applies a font to the formatting of a cell. The font is determined by its
index in the workbook's font list:
@param AFontIndex Index of the font in the workbook's font list
function TsWorksheet.WriteFont(ARow, ACol: Cardinal; AFontIndex: Integer): PCell;
WriteFont(Result, AFontIndex);
procedure TsWorksheet.WriteFont(ACell: PCell; AFontIndex: Integer);
if (AFontIndex < 0) or (AFontIndex >= Workbook.GetFontCount) then
raise EFPSpreadsheet.Create(rsInvalidFontIndex);
fmt.FontIndex := AFontIndex;
Replaces the text color used in formatting of a cell. Looks in the workbook's
font list if this modified font has already been used. If not a new font entry
is created. Returns the index of this font in the font list.
@param AFontColor RGB value of the new text color
function TsWorksheet.WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer;
Result := WriteFontColor(GetCell(ARow, ACol), AFontColor);
function TsWorksheet.WriteFontColor(ACell: PCell; AFontColor: TsColor): Integer;
if ACell = nil then begin
fnt := ReadCellFont(ACell);
Result := WriteFont(ACell, fnt.FontName, fnt.Size, fnt.Style, AFontColor);
Replaces the font used in formatting of a cell considering only the font face
and leaving font size, style and color unchanged. Looks in the workbook's
@param AFontName Name of the new font to be used
function TsWorksheet.WriteFontName(ARow, ACol: Cardinal; AFontName: String): Integer;
result := WriteFontName(GetCell(ARow, ACol), AFontName);
function TsWorksheet.WriteFontName(ACell: PCell; AFontName: String): Integer;
result := WriteFont(ACell, AFontName, fnt.Size, fnt.Style, fnt.Color);
Replaces the font size in formatting of a cell. Looks in the workbook's
@param ASize Size of the font to be used (in points).
function TsWorksheet.WriteFontSize(ARow, ACol: Cardinal; ASize: Single): Integer;
Result := WriteFontSize(GetCell(ARow, ACol), ASize);
function TsWorksheet.WriteFontSize(ACell: PCell; ASize: Single): Integer;
Result := WriteFont(ACell, fnt.FontName, ASize, fnt.Style, fnt.Color);
Replaces the font style (bold, italic, etc) in formatting of a cell.
Looks in the workbook's font list if this modified font has already been used.
If not a new font entry is created.
Returns the index of this font in the font list.
@param AStyle New font style to be used
@see TsFontStyle
function TsWorksheet.WriteFontStyle(ARow, ACol: Cardinal;
AStyle: TsFontStyles): Integer;
Result := WriteFontStyle(GetCell(ARow, ACol), AStyle);
function TsWorksheet.WriteFontStyle(ACell: PCell; AStyle: TsFontStyles): Integer;
Result := WriteFont(ACell, fnt.FontName, fnt.Size, AStyle, fnt.Color);
Adds text rotation to the formatting of a cell
@param ARotation How to rotate the text
@return Pointer to cell
@see TsTextRotation
function TsWorksheet.WriteTextRotation(ARow, ACol: Cardinal;
ARotation: TsTextRotation): PCell;
WriteTextRotation(Result, ARotation);
procedure TsWorksheet.WriteTextRotation(ACell: PCell; ARotation: TsTextRotation);
Include(fmt.UsedFormattingFields, uffTextRotation);
fmt.TextRotation := ARotation;
Directly modifies the used formatting fields of a cell.
Only formatting corresponding to items included in this set is executed.
@param AUsedFormatting set of the used formatting fields
@return Pointer to the (existing or created) cell
@see TsUsedFormattingFields
function TsWorksheet.WriteUsedFormatting(ARow, ACol: Cardinal;
AUsedFormatting: TsUsedFormattingFields): PCell;
WriteUsedFormatting(Result, AUsedFormatting);
Directly modifies the used formatting fields of an existing cell.
@param ACell Pointer to the cell to be modified
procedure TsWorksheet.WriteUsedFormatting(ACell: PCell;
AUsedFormatting: TsUsedFormattingFields);
fmt.UsedFormattingFields := AUsedFormatting;
Modifies the background parameters of the format record stored at the
specified index.
@param AFormatIndex Index of the format record to be changed
@param AStyle Fill style ("pattern") to be used - see TsFillStyle
@param APatternColor RGB value of the pattern color
@param ABackgroundColor RGB value of the background color
@return Index of the new format record.
@NOTE When AStyle is fsSolidFill the color is defined by APatternColor,
ABackgroundColor is ignored unless the APatternColor is not
used (scTransparent).
function TsWorksheet.ChangeBackground(AFormatIndex: Integer; AStyle: TsFillStyle;
ABackgroundColor: TsColor = scTransparent): Integer;
fmt := Workbook.GetCellFormat(AFormatIndex);
if (AStyle = fsNoFill) or
((APatternColor = scTransparent) and (ABackgroundColor = scTransparent))
Exclude(fmt.UsedFormattingFields, uffBackground)
Include(fmt.UsedFormattingFields, uffBackground);
fmt.Background.Style := AStyle;
fmt.Background.FgColor := APatternColor;
if (AStyle = fsSolidFill) and (APatternColor = scTransparent) then
fmt.Background.FgColor := ABackgroundColor
fmt.Background.BgColor := ABackgroundColor;
if (AStyle = fsSolidFill) and (ABackgroundColor = scTransparent) then
fmt.Background.BgColor := APatternColor
Result := Workbook.AddCellFormat(fmt);
Defines a background pattern for a cell
@param AFillStyle Fill style to be used - see TsFillStyle
@NOTE Is replaced by uniform fill if WriteBackgroundColor is called later.
function TsWorksheet.WriteBackground(ARow, ACol: Cardinal; AStyle: TsFillStyle;
APatternColor, ABackgroundColor: TsColor): PCell;
WriteBackground(Result, AStyle, APatternColor, ABackgroundColor);
procedure TsWorksheet.WriteBackground(ACell: PCell; AStyle: TsFillStyle;
APatternColor: TsColor = scTransparent; ABackgroundColor: TsColor = scTransparent);
idx := ACell^.FormatIndex;
ACell^.FormatIndex := ChangeBackground(idx, AStyle, APatternColor, ABackgroundColor);
Sets a uniform background color of a cell.
@param AColor RGB value of the new background color.
Use the value "scTransparent" to clear an existing
background color.
function TsWorksheet.WriteBackgroundColor(ARow, ACol: Cardinal;
AColor: TsColor): PCell;
WriteBackgroundColor(Result, AColor);
@param ACell Pointer to cell
procedure TsWorksheet.WriteBackgroundColor(ACell: PCell; AColor: TsColor);
if AColor = scTransparent then
WriteBackground(ACell, fsNoFill)
WriteBackground(ACell, fsSolidFill, AColor, AColor);
Sets the color of a cell border line.
Note: the border must be included in Borders set in order to be shown!
@param ABorder Indicates to which border (left/top etc) this color is
to be applied
@param AColor RGB value of the new border color
function TsWorksheet.WriteBorderColor(ARow, ACol: Cardinal;
ABorder: TsCellBorder; AColor: TsColor): PCell;
WriteBorderColor(Result, ABorder, AColor);
procedure TsWorksheet.WriteBorderColor(ACell: PCell; ABorder: TsCellBorder;
AColor: TsColor);
fmt.BorderStyles[ABorder].Color := AColor;
Sets the linestyle of a cell border.
Note: the border must be included in the "Borders" set in order to be shown!
@param ALineStyle Identifier of the new line style to be applied.
@see TsLineStyle
function TsWorksheet.WriteBorderLineStyle(ARow, ACol: Cardinal;
ABorder: TsCellBorder; ALineStyle: TsLineStyle): PCell;
WriteBorderLineStyle(Result, ABorder, ALineStyle);
procedure TsWorksheet.WriteBorderLineStyle(ACell: PCell;
ABorder: TsCellBorder; ALineStyle: TsLineStyle);
fmt.BorderStyles[ABorder].LineStyle := ALineStyle;
Shows the cell borders included in the set ABorders. No border lines are drawn
for those not included.
The borders are drawn using the "BorderStyles" assigned to the cell.
@param ABorders Set with elements to identify the border(s) to will be shown
@see TsCellBorder
function TsWorksheet.WriteBorders(ARow, ACol: Cardinal; ABorders: TsCellBorders): PCell;
WriteBorders(Result, ABorders);
procedure TsWorksheet.WriteBorders(ACell: PCell; ABorders: TsCellBorders);
if ABorders = [] then
Exclude(fmt.UsedFormattingFields, uffBorder)
Include(fmt.UsedFormattingFields, uffBorder);
fmt.Border := ABorders;
procedure TsWorksheet.WriteBorders(ALeft, ATop, ARight, ABottom: Integer;
function BorderVisible(const AStyle: TsCellBorderStyle): Boolean;
Result := (AStyle.Color <> scNotDefined) and (AStyle.Color <> scTransparent);
procedure SetNeighborBorder(NewRow, NewCol: Cardinal;
ANewBorder: TsCellBorder; const ANewBorderStyle: TsCellBorderStyle;
AInclude: Boolean);
neighbor: PCell;
border: TsCellBorders;
neighbor := FindCell(NewRow, NewCol);
if neighbor <> nil then
border := ReadCelLBorders(neighbor);
if AInclude then
Include(border, ANewBorder);
WriteBorderStyle(NewRow, NewCol, ANewBorder, ANewBorderStyle);
Exclude(border, ANewBorder);
WriteBorders(NewRow, NewCol, border);
procedure FixNeighborCellBorders(ACell: PCell);
if (ACell = nil) then
fmt := GetPointerToEffectiveCellFormat(ACell);
if Col > 0 then
SetNeighborBorder(Row, Col-1, cbEast, fmt^.BorderStyles[cbWest], cbWest in fmt^.Border);
SetNeighborBorder(Row, Col+1, cbWest, fmt^.BorderStyles[cbEast], cbEast in fmt^.Border);
if Row > 0 then
SetNeighborBorder(Row-1, Col, cbSouth, fmt^.BorderStyles[cbNorth], cbNorth in fmt^.Border);
SetNeighborBorder(Row+1, Col, cbNorth, fmt^.BorderStyles[cbSouth], cbSouth in fmt^.Border);
procedure ProcessBorder(ARow, ACol: Cardinal; ABorder: TsCellBorder;
const AStyle: TsCellBorderStyle);
cb: TsCellBorders = [];
if cell <> nil then
cb := ReadCellBorders(cell);
if BorderVisible(AStyle) then
Include(cb, ABorder);
cell := WriteBorders(ARow, ACol, cb);
WriteBorderStyle(cell, ABorder, AStyle);
if cb <> [] then
Exclude(cb, ABorder);
FixNeighborCellBorders(cell);
procedure ShowCellBorders(ALeft, ATop, ARight, ABottom: Integer;
ALeftOuterStyle, ATopOuterStyle, ARightOuterStyle, ABottomOuterStyle,
AHorInnerStyle, AVertInnerStyle: TsCellBorderStyle);
r, c, r1, c1, r2, c2: Cardinal;
// Preparations
EnsureOrder(ALeft, ARight);
EnsureOrder(ATop, ABottom);
// Top outer border
for c := ALeft to ARight do
ProcessBorder(r1, c, cbNorth, ATopOuterStyle);
// Bottom outer border
ProcessBorder(r2, c, cbSouth, ABottomOuterStyle);
// Left outer border
for r := ATop to ABottom do
ProcessBorder(r, c1, cbWest, ALeftOuterStyle);
// Right outer border
ProcessBorder(r, c2, cbEast, ARightOuterStyle);
// Horizontal inner border
if ATop <> ABottom then
for r := ATop to ABottom-1 do
ProcessBorder(r, c, cbSouth, AHorInnerStyle);
// Vertical inner border
if ALeft <> ARight then
for c := ALeft to ARight-1 do
ProcessBorder(r, c, cbEast, AVertInnerStyle);
procedure SetCellBorders(ACol, ARow: Integer);
r1, c1, r2, c2: Cardinal;
styles, saved_styles: TsCellBorderStyles;
cell := GetCell(ARow, ACol);
if IsMergeBase(cell) then
styles := ReadCellBorderStyles(cell);
saved_styles := styles;
if not (cbEast in ABorders) then styles[cbEast] := NO_CELL_BORDER;
if not (cbWest in ABorders) then styles[cbWest] := NO_CELL_BORDER;
if not (cbNorth in ABorders) then styles[cbNorth] := NO_CELL_BORDER;
if not (cbSouth in ABorders) then styles[cbSouth] := NO_CELL_BORDER;
FindMergedRange(cell, r1, c1, r2, c2);
// Set border flags and styles for all outer cells of the merged block
// Note: This overwrites the styles of the base ...
ShowCellBorders(r1,c1, r2,c2, styles[cbWest], styles[cbNorth],
styles[cbEast], styles[cbSouth], NO_CELL_BORDER, NO_CELL_BORDER);
// ... Restores base border style overwritten in prev instruction
WriteBorderStyles(cell, saved_styles);
WriteBorders(cell, ABorders);
Workbook.DisableNotifications;
SetCellBorders(c, r);
Workbook.EnableNotifications;
ChangedCell(ALeft, ATop);
Sets the style of a cell border, i.e. line style and line color.
@param ABorder Identifies the border to be modified (left/top/right/bottom)
@param AStyle record of parameters controlling how the border line is drawn
(line style, line color)
@result Pointer to cell
function TsWorksheet.WriteBorderStyle(ARow, ACol: Cardinal;
ABorder: TsCellBorder; AStyle: TsCellBorderStyle): PCell;
WriteBorderStyle(Result, ABorder, AStyle);
procedure TsWorksheet.WriteBorderStyle(ACell: PCell; ABorder: TsCellBorder;
AStyle: TsCellBorderStyle);
fmt.BorderStyles[ABorder] := AStyle;
Sets line style and line color of a cell border.
@param ARow Row index of the considered cell
@param ACol Column index of the considered cell
@param ABorder Identifier of the border to be modified
@param ALineStyle Identifier for the new line style of the border
@param AColor RGB value of the border line color
@see WriteBorderStyles
ABorder: TsCellBorder; ALineStyle: TsLineStyle; AColor: TsColor): PCell;
WriteBorderStyle(Result, ABorder, ALineStyle, AColor);
@param AColor RGB value of the color of the border line
ALineStyle: TsLineStyle; AColor: TsColor);
Sets the style of all cell border of a cell, i.e. line style and line color.
Note: Only those borders included in the "Borders" set are shown!
@param AStyles Array of CellBorderStyles for each cell border.
@see WriteBorderStyle
function TsWorksheet.WriteBorderStyles(ARow, ACol: Cardinal;
const AStyles: TsCellBorderStyles): PCell;
WriteBorderStyles(Result, AStyles);
procedure TsWorksheet.WriteBorderStyles(ACell: PCell;
const AStyles: TsCellBorderStyles);
b: TsCellBorder;
if Assigned(ACell) then begin
for b in TsCellBorder do fmt.BorderStyles[b] := AStyles[b];
Assigns a complete cell format record to a cell
@param ACellFormat Cell format record to be used by the cell
@see TsCellFormat
procedure TsWorksheet.WriteCellFormat(ACell: PCell;
const ACellFormat: TsCellFormat);
idx := Workbook.AddCellFormat(ACellFormat);
WriteCellFormatIndex(ACell, idx);
Formats a cell to the cell format stored at the specified index in the
workbook's cell format list.
@param AIndex Index of the cell format record to be used by the cell
procedure TsWorksheet.WriteCellFormatIndex(ACell: PCell; AIndex: Integer);
if AIndex >= Workbook.GetNumCellFormats then
raise EFpSpreadsheet.Create('[Worksheet.WriteCellFormat] Invalid cell format index.');
// The default format index is 0, but it could also be refered to by -1
if AIndex < 0 then AIndex := 0;
ACell^.FormatIndex := AIndex;
Defines the horizontal alignment of text in a cell.
@param AValue Parameter for horizontal text alignment
(haDefault, vaLeft, haCenter, haRight)
By default, texts are left-aligned, numbers and dates are
right-aligned.
function TsWorksheet.WriteHorAlignment(ARow, ACol: Cardinal; AValue: TsHorAlignment): PCell;
WriteHorAlignment(Result, AValue);
procedure TsWorksheet.WriteHorAlignment(ACell: PCell; AValue: TsHorAlignment);
if AValue = haDefault then
Exclude(fmt.UsedFormattingFields, uffHorAlign)
Include(fmt.UsedFormattingFields, uffHorAlign);
fmt.HorAlignment := AValue;
Defines the vertical alignment of text in a cell.
@param AValue Parameter for vertical text alignment
(vaDefault, vaTop, vaCenter, vaBottom)
By default, texts are bottom-aligned.
function TsWorksheet.WriteVertAlignment(ARow, ACol: Cardinal;
AValue: TsVertAlignment): PCell;
WriteVertAlignment(Result, AValue);
@param ACell Poiner to the cell considered
procedure TsWorksheet.WriteVertAlignment(ACell: PCell; AValue: TsVertAlignment);
if AValue = vaDefault then
Exclude(fmt.UsedFormattingFields, uffVertAlign)
Include(fmt.UsedFormattingFields, uffVertAlign);
fmt.VertAlignment := AValue;
Enables or disables the word-wrapping feature for a cell.
@param AValue true = word-wrapping enabled, false = disabled.
function TsWorksheet.WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean): PCell;
WriteWordWrap(Result, AValue);
@param ACel Pointer to the cell considered
procedure TsWorksheet.WriteWordwrap(ACell: PCell; AValue: boolean);
if AValue then
Include(fmt.UsedFormattingFields, uffWordwrap)
Exclude(fmt.UsedFormattingFields, uffWordwrap);
function TsWorksheet.WriteBiDiMode(ARow, ACol: Cardinal; AValue: TsBiDiMode): PCell;
WriteBiDiMode(Result, AValue);
procedure TsWorksheet.WriteBiDiMode(ACell: PCell; AValue: TsBiDiMode);
fmt.BiDiMode := AValue;
if AValue <> bdDefault then
Include(fmt.UsedFormattingFields, uffBiDi)
Exclude(fmt.UsedFormattingFields, uffBiDi);
Defines how the cell at the specified row and column is protected: lock
cell modification and/or hide formulas. Note that this is activated only after
enabling worksheet protection (worksheet.Protect(true)).
NOTE:
FPSpreadsheet does not enforce these actions. They are only written
to the file for the Office application.
function TsWorksheet.WriteCellProtection(ARow, ACol: Cardinal;
AValue: TsCellProtections): PCell;
WriteCellProtection(Result, AValue);
procedure TsWorksheet.WriteCellProtection(ACell: PCell;
AValue: TsCellProtections);
fmt.Protection := AValue;
if AValue <> DEFAULT_CELL_PROTECTION then
Include(fmt.UsedFormattingFields, uffProtection)
Exclude(fmt.UsedFormattingFields, uffProtection);
function TsWorksheet.GetDefaultColWidth: Single;
Result := ReadDefaultColWidth(suChars);
procedure TsWorksheet.SetDefaultColWidth(AValue: Single);
WriteDefaultColWidth(AValue, suChars);
function TsWorksheet.GetDefaultRowHeight: Single;
Result := ReadDefaultRowHeight(suLines);
procedure TsWorksheet.SetDefaultRowHeight(AValue: Single);
WriteDefaultRowHeight(AValue, suLines);
function TsWorksheet.GetFormatSettings: TFormatSettings;
Result := FWorkbook.FormatSettings;
function TsWorksheet.GetIndex: Integer;
Result := TsWorkbook(FWorkbook).GetWorksheetIndex(self);
Moves the worksheet to the specified index in the workbook.
@param AValue New index of the sheet in the workbook. If less than 0 the
worksheet will become the first, if greater than the
worksheet count it will become the last worksheet of the
workbook.
procedure TsWorksheet.SetIndex(AValue: Integer);
oldIndex: Integer;
if AValue < 0 then
AValue := 0
else if AValue >= TsWorkbook(FWorkbook).GetWorksheetCount then
AValue := TsWorkbook(FWorkbook).GetWorksheetCount - 1;
oldIndex := GetIndex;
if oldIndex <> AValue then
TsWorkbook(FWorkbook).MoveSheet(oldIndex, Avalue);
procedure TsWorksheet.SetTabColor(AValue: TsColor);
if AValue = FTabColor then exit;
FTabColor := AValue;
TsWorkbook(FWorkbook).ChangedWorksheet(self);
Calculates the optimum height of a given row. Depends on the font size
of the individual cells in the row. Is converted to workbook units.
@param ARow Index of the row to be considered
@return Row height in workbook units
function TsWorksheet.CalcAutoRowHeight(ARow: Cardinal): Single;
for cell in Cells.GetRowEnumerator(ARow) do
Result := Max(Result, ReadCellFont(cell).Size);
// FixMe: This is not correct if text is rotated or wrapped
Result := FWorkbook.ConvertUnits(Result, suPoints, FWorkbook.Units);
function TsWorksheet.CalcRowHeight(ARow: Cardinal): Single;
// In workbook units
r: PRow;
r := FindRow(ARow);
if (r <> nil) and (r^.RowHeightType = rhtCustom) then
Result := GetRowHeight(ARow, FWorkbook.Units)
Result := CalcAutoRowHeight(ARow);
Result := GetRowHeight(ARow, FWorkbook.Units);
Returns the first column record, i.e. that of the left-most column
function TsWorksheet.FindFirstCol: PCol;
AVLNode: TAVGLVLTreeNode;
AVLNode := FCols.FindLowest;
if AVLNode <> nil then Result := PCol(AVLNode.Data);
Returns the first row record, i.e. that of the top-most row
function TsWorksheet.FindFirstRow: PRow;
AVLNode := FRows.FindLowest;
if AVLNode <> nil then Result := PRow(AVLNode.Data);
Checks if a row record exists for the given row index and returns a pointer
to the row record, or nil if not found
@param ARow Index of the row looked for
@return Pointer to the row record with this row index, or nil if not found
function TsWorksheet.FindRow(ARow: Cardinal): PRow;
LElement: TRow;
LElement.Row := ARow;
AVLNode := FRows.Find(@LElement);
if Assigned(AVLNode) then
result := PRow(AVLNode.Data);
Checks if a column record exists for the given column index and returns a
pointer to the TCol record, or nil if not found
@param ACol Index of the column looked for
@return Pointer to the column record with this column index, or nil
if not found
function TsWorksheet.FindCol(ACol: Cardinal): PCol;
LElement: TCol;
LElement.Col := ACol;
AVLNode := FCols.Find(@LElement);
result := PCol(AVLNode.Data);
Checks if a row record exists for the given row index and creates it if not
found.
@return Pointer to the row record with this row index. It can safely be
assumed that this row record exists.
function TsWorksheet.GetRow(ARow: Cardinal): PRow;
Result := FindRow(ARow);
if (Result = nil) then
Result := AddRow(ARow);
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 to be added
@return Pointer to the row record with this row index.
function TsWorksheet.AddRow(ARow: Cardinal): PRow;
Result := GetMem(SizeOf(TRow));
FillChar(Result^, SizeOf(TRow), #0);
Result^.Row := ARow;
FRows.Add(Result);
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);
Checks if a column record exists for the given column index and creates it
if not found.
@return Pointer to the TCol record with this column index. It can
safely be assumed that this column record exists.
function TsWorksheet.GetCol(ACol: Cardinal): PCol;
Result := FindCol(ACol);
Result := AddCol(ACol);
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;
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);
Counts how many cells exist in the given column. Blank cells do contribute
to the sum, as well as formatted cells.
@param ACol Index of the column considered
@return Count of cells with value or format in this column
function TsWorksheet.GetCellCountInCol(ACol: Cardinal): Cardinal;
r: Cardinal;
row: PRow;
for r := GetFirstRowIndex to GetLastRowIndex do begin
cell := FindCell(r, ACol);
inc(Result)
row := FindRow(r);
if row <> nil then inc(Result);
Counts how many cells exist in the given row. Blank cells do contribute
to the sum, as well as formatted cell.s
@param ARow Index of the row considered
@return Count of cells with value or format in this row
function TsWorksheet.GetCellCountInRow(ARow: Cardinal): Cardinal;
c: Cardinal;
col: PCol;
for c := 0 to GetLastColIndex do begin
cell := FindCell(ARow, c);
col := FindCol(c);
if col <> nil then inc(Result);
Returns the index to the cell format to be used for a given column.
If there is no column record then the default format (index 0) is used.
@return Index of the format into the workbook's FCellFormatList. This format
will be used for formatting a cell if itself does not have a
non-zero format index, and if there is no row format either.
function TsWorksheet.GetColFormatIndex(ACol: Cardinal): Integer;
lCol: PCol;
Result := 0; // Default format has index 0
if ACol <> UNASSIGNED_ROW_COL_INDEX then
lCol := FindCol(ACol);
if lCol <> nil then
Result := lCol^.FormatIndex
Returns the width of the given column. If there is no column record then
the default column width is returned.
@param AUnits Units for the column width.
@return Width of the column
function TsWorksheet.GetColWidth(ACol: Cardinal; AUnits: TsSizeUnits): Single;
Result := FDefaultColWidth;
if ACol <> UNASSIGNED_ROW_COL_INDEX then begin
col := FindCol(ACol);
if (col <> nil) and (col^.ColWidthType <> cwtDefault) then
Result := col^.Width;
Result := FWorkbook.ConvertUnits(Result, FWorkbook.Units, AUnits);
if ACol = UNASSIGNED_ROW_COL_INDEX then
Result := 0
if (col = nil) or (col^.ColWidthType = cwtDefault) then
Result := FDefaultColWidth
function TsWorksheet.GetColWidth(ACol: Cardinal): Single;
Result := GetColWidth(ACol, suChars);
Returns the type of column width of a specific column.
If there is no column record then cwtDefault is returned.
@return Width of the column. This is the "raw" value, without application of
the zoom factor.
function TsWorksheet.GetColWidthType(ACol: Cardinal): TsColWidthType;
if lCol = nil then
Result := cwtDefault
Result := lCol^.ColWidthType;
Returns the index to the cell format to be used for a given row.
If there is no row record then the default format (index 0) is returned.
non-zero format index.
function TsWorksheet.GetRowFormatIndex(ARow: Cardinal): Integer;
if ARow <> UNASSIGNED_ROW_COL_INDEX then
row := FindRow(ARow);
if row <> nil then
Result := row^.FormatIndex
Returns the height of the given row. If there is no row record then the
default row height is returned
@param AUnits Units for the row height.
@return Height of the row. This is the "raw" value, without application of
function TsWorksheet.GetRowHeight(ARow: Cardinal; AUnits: TsSizeUnits): Single;
lRow: PRow;
Result := FDefaultRowHeight;
lRow := FindRow(ARow);
if (lRow <> nil) and (lRow^.RowHeightType <> rhtDefault) then
Result := lRow^.Height;
if ARow = UNASSIGNED_ROW_COL_INDEX then
if lRow <> nil then begin
if lRow.RowHeightType = rhtDefault then
function TsWorksheet.GetRowHeight(ARow: Cardinal): Single;
Result := GetRowHeight(ARow, suLines);
Returns the type of rowheight of a specific row.
If there is no row record then rhtDefault is returned.
function TsWorksheet.GetRowHeightType(ARow: Cardinal): TsRowHeightType;
if lRow = nil then
Result := rhtDefault
Result := lRow^.RowHeightType;
function TsWorksheet.HasColFormats: Boolean;
c: Integer;
for c := 0 to FCols.Count-1 do
if PCol(FCols[c]).FormatIndex > 0 then
function TsWorksheet.HasRowFormats: Boolean;
r: Integer;
for r := 0 to FRows.Count-1 do
if PRow(FRows[r]).FormatIndex > 0 then
Determines whether the properties stored in a TCol record are default values
only. Such a record usually can be removed.
function TsWorksheet.IsDefaultCol(ACol: PCol): Boolean;
Result :=
(ACol = nil) or (
(ACol^.ColWidthType = cwtDefault) and (ACol^.FormatIndex = 0) and (ACol^.Options = [])
Determines whether the properties stored in a TRow record are default values
only. Such a record normally can be removed.
function TsWorksheet.IsDefaultRow(ARow: PRow): Boolean;
(ARow = nil) or (
(ARow^.RowHeightType = rhtDefault) and (ARow^.FormatIndex = 0) and (ARow^.Options = [])
Returns whether the specified column is hidden
function TsWorksheet.ColHidden(ACol: Cardinal): Boolean;
c: PCol;
c := FindCol(ACol);
Result := Assigned(c) and (croHidden in c^.Options);
Returns whether the specified row is hidden
function TsWorksheet.RowHidden(ARow: Cardinal): Boolean;
Result := Assigned(r) and (croHidden in r^.Options);
Hides the specified column
procedure TsWorksheet.HideCol(ACol: Cardinal);
c := GetCol(ACol);
if not (croHidden in c^.Options) then begin
Include(c^.Options, croHidden);
ChangedCell(0, ACol);
Hides the specified row
procedure TsWorksheet.HideRow(ARow: Cardinal);
r := GetRow(ARow);
if not (croHidden in r^.Options) then begin
Include(r^.Options, croHidden);
ChangedCell(ARow, 0);
Shows the specified column which was hidden previously
procedure TsWorksheet.ShowCol(ACol: Cardinal);
if Assigned(c) and (croHidden in c^.Options) then begin
Exclude(c^.Options, croHidden);
Shows the specified row which was hidden previously
procedure TsWorksheet.ShowRow(ARow: Cardinal);
if Assigned(r) and (croHidden in r^.Options) then begin
Exclude(r^.Options, croHidden);
Determines whether the specified row contains any occupied cell.
function TsWorksheet.IsEmptyRow(ARow: Cardinal): Boolean;
Result := Cells.GetFirstCellOfRow(ARow) = nil;
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
formulas are considered as well.
@param ACol Index of the column to be deleted
procedure TsWorksheet.DeleteCol(ACol: Cardinal);
DeleteRowOrCol(ACol, false);
Deletes the row at the index specified. Cells with greater row indexes are
moved one row up. Merged cell blocks and cell references in formulas
are considered as well.
@param ARow Index of the row to be deleted
procedure TsWorksheet.DeleteRow(ARow: Cardinal);
DeleteRowOrCol(ARow, true);
Deletes the row or column at the index specified. AIsRow determines whether
the index is a row or column index.
Cells with greader row/column indexes are moved one row up/left.
Merged cell blocks and cell references in formulas are considered as well.
@param AIndex Index of the row to be deleted
@param IsRow If TRUE then AIndex is a row index, otherwise a column index
procedure TsWorksheet.DeleteRowOrCol(AIndex: Integer; IsRow: Boolean);
// Fix merged cells
FMergedCells.DeleteRowOrCol(AIndex, IsRow);
// Fix comments
FComments.DeleteRowOrCol(AIndex, IsRow);
// Fix hyperlinks
FHyperlinks.DeleteRowOrCol(AIndex, IsRow);
// Fix formulas:
// 1) Fix Row/Col index of in-sheet formulas
FFormulas.DeleteRowOrCol(AIndex, IsRow);
// 2) Fix formula references to this sheet
for i := 0 to FWorkbook.GetWorksheetcount-1 do begin
sheet := FWorkbook.GetWorksheetByIndex(i);
sheet.Formulas.FixReferences(AIndex, IsRow, true, self);
// Delete cells
FCells.DeleteRowOrCol(AIndex, IsRow);
// Fix formula flags
if HasFormula(cell) and (FFormulas.FindFormula(cell) = nil) then
cell^.Flags := cell^.flags - [cfHasFormula, cf3dFormula];
// Fix formula left-overs (formulas having no cell)
if FindCell(formula^.Row, formula^.Col) = nil then
FFormulas.DeleteFormula(formula^.Row, formula^.Col);
if IsRow then
for i:= FRows.Count-1 downto 0 do begin
row := PRow(FRows.Items[i]);
if Integer(row^.Row) > AIndex then
dec(row^.Row)
// Update first and last row index
UpdateCaches;
ChangedCell(AIndex, 0);
// Update column index of col records
for i:=FCols.Count-1 downto 0 do begin
col := PCol(FCols.Items[i]);
if Integer(col^.Col) > AIndex then
dec(col^.Col)
// Update first and last column index
UpDateCaches;
ChangedCell(0, AIndex);
Inserts a column BEFORE the column index specified.
Cells with greater column indexes are moved one row to the right.
@param ACol Index of the column before which a new column is inserted.
procedure TsWorksheet.InsertCol(ACol: Cardinal);
InsertRowOrCol(ACol, false);
Inserts a row BEFORE the row specified. Cells with greater row indexes are
moved one row down. Merged cell blocks and cell references in formulas are
considered as well.
@param ARow Index of the row before which a new row is inserted.
procedure TsWorksheet.InsertRow(ARow: Cardinal);
InsertRowOrCol(ARow, true);
Inserts a row or column BEFORE the row/column specified by AIndex. Depending
on IsRow this is either the row or column index.
Cells with greater row/column indexes are moved one row down/right.
@param AIndex Index of the row or column before which a new row or
column is inserted.
@param IsRow Determines whether AIndex refers to a row index (TRUE) or
column index (FALSE).
procedure TsWorksheet.InsertRowOrCol(AIndex: Integer; IsRow: Boolean);
// Update row indexes of cell comments
FComments.InsertRowOrCol(AIndex, IsRow);
// Update row indexes of cell hyperlinks
FHyperlinks.InsertRowOrCol(AIndex, IsRow);
// 1) Update Row/Col index of in-sheet formulas
FFormulas.InsertRowOrCol(AIndex, IsRow);
sheet.Formulas.FixReferences(AIndex, IsRow, false, self);
// Update cell indexes of cell records
FCells.InsertRowOrCol(AIndex, IsRow);
if IsRow then begin
// Update row index of row records
for i:=0 to FRows.Count-1 do begin
if Integer(row^.Row) >= AIndex then inc(row^.Row);
// Update column index of column records
for i:=0 to FCols.Count-1 do begin
if Integer(col^.Col) >= AIndex then inc(col^.Col);
// Update first and last row/column index
for rng in FMergedCells do
// The new row is ABOVE the merged block --> Shift entire range down by 1 row
if (AIndex < Integer(rng^.Row1)) then
// The formerly first row is no longer merged --> un-tag its cells
for cell in Cells.GetRowEnumerator(rng^.Row1, rng^.Col1, rng^.Col2) do
// Shift merged block down
// (Don't call "MergeCells" here - this would add a new merged block
// because of the new merge base! --> infinite loop!)
inc(rng^.Row1);
inc(rng^.Row2);
// The last row needs to be tagged
for cell in Cells.GetRowEnumerator(rng^.Row2, rng^.Col1, rng^.Col2) do
// The new row goes through this cell block --> Shift only the bottom row
// of the range down by 1
if (AIndex >= Integer(rng^.Row1)) and (AIndex <= Integer(rng^.Row2)) then
MergeCells(rng^.Row1, rng^.Col1, rng^.Row2+1, rng^.Col2);
// The new column is at the LEFT of the merged block
// --> Shift entire range to the right by 1 column
if (AIndex < Integer(rng^.Col1)) then
// The former first column is no longer merged --> un-tag its cells
for cell in Cells.GetColEnumerator(rng^.Col1, rng^.Row1, rng^.Row2) do
// Shift merged block to the right
// Don't call "MergeCells" here - this would add a new merged block
// because of the new merge base! --> infinite loop!
inc(rng^.Col1);
inc(rng^.Col2);
// The right column needs to be tagged
for cell in Cells.GetColEnumerator(rng^.Col2, rng^.Row1, rng^.Row2) do
// The new column goes through this cell block --> Shift only the right
// column of the range to the right by 1
if (AIndex >= Integer(rng^.Col1)) and (AIndex <= Integer(rng^.Col2)) then
MergeCells(rng^.Row1, rng^.Col1, rng^.Row2, rng^.Col2+1);
Moves a column from a specified column index to another column index.
The operation includes everything associated with the column (cell values,
cell properties, formats, formulas, column formats, column widths).
Formulas are automatically adjusted for the new position.
procedure TsWorksheet.MoveCol(AFromCol, AToCol: Cardinal);
if AFromCol = AToCol then
// Nothing to do
for r := 0 to GetLastRowIndex do begin
FCells.MoveAlongRow(r, AFromCol, AToCol);
FComments.MoveAlongRow(r, AFromCol, AToCol);
FHyperlinks.MoveAlongRow(r, AFromCol, AToCol);
FFormulas.MoveAlongRow(r, AFromCol, AToCol);
Moves a row from a specified row index to another row index.
The operation includes everything associated with the row (cell values,
procedure TsWorksheet.MoveRow(AFromRow, AToRow: Cardinal);
if AFromRow = AToRow then
FCells.MoveAlongCol(AFromRow, c, AToRow);
FComments.MoveAlongCol(AFromRow, c, AToRow);
FHyperlinks.MoveAlongCol(AFromRow, c, AToRow);
FFormulas.MoveAlongCol(AFromRow, c, AToRow);
Reads the value of the default column width and converts it to the specified
units
function TsWorksheet.ReadDefaultColWidth(AUnits: TsSizeUnits): Single;
Result := FWorkbook.ConvertUnits(FDefaultColWidth, FWorkbook.Units, AUnits);
Reads the value of the default row height and converts it to the specified
function TsWorksheet.ReadDefaultRowHeight(AUnits: TsSizeUnits): Single;
Result := FWorkbook.ConvertUnits(FDefaultRowHeight, FWorkbook.Units, AUnits);
Removes all row records from the worksheet and frees the occupied memory.
Note: Cells are retained.
procedure TsWorksheet.RemoveAllRows;
Node: Pointer;
for i := FRows.Count-1 downto 0 do begin
Node := FRows.Items[i];
FreeMem(Node, SizeOf(TRow));
FRows.Clear;
Removes all column records from the worksheet and frees the occupied memory.
procedure TsWorksheet.RemoveAllCols;
for i := FCols.Count-1 downto 0 do begin
Node := FCols.Items[i];
FreeMem(Node, SizeOf(TCol));
FCols.Clear;
Removes a specified column record from the worksheet and frees the occupied
memory. This resets its column width and format to default.
Note: Cells in that column are retained.
procedure TsWorksheet.RemoveCol(ACol: Cardinal);
lCol: TCol;
lCol.Col := ACol;
AVLNode := FCols.Find(@lCol);
FreeMem(PCol(AVLNode.Data), SizeOf(TCol));
FCols.Delete(AVLNode);
Removes a specified row record from the worksheet and frees the occupied memory.
This resets the its row height to default.
Note: Cells in that row are retained.
procedure TsWorksheet.RemoveRow(ARow: Cardinal);
lRow: TRow;
lRow.Row := ARow;
AVLNode := FRows.Find(@lRow);
FreeMem(PRow(AVLNode.Data), SizeOf(TRow));
FRows.Delete(AVLNode);
Writes a row record for the row at a given index to the spreadsheet.
The row record contains info on the row height and the row format index.
Creates a new row record if it does not yet exist.
@param ARow Index of the row record which will be created or modified
@param AData Data to be written. Row height expected to be already in the
units defined for the workbook.
procedure TsWorksheet.WriteRowInfo(ARow: Cardinal; AData: TRow);
lRow := GetRow(ARow);
lRow^.Height := AData.Height;
lRow^.RowHeightType := AData.RowHeightType;
lRow^.FormatIndex := AData.FormatIndex;
lRow^.Options := AData.Options;
ChangedRow(ARow);
Sets the cell format index for a specific row.
@param AFormatIndex Index into the workbook's FCellFormatList. This format
will be used if a cell has default format index (0).
procedure TsWorksheet.WriteRowFormatIndex(ARow: Cardinal; AFormatIndex:Integer);
lRow^.FormatIndex := AFormatIndex;
Sets the row height for a given row. Creates a new row record if it
does not yet exist.
@param AHeight Row height to be assigned to the row.
@param AUnits Units measuring the row height.
@param ARowHeightType Specifies whether the row height is a default,
automatic or custom row height.
procedure TsWorksheet.WriteRowHeight(ARow: Cardinal; AHeight: Single;
AUnits: TsSizeUnits; ARowHeightType: TsRowHeightType = rhtCustom);
if not (croHidden in lRow^.Options) then
lRow^.Height := FWorkbook.ConvertUnits(AHeight, AUnits, FWorkbook.FUnits);
lRow^.RowHeightType := ARowHeightType;
Sets the row height for a given row. The height is given in terms of
line count of the worksheet's default font.
Note that this method is deprecated and will be removed.
Use the variant in which the units of the new height can be specified.
ARowHeightType: TsRowHeightType = rhtCustom);
WriteRowHeight(ARow, AHeight, suLines, ARowHeightType);
Writes a column record for the column at a specific index to the spreadsheet.
The column record contains info on the column width and the format index.
Creates a new column record if it does not yet exist.
@param ACol Index of the column record which will be created or modified
@param AData Data to be written. The column width must already be in
the units defined for the workbook.
procedure TsWorksheet.WriteColInfo(ACol: Cardinal; AData: TCol);
lCol := GetCol(ACol);
lCol^.Width := AData.Width;
lCol^.ColWidthType := AData.ColWidthType;
lCol^.FormatIndex := AData.FormatIndex;
lCol^.Options := AData.Options;
ChangedCol(ACol);
Sets the cell format index for a specific column.
@param ACol Index of the column to be considered
will be used if a cell has default format index (0) and
if there is no specific default row format.
procedure TsWorksheet.WriteColFormatIndex(ACol: Cardinal; AFormatIndex:Integer);
lCol^.FormatIndex := AFormatIndex;
Sets the column width for a given column. Creates a new column record if it
@param AWidth Width to be assigned to the column.
@param AColWidthType Type of the column width (default -> AWidth is ignored)
or custom)
@param AUnits Units used for parameter AWidth.
procedure TsWorksheet.WriteColWidth(ACol: Cardinal; AWidth: Single;
AUnits: TsSizeUnits; AColWidthType: TsColWidthType = cwtCustom);
if not (croHidden in lCol^.Options) then
lCol^.Width := FWorkbook.ConvertUnits(AWidth, AUnits, FWorkbook.FUnits);
lCol^.ColWidthType := AColWidthType;
Sets the column width for a given column. The width is given in terms of
count of the "0" character using the worksheet's default font.
Use the variant in which the units of the new width can be specified.
AColWidthType: TsColWidthType = cwtCustom);
WriteColWidth(ACol, AWidth, suChars, AColWidthType);
Sets the default column widtht of the worksheet. The value will be stored
in workbook units.
@param AValue New value of the default column width
@param AUnits Units used by AValue
procedure TsWorksheet.WriteDefaultColWidth(AValue: Single; AUnits: TsSizeUnits);
FDefaultColWidth := FWorkbook.ConvertUnits(AValue, AUnits, FWorkbook.Units);
Sets the default row height of the worksheet. The value will be stored
@param AValue New value of the default row height
procedure TsWorksheet.WriteDefaultRowHeight(AValue: Single; AUnits: TsSizeUnits);
FDefaultRowHeight := FWorkbook.ConvertUnits(AValue, AUnits, FWorkbook.Units);
Sets the PageBreak flag for the column record with the specified column index.
This means that, when printed, a page break will occur before this column.
Note that FPS currently does not support printing by itself.
procedure TsWorksheet.AddPageBreakToCol(ACol: Cardinal);
Include(lCol^.Options, croPageBreak);
Sets the PageBreak flag for the row record with the specified row index.
This means that, when printed, a page break will occur before this row.
procedure TsWorksheet.AddPageBreakToRow(ARow: Cardinal);
Include(lRow^.Options, croPageBreak);
Returns true if the column with the specified index is the first one after a
manual page break.
function TsWorksheet.IsPageBreakCol(ACol: Cardinal): Boolean;
Result := Assigned(lCol) and (croPageBreak in lCol^.Options);
Returns true if the row with the specified index is the first one after a
function TsWorksheet.IsPageBreakRow(ARow: Cardinal): Boolean;
Result := Assigned(lRow) and (croPageBreak in lRow^.Options);
Removes the PageBreak flag for the column record with the specified column
index.
This means that, during printing, page break handling of this column will be
automatic.
procedure TsWorksheet.RemovePageBreakFromCol(ACol: Cardinal);
if Assigned(lCol) then begin
Exclude(lCol^.Options, croPageBreak);
// Free and delete node when the col record only has default values now.
if (lCol^.Options = []) and (lCol^.FormatIndex = 0) and (lCol^.ColWidthType = cwtDefault) then
RemoveCol(ACol);
Removes the PageBreak flag for the row record with the specified row index.
This means that, during printing, page break handling of this row will be
procedure TsWorksheet.RemovePageBreakFromRow(ARow: Cardinal);
if Assigned(lRow) then begin
Exclude(lRow^.Options, croPageBreak);
// Free and delete node if the row record only has default values now.
if (lRow^.Options = []) and (lRow^.FormatIndex = 0) and (lRow^.RowHeightType = rhtDefault) then
RemoveRow(ARow);
{$include fpspreadsheet_CF.inc} // conditional formatting
Helper method called before reading the workbook. Clears the error log.
procedure TsWorkbook.PrepareBeforeReading;
Clear;
// Abort if virtual mode is active without an event handler
if (boVirtualMode in FOptions) and not Assigned(OnReadCellData) then
raise EFPSpreadsheet.Create('[TsWorkbook.PrepareBeforeReading] Event handler "OnReadCellData" required for virtual mode.');
Helper method called before saving the workbook. Clears the error log, and
calculates the formulas in all worksheets if workbook option soCalcBeforeSaving
is set.
procedure TsWorkbook.PrepareBeforeSaving;
virtModeOK: Boolean;
// Clear error log
ClearErrorList;
// Updates fist/last column/row index
// Calculated formulas (if requested)
if (boCalcBeforeSaving in FOptions) then
for sheet in FWorksheets do
sheet.CalcFormulas;
if (boVirtualMode in FOptions) then
virtModeOK := false;
if Assigned(sheet.OnWriteCellData) then
virtModeOK := true;
if not virtModeOK then
raise EFPSpreadsheet.Create('[TsWorkbook.PrepareBeforeWriting] At least one '+
'sheet must have an event handler "OnWriteCellData" for virtual mode.');
Conversion of length values between units
function TsWorkbook.ConvertUnits(AValue: Double;
AFromUnits, AToUnits: TsSizeUnits): Double;
if AFromUnits = AToUnits then
Result := AValue;
// Convert to mm
case AFromUnits of
suMillimeters:
suCentimeters:
Result := AValue * 10.0;
suInches:
Result := inToMM(AValue);
suPoints:
Result := ptsToMM(AValue);
suChars:
Result := ptsToMM(GetDefaultFont.Size * ZERO_WIDTH_FACTOR * AValue);
suLines:
Result := ptsToMM(GetDefaultFont.Size * (AValue + ROW_HEIGHT_CORRECTION));
raise EFPSpreadsheet.Create('Unit not supported.');
// Convert from mm
case AToUnits of
suMillimeters: ; // nothing to do
Result := Result * 0.1;
Result := mmToIn(Result);
Result := mmToPts(Result);
Result := mmToPts(Result) / (GetDefaultFont.Size * ZERO_WIDTH_FACTOR);
Result := mmToPts(Result) / GetDefaultFont.Size - ROW_HEIGHT_CORRECTION;
Helper method for rebuilding all string formulas of the workbook from the
pared formulas.
procedure TsWorkbook.RebuildFormulasCallback(Data, Arg: Pointer);
Unused(Arg);
for formula in TsWorksheet(Data).Formulas do
Helper method for clearing the spreadsheet list.
procedure TsWorkbook.RemoveWorksheetsCallback(Data, Arg: pointer);
TsWorksheet(Data).Free;
Notification of visual controls that some global data of a worksheet
have changed.
procedure TsWorkbook.ChangedWorksheet(AWorksheet: TsWorksheet);
if FReadWriteFlag = rwfRead then
if NotificationsEnabled and Assigned(FOnChangeWorksheet)
then OnChangeWorksheet(self, AWorksheet);
Helper method to disable notification of visual controls
procedure TsWorkbook.DisableNotifications;
inc(FNotificationLock);
Helper method to enable notification of visual controls
procedure TsWorkbook.EnableNotifications;
dec(FNotificationLock);
Helper method to determine whether visual controls are notified of changes
function TsWorkbook.NotificationsEnabled: Boolean;
Result := (FNotificationLock = 0);
procedure TsWorkbook.UpdateCaches;
sheet.UpdateCaches;
Constructor of the workbook class. Among others, it initializes the built-in
fonts, defines the default font, and sets up the FormatSettings for
localization of some number formats.
constructor TsWorkbook.Create;
FWorksheets := TFPList.Create;
FFontList := TFPList.Create;
SetDefaultFont(DEFAULT_FONTNAME, DEFAULT_FONTSIZE);
InitFonts;
FNumFormatList := TsNumFormatList.Create(FormatSettings, true);
FCellFormatList := TsCellFormatList.Create(false);
FConditionalFormatList := TsConditionalFormatList.Create;
FEmbeddedObjList := TFPList.Create;
// Add default cell format
InitFormatRecord(fmt);
AddCellFormat(fmt);
// Protection
Destructor of the workbook class
destructor TsWorkbook.Destroy;
DisableNotifications;
RemoveAllWorksheets;
EnableNotifications;
FWorksheets.Free;
FConditionalFormatList.Free;
FCellFormatList.Free;
FNumFormatList.Free;
RemoveAllFonts;
FFontList.Free;
RemoveAllEmbeddedObj;
FEmbeddedObjList.Free;
FreeAndNil(FSearchEngine);
Clears content and formats from the workbook
procedure TsWorkbook.Clear;
// Initialize fonts
// Remove already existing worksheets.
// Remove all cell formats, but keep the default format
RemoveAllCellFormats(true);
// Remove all number formats
RemoveAllNumberFormats;
// Remove embedded images
// Reset cryptoinfo
Helper method for determining the spreadsheet type. Read the first few bytes
of a file and determines the spreadsheet type from the characteristic
signature.
class procedure TsWorkbook.GetFormatFromFileHeader(const AFileName: TFileName;
out AFormatIDs: TsSpreadFormatIDArray);
stream: TStream;
stream := TFileStream.Create(AFileName, fmOpenRead + fmShareDenyNone);
GetFormatFromFileHeader(stream, AFormatIDs)
stream.Free;
Helper method for determining the spreadsheet format. Reads the first
few bytes of a stream and determines the spreadsheet type from the
characteristic signature.
class procedure TsWorkbook.GetFormatFromFileHeader(AStream: TStream;
reader: TsSpreadReaderClass;
fmtIDs: TsSpreadformatIDArray;
i, j: Integer;
SetLength(AFormatIDs, 0);
if AStream = nil then
fmtIDs := GetSpreadFormats(faRead, [ord(sfExcel8)]);
SetLength(AFormatIDs, Length(fmtIDs));
j := 0;
for i:=0 to High(fmtIDs) do begin
reader := GetSpreadReaderClass(fmtIDs[i]);
if Assigned(reader) and reader.CheckFileFormat(AStream) then begin
AFormatIDs[j] := fmtIDs[i];
inc(j);
SetLength(AFormatIDs, j);
Determines the maximum index of used columns and rows in all sheets of this
workbook. Respects VirtualMode.
Is needed to disable saving when limitations of the format is exceeded.
procedure TsWorkbook.GetLastRowColIndex(out ALastRow, ALastCol: Cardinal);
ALastRow := 0;
ALastCol := 0;
if (boVirtualMode in Options) then
if sheet.VirtualRowCount > 0 then
ALastRow := Max(ALastRow, sheet.VirtualRowCount - 1);
if sheet.VirtualColCount > 0 then
ALastCol := Max(ALastCol, sheet.VirtualColCount - 1);
ALastRow := Max(ALastRow, sheet.GetLastRowIndex);
ALastCol := Max(ALastCol, sheet.GetLastColIndex);
Reads the document from a file. It is assumed to have the given file format.
This method is intended for built-in file formats only. For user-provided
formats, call the overloaded method with the FormadID parameter.
@param AFileName Name of the file to be read
@param AFormat File format assumed
procedure TsWorkbook.ReadFromFile(AFileName: string;
AFormat: TsSpreadsheetFormat; AParams: TsStreamParams = []);
if AFormat = sfUser then
raise EFPSpreadsheetReader.Create('[TsWorkbook.ReadFromFile] Don''t call this method for user-provided file formats.');
ReadFromFile(AFilename, ord(AFormat), '', AParams);
Works also for user-provided file formats.
@param AFormatID Identifier of the file format assumed
procedure TsWorkbook.ReadFromFile(AFileName: string; AFormatID: TsSpreadFormatID;
AReader: TsBasicSpreadReader;
ok: Boolean;
if not FileExists(AFileName) then
raise EFPSpreadsheetReader.CreateFmt(rsFileNotFound, [AFileName]);
if AFormatID = sfIDUnknown then begin
ReadFromFile(AFileName, APassword, AParams);
AReader := CreateSpreadReader(self, AFormatID);
FFileName := AFileName;
PrepareBeforeReading;
ok := false;
FReadWriteFlag := rwfRead;
inc(FNotificationLock); // This locks various notifications from being sent
AReader.ReadFromFile(AFileName, APassword, AParams);
ok := true;
if (boAutoCalc in Options) then
// Recalc;
FFormatID := AFormatID;
FReadWriteFlag := rwfNormal;
if ok and Assigned(FOnOpenWorkbook) then // ok is true if file has been read successfully
FOnOpenWorkbook(self); // send common notification
AReader.Free;
Reads the document from a file. This method will try to guess the format from
the extension. In the case of the ambiguous xls extension, it will simply
assume that it is BIFF8. Note that it could be BIFF2 or 5 as well.
procedure TsWorkbook.ReadFromFile(AFileName: string; APassword: String = '';
success: Boolean;
fmtID: TsSpreadFormatID;
fileFormats: TsSpreadFormatIDArray;
ext: String;
// Try to get file format from file header
GetFormatFromFileHeader(AFileName, fileformats);
if Length(fileformats) = 0 then
// If not successful use formats defined by extension
fileFormats := GetSpreadFormatsFromFileName(faRead, AFileName);
fileformats := GetSpreadFormats(faRead, [ord(sfExcel8)]);
// Move file format corresponding to file extension to the top to load it first.
ext := Lowercase(ExtractFileExt(AFileName));
for i := 0 to High(fileformats) do
if ext = GetSpreadFormatExt(fileformats[i]) then begin
fmtID := fileformats[0];
fileFormats[0] := fileformats[i];
fileFormats[i] := fmtID;
// No file format found for this file --> error
raise EFPSpreadsheetReader.CreateFmt(rsReaderNotFound, [AFileName]);
// Here is the trial-and-error loop checking for the various formats.
success := false;
for i:=0 to High(fileformats) do begin
ReadFromFile(AFileName, fileformats[i], APassword, AParams);
success := true;
break; // Exit the loop if we reach this point successfully.
// The file could not be opened successfully --> Error.
if not success then
raise EFPSpreadsheetReader.CreateFmt(rsInvalidSpreadsheetFile, [AFileName]);
Reads the document from a file, but ignores the extension.
procedure TsWorkbook.ReadFromFileIgnoringExtension(AFileName: string;
formatID: TsSpreadFormatID;
fileformats: TsSpreadFormatIDArray;
fileformats := GetSpreadFormats(faRead, [ord(sfOOXML), ord(sfOpenDocument), ord(sfExcel8)]);
for formatID in fileformats do begin
ReadFromFile(AFileName, formatID, APassword, AParams);
Reads the document from a seekable stream.
@param AStream Stream being read
@param AFormat File format assumed.
@param AParams Optional parameters to control stream access.
procedure TsWorkbook.ReadFromStream(AStream: TStream;
ReadFromStream(AStream, ord(AFormat), '', AParams);
@param AFormatID Identifier of the file format assumed.
procedure TsWorkbook.ReadFromStream(AStream: TStream; AFormatID: TsSpreadFormatID;
AStream.Position := 0;
AReader.ReadFromStream(AStream, APassword, AParams);
if ok and Assigned(FOnOpenWorkbook) then // ok is true if stream has been read successfully
Writes the document to a file. If the file doesn't exist, it will be created.
Can be used only for built-in file formats.
@param AFileName Name of the file to be written
@param AFormat The file will be written in this file format.
@param AOverwriteExisting If the file is already existing it will be
overwritten in case of AOverwriteExisting = true.
If false an exception will be raised.
procedure TsWorkbook.WriteToFile(const AFileName: string;
const AFormat: TsSpreadsheetFormat; const AOverwriteExisting: Boolean = False;
raise EFPSpreadsheetWriter.Create('[TsWorkbook.WriteToFile] Don''t call this method for user-provided file formats.');
WriteToFile(AFilename, ord(AFormat), AOverwriteExisting, AParams);
Can be used for both built-in and user-provided file formats.
@param AFormatID The file will be written in the file format identified by
this number.
If the parameter is FALSE then an exception will be raised.
const AFormatID: TsSpreadFormatID; const AOverwriteExisting: Boolean = False;
AWriter: TsBasicSpreadWriter;
AWriter := CreateSpreadWriter(self, AFormatID);
PrepareBeforeSaving;
AWriter.CheckLimitations;
FReadWriteFlag := rwfWrite;
AWriter.WriteToFile(AFileName, AOverwriteExisting, AParams);
AWriter.Free;
Writes the document to file based on the extension.
If this was an earlier sfExcel type file, it will be upgraded to sfExcel8.
@param AFileName Name of the destination file
@param AOverwriteExisting If the file already exists it will be overwritten
of AOverwriteExisting is true. In case of false, an
exception will be raised.
@param AParams Optional parameters to control stream access
procedure TsWorkbook.WriteToFile(const AFileName: String;
const AOverwriteExisting: Boolean; AParams: TsStreamParams = []);
ext := ExtractFileExt(AFileName);
if Lowercase(ext) = STR_EXCEL_EXTENSION then
fileformats := GetSpreadFormatsFromFileName(faWrite, AFileName, ord(sfExcel8)) // give preference to BIFF8
fileformats := GetSpreadFormatsFromFileName(faWrite, AFileName);
if Length(fileformats) > 0 then
WriteToFile(AFileName, fileformats[0], AOverwriteExisting, AParams)
raise EFPSpreadsheetWriter.Create(Format(rsInvalidExtension, [ext]));
Writes the document to a stream
@param AStream Instance of the stream being written to
@param AFormat File format to be written.
@param AClipboardMode Stream will be used by calling method for clipboard access
The HTML writer, for example, can be forced to write
a valid html document in Windows.
procedure TsWorkbook.WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
raise EFPSpreadsheet.Create('[TsWorkbook.WriteToFile] Don''t call this method for user-provided file formats.');
WriteToStream(AStream, ord(AFormat), AParams);
Can be used for both built-in and userprovided file formats.
@param AFormatID Identifier of the file format to be written.
procedure TsWorkbook.WriteToStream(AStream: TStream;
AFormatID: TsSpreadFormatID; AParams: TsStreamParams = []);
AWriter := CreateSpreadWriter(self, AFormatID, AParams);
AWriter.WriteToStream(AStream, AParams);
Adds a new worksheet to the workbook.
It is put to the end of the worksheet list.
@param AName The name of the new worksheet
@param ReplaceDupliateName If true and the sheet name already exists then
a number is added to the sheet name to make it
unique.
@return The instance of the newly created worksheet
@see TsWorksheet
function TsWorkbook.AddWorksheet(AName: string;
// Check worksheet name
if not ReplaceDuplicateName and (GetWorksheetByName(AName) <> nil) then
raise EFPSpreadsheet.CreateFmt(rsDuplicateWorksheetName, [AName]);
if not ValidWorksheetName(AName, ReplaceDuplicateName) then
raise EFPSpreadsheet.CreateFmt(rsInvalidWorksheetName, [AName]);
// Create worksheet...
Result := TsWorksheet.Create;
// Add it to the internal worksheet list
FWorksheets.Add(Pointer(Result));
// Remember the workbook to which it belongs (This must occur before
// setting the workbook name because the workbook is needed there).
Result.FWorkbook := Self;
Result.FActiveCellRow := 0;
Result.FActiveCellCol := 0;
// Set the name of the new worksheet.
// For this we turn off notification of listeners. This is not necessary here
// because it will be repeated at end when OnAddWorksheet is executed below.
inc(FRebuildFormulaLock);
Result.Name := AName;
dec(FRebuildFormulaLock);
// Send notification for new worksheet to listeners. They get the worksheet
// name here as well.
if (FNotificationLock = 0) and Assigned(FOnAddWorksheet) then
FOnAddWorksheet(self, Result);
// Make sure that there is an "active" worksheet
if FActiveWorksheet = nil then
SelectWorksheet(result);
Copies a worksheet (even from an external workbook) and adds it to the
current workbook
@param AWorksheet Worksheet to be copied. Can be in a different
@param ReplaceDuplicateName The copied worksheet gets the name of the original.
If ReplaceDuplicateName is true and this sheet
name already exists then a number is added to
the sheet name to make it unique.
function TsWorkbook.CopyWorksheetFrom(AWorksheet: TsWorksheet;
ReplaceDuplicateName: boolean): TsWorksheet;
if (AWorksheet = nil) then
Result := AddWorksheet(AWorksheet.Name, ReplaceDuplicateName);
for cell in AWorksheet.Cells do
r := cell^.Row;
c := cell^.Col;
Result.CopyCell(r, c, r, c, AWorksheet);
for i := 0 to AWorksheet.Cols.Count-1 do
col := AWorksheet.Cols[i];
c := col^.Col;
Result.CopyCol(c, c, AWorksheet);
for i := 0 to AWorksheet.Rows.Count-1 do
row := AWorksheet.Rows[i];
r := row^.Row;
Result.CopyRow(r, r, AWorksheet);
Result.ChangedCell(r, c);
Quick helper routine which returns the first worksheet
@return A TsWorksheet instance if at least one is present.
nil otherwise.
@see TsWorkbook.GetWorksheetByIndex
@see TsWorkbook.GetWorksheetByName
function TsWorkbook.GetFirstWorksheet: TsWorksheet;
Result := TsWorksheet(FWorksheets.First);
Quick helper routine which returns the last worksheet
function TsWorkbook.GetLastWorksheet: TsWorksheet;
Result := TsWorksheet(FWorksheets.Last);
Returns the worksheet following the specified one.
@return A TsWorksheet instance if the specified worksheet is not the last one
@see TsWorkbook.GetFirstWorksheet
@see TsWorkbook.GetPreviousWorksheet
@see TsWorkbook.GetLastWorksheet
function TsWorkbook.GetNextWorksheet(AWorksheet: TsWorksheet): TsWorksheet;
idx := FWorksheets.Indexof(AWorksheet);
if idx < FWorksheets.Count-1 then
Result := TsWorksheet(FWorksheets.Items[idx + 1])
Returns the worksheet preceding the specified one.
@return A TsWorksheet instance if the specified worksheet is not
the first one, nil otherwise.
@see TsWorkbook.GetNextWorksheet
function TsWorkbook.GetPreviousWorksheet(AWorksheet: TsWorksheet): TsWorksheet;
idx := FWorksheets.IndexOf(AWorksheet);
if idx > 0 then
Result := TsWorksheet(FWorksheets.Items[idx - 1])
Gets the worksheet with a given index
The index is zero-based, so the first worksheet
added has index 0, the second 1, etc.
@param AIndex The index of the worksheet (0-based)
@return A TsWorksheet instance if one is present at that index.
function TsWorkbook.GetWorksheetByIndex(AIndex: Integer): TsWorksheet;
if (integer(AIndex) < FWorksheets.Count) and (integer(AIndex) >= 0) then
Result := TsWorksheet(FWorksheets.Items[AIndex])
Gets the worksheet with a given worksheet name
@param AName The name of the worksheet
@return A TsWorksheet instance if one is found with that name,
nil otherwise. Case is ignored.
function TsWorkbook.GetWorksheetByName(AName: String): TsWorksheet;
i:integer;
for i:=0 to FWorksheets.Count-1 do
s := TsWorksheet(FWorksheets.Items[i]).Name;
if UTF8CompareText(s, AName) = 0 then
Result := TsWorksheet(FWorksheets.Items[i]);
The number of worksheets on the workbook
function TsWorkbook.GetWorksheetCount: Integer;
Result := FWorksheets.Count;
Counts the number of visible (= not hidden) worksheets
function TsWorkbook.GetVisibleWorksheetCount: Integer;
for i:=0 to GetWorksheetCount-1 do
if not (soHidden in GetWorksheetByIndex(i).Options) then
inc(Result);
Returns the index of a worksheet in the worksheet list
function TsWorkbook.GetWorksheetIndex(AWorksheet: TsBasicWorksheet): Integer;
Result := FWorksheets.IndexOf(AWorksheet);
Returns the index of the worksheet having the specified name, or -1 if the
worksheet does not exist.
function TsWorkbook.GetWorksheetIndex(const AWorksheetName: String): Integer;
for Result := 0 to FWorksheets.Count-1 do
s := TsWorksheet(FWorksheets[Result]).Name;
if SameText(s, AWorksheetName) then
Clears the list of Worksheets and releases their memory.
NOTE: This procedure conflicts with the WorkbookLink mechanism which requires
at least 1 worksheet per workbook!
procedure TsWorkbook.RemoveAllWorksheets;
FActiveWorksheet := nil;
FWorksheets.ForEachCall(RemoveWorksheetsCallback, nil);
FWorksheets.Clear;
if (FNotificationLock = 0) and Assigned(FOnRemoveWorksheet) then
FOnRemoveWorksheet(self, -1);
Removes all empty worksheets
procedure TsWorkbook.RemoveAllEmptyWorksheets;
for i:= FWorksheets.Count-1 downto 0 do
sheet := TsWorksheet(FWorksheets.Items[i]);
if sheet.IsEmpty then
RemoveWorksheet(sheet);
Removes the specified worksheet: Removes the sheet from the internal sheet
list, generates an event OnRemoveWorksheet, and releases all memory.
The event handler specifies the index of the deleted worksheet; the worksheet
itself does no longer exist.
procedure TsWorkbook.RemoveWorksheet(AWorksheet: TsWorksheet);
rebuildFormulas: Boolean;
if GetWorksheetCount > 1 then // There must be at least 1 worksheet left!
i := GetWorksheetIndex(AWorksheet);
if (i <> -1) and (AWorksheet <> nil) then
if Assigned(FOnRemovingWorksheet) then
FOnRemovingWorksheet(self, AWorksheet);
rebuildFormulas := FixFormulas(fcWorksheetDeleted, AWorksheet, 0);
FWorksheets.Delete(i);
AWorksheet.Free;
if rebuildFormulas then Self.RebuildFormulas;
if boAutoCalc in Options then
if Assigned(FOnRemoveWorksheet) then
FOnRemoveWorksheet(self, i);
Makes the specified worksheet "active". Only needed for visual controls.
The active worksheet is displayed in a TsWorksheetGrid and in the selected
tab of a TsWorkbookTabControl.
procedure TsWorkbook.SelectWorksheet(AWorksheet: TsWorksheet);
if (AWorksheet <> nil) and (FWorksheets.IndexOf(AWorksheet) = -1) then
raise EFPSpreadsheet.Create('[TsWorkbook.SelectSheet] Worksheet does not belong to the workbook');
FActiveWorksheet := AWorksheet;
if Assigned(FOnSelectWorksheet) then
FOnSelectWorksheet(self, AWorksheet);
Checks whether the passed string is a valid worksheet name according to Excel
(ODS seems to be a bit less restrictive, but if we follow Excel's convention
we always have valid sheet names independent of the format.
@param AName Name to be checked.
@param ReplaceDuplicateName If there exists already a sheet name equal to
AName then a number is added to AName such that
the name is unique.
@return TRUE if it is a valid worksheet name, FALSE otherwise
function TsWorkbook.ValidWorksheetName(var AName: String;
// see: http://stackoverflow.com/questions/451452/valid-characters-for-excel-sheet-names
INVALID_CHARS: set of char = ['[', ']', ':', '*', '?', '/', '\'];
unique: Boolean;
ch: char;
// Name must not be empty
if (AName = '') then
{ wp: the length restriction has been moved to the writer...
// Length must be less than 31 characters
if UTF8Length(AName) > 31 then
// Name must not contain any of the INVALID_CHARS
for ch in AName do
if ch in INVALID_CHARS then
// Name must be unique
unique := (GetWorksheetByName(AName) = nil);
if not unique then
if ReplaceDuplicateName then
i := 0;
inc(i);
unique := (GetWorksheetByName(AName + IntToStr(i)) = nil);
until unique;
AName := AName + IntToStr(i);
Analyses a string which can contain an array of cell ranges along with a
worksheet name. Extracts the worksheet (if missing the "active" worksheet of
the workbook is returned) and the cell's row and column indexes.
@param AText General cell range string in Excel notation,
i.e. worksheet name + ! + cell in A1 notation.
Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
@param AWorksheet Pointer to the worksheet referred to by AText. If AText
does not contain the worksheet name, the active worksheet
of the workbook is returned
@param ARow, ACol Zero-based row and column index of the cell identified
by ATest. If AText contains one ore more cell ranges
then the upper left corner of the first range is returned.
@param AListSeparator Character to separate the cell blocks in the text
If #0 then the ListSeparator of the workbook's FormatSettings
is used.
@returns TRUE if AText is a valid list of cell ranges, FALSE if not. If the
result is FALSE then AWorksheet, ARow and ACol may have unpredictable
values.
function TsWorkbook.TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
ranges: TsCellRangeArray;
Result := TryStrToCellRanges(AText, AWorksheet, ranges, AListSeparator);
if Result then
ARow := ranges[0].Row1;
ACol := ranges[0].Col1;
the workbook is returned) and the cell range (or the first cell range, if there
are several ranges).
@param ARange TsCellRange records identifying the cell block. If AText
contains several cell ranges the first one is returned.
@returns TRUE if AText is a valid cell range, FALSE if not. If the
result is FALSE then AWorksheet and ARange may have unpredictable
function TsWorkbook.TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
if Result then ARange := ranges[0];
the workbook is returned) and the range array.
@param ARanges Array of TsCellRange records identifying the cell blocks
result is FALSE then AWorksheet and ARanges may have unpredictable
function TsWorkbook.TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
L: TStrings;
sheetname: String;
AWorksheet := nil;
SetLength(ARanges, 0);
i := pos(SHEETSEPARATOR, AText);
if i = 0 then
AWorksheet := FActiveWorksheet
sheetname := Copy(AText, 1, i-1);
if (sheetname <> '') and (sheetname[1] = '''') then
Delete(sheetname, 1, 1);
if (sheetname <> '') and (sheetname[Length(sheetname)] = '''') then
Delete(sheetname, Length(sheetname), 1);
AWorksheet := GetWorksheetByName(sheetname);
if AWorksheet = nil then
AText := Copy(AText, i+1, Length(AText));
if AListSeparator = #0 then
L.Delimiter := FormatSettings.ListSeparator
L.Delimiter := AListSeparator;
L.DelimitedText := AText;
if L.Count = 0 then
SetLength(ARanges, L.Count);
for i:=0 to L.Count-1 do begin
if pos(':', L[i]) = 0 then begin
Result := ParseCellString(L[i], ARanges[i].Row1, ARanges[i].Col1);
if Result then begin
ARanges[i].Row2 := ARanges[i].Row1;
ARanges[i].Col2 := ARanges[i].Col1;
Result := ParseCellRangeString(L[i], ARanges[i]);
if not Result then begin
{ Format handling }
Adds the specified format record to the internal list and returns the index
in the list. If the record had already been added before the function only
returns the index.
function TsWorkbook.AddCellFormat(const AValue: TsCellFormat): Integer;
Result := FCellFormatList.Add(AValue);
Returns the contents of the format record with the specified index.
function TsWorkbook.GetCellFormat(AIndex: Integer): TsCellFormat;
Result := FCellFormatList.Items[AIndex]^;
Returns a string describing the cell format with the specified index.
function TsWorkbook.GetCellFormatAsString(AIndex: Integer): String;
cb: TsCellBorder;
fmt := GetPointerToCellFormat(AIndex);
if fmt = nil then
if (uffFont in fmt^.UsedFormattingFields) then
Result := Format('%s; Font%d', [Result, fmt^.FontIndex]);
if (uffBackground in fmt^.UsedFormattingFields) then begin
Result := Format('%s; Bg %s', [Result, GetColorName(fmt^.Background.BgColor)]);
Result := Format('%s; Fg %s', [Result, GetColorName(fmt^.Background.FgColor)]);
Result := Format('%s; Pattern %s', [Result, GetEnumName(TypeInfo(TsFillStyle), ord(fmt^.Background.Style))]);
if (uffHorAlign in fmt^.UsedFormattingfields) then
Result := Format('%s; %s', [Result, GetEnumName(TypeInfo(TsHorAlignment), ord(fmt^.HorAlignment))]);
Result := Format('%s; %s', [Result, GetEnumName(TypeInfo(TsVertAlignment), ord(fmt^.VertAlignment))]);
if (uffWordwrap in fmt^.UsedFormattingFields) then
Result := Format('%s; Word-wrap', [Result]);
numFmt := GetNumberFormat(fmt^.NumberFormatIndex);
Result := Format('%s; %s (%s)', [Result,
GetEnumName(TypeInfo(TsNumberFormat), ord(numFmt.NumFormat)),
numFmt.NumFormatStr
])
Result := Format('%s; %s', [Result, 'nfGeneral']);
s := '';
for cb in fmt^.Border do
if s = '' then s := GetEnumName(TypeInfo(TsCellBorder), ord(cb))
else s := s + '+' + GetEnumName(TypeInfo(TsCellBorder), ord(cb));
Result := Format('%s; %s', [Result, s]);
Result := Format('%s; %s', [Result, GetEnumName(TypeInfo(TsBiDiMode), ord(fmt^.BiDiMode))]);
if Result <> '' then Delete(Result, 1, 2);
Returns the count of format records used all over the workbook
function TsWorkbook.GetNumCellFormats: Integer;
Result := FCellFormatList.Count;
Returns a pointer to the format record with the specified index
function TsWorkbook.GetPointerToCellFormat(AIndex: Integer): PsCellFormat;
if FCellFormatList.Count = 0 then
raise Exception.Create('[TsWorkbook.GetPointerToCellFormat]: No format items.');
if (AIndex < 0) or (AIndex >= FCellFormatList.Count) then
AIndex := 0; // 0 is default format
Result := FCellFormatList.Items[AIndex];
Removes all cell formats from the workbook.
If AKeepDefaultFormat is true then index 0 containing the default cell format
is retained.
Use carefully!
procedure TsWorkbook.RemoveAllCellFormats(AKeepDefaultFormat: Boolean);
if AKeepDefaultFormat then
for i := FCellFormatList.Count-1 downto 1 do
FCellFormatList.Delete(i)
FCellFormatList.Clear;
{ Conditional formats }
function TsWorkbook.GetConditionalFormat(AIndex: Integer): TsConditionalFormat;
Result := FConditionalFormatList[AIndex] as TsConditionalFormat;
function TsWorkbook.GetNumConditionalFormats: Integer;
Result := FConditionalFormatList.Count;
Adds a font to the font list. Returns the index in the font list.
@param AFontName Name of the font (like 'Arial')
@param ASize Size of the font in points
@param AStyle Style of the font, a combination of TsFontStyle elements
@param AColor RGB valoe of the font color
@param APosition Specifies subscript or superscript text.
@return Index of the font in the workbook's font list
function TsWorkbook.AddFont(const AFontName: String; ASize: Single;
AStyle: TsFontStyles; AColor: TsColor;
fnt := TsFont.Create;
fnt.FontName := AFontName;
fnt.Size := ASize;
fnt.Style := AStyle;
fnt.Color := AColor;
fnt.Position := APosition;
Result := AddFont(fnt);
@param AFont TsFont record containing all font parameters
function TsWorkbook.AddFont(const AFont: TsFont): Integer;
result := FFontList.Add(AFont);
Creates a new font as a copy of the font at the specified index.
The new font is NOT YET added to the font list.
If the user does not add the font to the font list he is responsibile for
destroying it.
function TsWorkbook.CloneFont(const AFontIndex: Integer): TsFont;
Result := TsFont.Create;
fnt := GetFont(AFontIndex);
Result.FontName := fnt.FontName;
Result.Size := fnt.Size;
Result.Style := fnt.Style;
Result.Color := fnt.Color;
Result.Position := fnt.Position;
Deletes a font.
Use with caution because this will screw up the font assignment to cells.
The only legal reason to call this method is from a reader of a file format
in which the missing font #4 of BIFF does exist.
procedure TsWorkbook.DeleteFont(const AFontIndex: Integer);
if AFontIndex < FFontList.Count then
fnt := TsFont(FFontList.Items[AFontIndex]);
if fnt <> nil then fnt.Free;
FFontList.Delete(AFontIndex);
Checks whether the font with the given specification is already contained in
the font list. Returns the index, or -1 if not found.
@param AColor RGB value of the font color
@return Index of the font in the font list, or -1 if not found.
function TsWorkbook.FindFont(const AFontName: String; ASize: Single;
AStyle: TsFontStyles; AColor: TsColor; APosition: TsFontPosition = fpNormal): Integer;
Result := FindFontInList(FFontList, AFontName, ASize, AStyle, AColor, APosition);
EPS = 1e-3;
for Result := 0 to FFontList.Count-1 do
fnt := TsFont(FFontList.Items[Result]);
if (fnt <> nil) and
SameText(AFontName, fnt.FontName) and
SameValue(ASize, fnt.Size, EPS) and // careful when comparing floating point numbers
(AStyle = fnt.Style) and
(AColor = fnt.Color) and
(APosition = fnt.Position)
Initializes the font list by adding 5 fonts:
0: default font
1: like default font, but blue and underlined (for hyperlinks)
2: like default font, but bold
3: like default font, but italic
procedure TsWorkbook.InitFonts;
fntName: String;
fntSize: Single;
// Memorize old default font
with TsFont(FFontList.Items[0]) do
fntName := FontName;
fntSize := Size;
// Remove current font list
// Build new font list
SetDefaultFont(fntName, fntSize); // FONT0: Default font
AddFont(fntName, fntSize, [fssUnderline], scBlue); // FONT1: Hyperlink font = blue & underlined
AddFont(fntName, fntSize, [fssBold], scBlack); // FONT2: Bold font
AddFont(fntName, fntSize, [fssItalic], scBlack); // FONT3: Italic font (not used directly)
FBuiltinFontCount := FFontList.Count;
Clears the list of fonts and releases their memory.
procedure TsWorkbook.RemoveAllFonts;
for i := FFontList.Count-1 downto 0 do
fnt := TsFont(FFontList.Items[i]);
fnt.Free;
FFontList.Delete(i);
FBuiltinFontCount := 0;
Replaces the built-in font at a specific index with different font parameters
procedure TsWorkbook.ReplaceFont(AFontIndex: Integer; AFontName: String;
if (AFontIndex < FBuiltinFontCount) then //and (AFontIndex <> 4) then
fnt := TsFont(FFontList[AFontIndex]);
Defines the default font. This is the font with index 0 in the FontList.
The next built-in fonts will have the same font name and size
procedure TsWorkbook.SetDefaultFont(const AFontName: String; ASize: Single);
if FFontList.Count = 0 then
AddFont(AFontName, ASize, [], scBlack)
for i:=0 to FBuiltinFontCount-1 do
if (i <> 4) and (i < FFontList.Count) then // wp: why if font #4 relevant here ????
with TsFont(FFontList[i]) do
FontName := AFontName;
Size := ASize;
Returns the count of built-in fonts (default font, hyperlink font, bold font
by default).
function TsWorkbook.GetBuiltinFontCount: Integer;
Result := FBuiltinFontCount;
Returns the default font. This is the first font (index 0) in the font list
function TsWorkbook.GetDefaultFont: TsFont;
Result := GetFont(0);
Returns the point size of the default font
function TsWorkbook.GetDefaultFontSize: Single;
Result := GetFont(0).Size;
Returns the font with the given index.
@param AIndex Index of the font to be considered
@return Record containing all parameters of the font (or nil if not found).
function TsWorkbook.GetFont(AIndex: Integer): TsFont;
if (AIndex >= 0) and (AIndex < FFontList.Count) then
Result := FFontList.Items[AIndex]
Returns a string which identifies the font with a given index.
@param AIndex Index of the font
@return String with font name, font size etc.
function TsWorkbook.GetFontAsString(AIndex: Integer): String;
Result := fpsUtils.GetFontAsString(GetFont(AIndex));
Returns the count of registered fonts
function TsWorkbook.GetFontCount: Integer;
Result := FFontList.Count;
Returns the hypertext font. This is font with index 6 in the font list
function TsWorkbook.GetHyperlinkFont: TsFont;
Result := GetFont(HYPERLINK_FONTINDEX);
Adds a number format to the internal list. Returns the list index if already
present, or creates a new format item and returns its index.
function TsWorkbook.AddNumberFormat(AFormatStr: String): Integer;
if AFormatStr = '' then
Result := -1 // General number format is not stored
Result := TsNumFormatList(FNumFormatList).AddFormat(AFormatStr);
Returns the parameters of the number format stored in the NumFormatList at the
"General" number format is returned as nil.
function TsWorkbook.GetNumberFormat(AIndex: Integer): TsNumFormatParams;
if (AIndex >= 0) and (AIndex < FNumFormatList.Count) then
Result := TsNumFormatParams(FNumFormatList.Items[AIndex])
Returns the count of number format records stored in the NumFormatList
function TsWorkbook.GetNumberFormatCount: Integer;
Result := FNumFormatList.Count;
Removes all numberformats
procedure TsWorkbook.RemoveAllNumberFormats;
for i:= FEmbeddedObjList.Count-1 downto 0 do begin
nfp := TsNumFormatParams(FNumFormatList[i]);
FNumFormatList.Delete(i);
nfp.Free;
Calculates all formulas of the workbook.
2. Formulas are calculated. If referenced formulas are found as being
are calculated. This strategy is often very ineffective because it
unnecessarily recalculates formulas. You can provide a different algorithm in
the OnCalcWorkbook event.
procedure TsWorkbook.CalcFormulas;
p: Pointer;
if (boIgnoreFormulas in Options) then
inc(FCalculationLock);
if Assigned(FOnCalcWorkbook) then
FOnCalcWorkbook(self);
// Step1 - mark all formulas as "not calculated"
for p in FWorksheets do begin
sheet := TsWorksheet(p);
for formula in sheet.Formulas do
// Step 2 - calculate formulas. If the formula calculted requires another
// the result of another formula not yet calculated this formula is
// calculated immediately.
sheet.CalcFormula(formula);
dec(FCalculationLock);
Something was changed anywhere in the workbook which has an effect on existing
formulas. This procedure runs through all formulas and performs the
correction.
@param ACorrection Describes what has to be corrected.
Example: fcWorksheetRenamed means that a worksheet has
been renamed and the new name must be used in
corresponding formulas
@param AData A pointer with further information on the correction to
be made. Depends on ACorrection.
Example:
In the fcWorksheetRenamed example above this points to
the worksheet that was renamed.
@param AParam Provides additional information. Depends on ACorrection
@return The function returns true if the string formulas of the
workbook have to be recreated.
function TsWorkbook.FixFormulas(ACorrection: TsFormulaCorrection;
for i := 0 to GetWorksheetCount-1 do begin
sheet := GetWorksheetByIndex(i);
Result := FixFormula(formula, ACorrection, AData, AParam);
if (boAutoCalc in Options) and formulaChanged then
procedure TsWorkbook.RebuildFormulas;
if FRebuildFormulaLock = 0 then
FWorksheets.ForEachCall(RebuildFormulasCallback, nil);
procedure TsWorkbook.LockFormulas;
inc(FDeleteFormulaLock);
procedure TsWorkbook.UnlockFormulas;
dec(FDeleteFormulaLock);
{ AData points to the deleted worksheet }
procedure FixWorksheetDeletedCallback(ANode: TsExprNode; AData1, AData2: Pointer;
var MustRebuildFormulas: Boolean);
deletedindex: Integer;
deletedSheet: TsWorksheet;
cellNode: TsCellExprNode;
rngNode: TsCellRangeExprNode;
index, index1, index2: Integer;
Unused(AData2);
if ANode is TsCellExprNode then
cellNode := TsCellExprNode(ANode);
deletedSheet := TsWorksheet(AData1);
deletedindex := TsWorkbook(cellNode.GetWorkbook).GetWorksheetIndex(deletedSheet);
index := cellNode.GetSheetIndex;
if deletedindex < index then begin
cellNode.SetSheetIndex(index-1);
MustRebuildFormulas := true;
if deletedIndex = index then begin
cellNode.Error := errIllegalRef;
if ANode is TsCellRangeExprNode then
rngNode := TsCellRangeExprNode(ANode);
deletedIndex := TsWorkbook(rngNode.GetWorkbook).GetWorksheetIndex(deletedSheet);
index1 := rngNode.GetSheetIndex(1);
index2 := rngNode.GetSheetIndex(2);
if deletedIndex < index1 then begin
rngNode.SetSheetIndex(1, index1-1);
rngNode.SetSheetIndex(2, index2-1);
if (deletedIndex > index1) and (deletedIndex < index2) then begin
if (deletedIndex = index1) and (index1 <> index2) then begin
if (deletedIndex = index2) and (index1 <> index2) then begin
if (deletedIndex = index1) and (deletedIndex = index2) then begin
rngNode.Error := errIllegalRef;
function TsWorkbook.FixFormula(AFormula: PsFormula;
ACorrection: TsFormulaCorrection; AData: Pointer; AParam: PtrInt): Boolean;
Unused(AParam); // Maybe later...
case ACorrection of
fcWorksheetRenamed:
Result := true; // Nothing to do, no sheet names in formula nodes
fcWorksheetDeleted:
Result := AFormula^.Parser.IterateNodes(FixWorksheetDeletedCallback, AData, nil);
procedure TsWorkbook.MoveSheet(AFromIndex, AToIndex: Integer);
FWorksheets.Move(AFromIndex, AToIndex);
if Assigned(FOnChangeWorksheet) then
FOnChangeWorksheet(Self, GetWorksheetByIndex(AToIndex));
Writes the selected cells to a stream for usage in the clipboard.
Transfer to the clipboard has do be done by the calling routine since
fpspreadsheet does not "know" the system's clipboard.
procedure TsWorkbook.CopyToClipboardStream(AStream: TStream;
clipbook: TsWorkbook;
clipsheet: TsWorksheet;
range: TsCellRangeArray;
srccell, destcell: PCell;
if ActiveWorksheet = nil then
// Create workbook which will be written to clipboard stream
// Contains only the selected worksheet and the selected cells.
clipbook := TsWorkbook.Create;
clipsheet := clipbook.AddWorksheet(ActiveWorksheet.Name);
for sel in ActiveWorksheet.GetSelection do
for r := sel.Row1 to sel.Row2 do
for c := sel.Col1 to sel.Col2 do
srccell := ActiveWorksheet.FindCell(r, c);
if ActiveWorksheet.IsMerged(srccell) then
srccell := ActiveWorksheet.FindMergeBase(srccell);
if srccell <> nil then begin
destcell := clipsheet.GetCell(r, c); // wp: why was there AddCell?
clipsheet.CopyCell(srccell, destcell);
// Select the same cells as in the source workbook.
range := ActiveWorksheet.GetSelection;
clipsheet.SetSelection(range);
clipsheet.SelectCell(range[0].Row1, range[0].Col1);
// Write this workbook to a stream. Set the parameter spClipboard to
// indicate that this should be the special clipboard version of the stream.
clipbook.WriteToStream(AStream, AFormat, AParams + [spClipboard]);
if AFormat = sfCSV then
AStream.WriteByte(0);
// The calling routine which copies the stream to the clipboard requires
// the stream to be at its beginning.
clipbook.Free;
Copies the cells stored in the specified stream to the active worksheet.
The provided stream contains data from the system's clipboard.
Note that transfer from the clipboard to the stream has to be done by the
calling routine since fpspreadsheet does not "know" the system's clipboard.
procedure TsWorkbook.PasteFromClipboardStream(AStream: TStream;
AFormat: TsSpreadsheetFormat; AOperation: TsCopyOperation;
AParams: TsStreamParams = []; ATransposed: Boolean = false);
selArray: TsCellRangeArray;
r, c: LongInt;
dr, dc: LongInt;
i: Integer; // counter
ncs, nrs: Integer; // Num cols source, num rows source, ...
//ncd, nrd: Integer;
rdest, cdest: Integer; // row and column index at destination
nselS, nselD: Integer; // count of selected blocks
Unused(ATransposed);
if AOperation = coNone then
// Create workbook into which the clipboard stream will write
clipbook.Options := clipbook.Options + [boReadFormulas];
// Read stream into this temporary workbook
// Set last parameter (ClipboardMode) to TRUE to activate special format
// treatment for clipboard, if needed.
clipbook.ReadFromStream(AStream, AFormat, AParams + [spClipboard]);
clipsheet := clipbook.GetWorksheetByIndex(0);
// count of blocks in source (clipboard sheet)
nselS := clipsheet.GetSelectionCount;
// count of selected blocks at destination
nselD := ActiveWorksheet.GetSelectionCount;
// -------------------------------------------------------------------------
// Case (1): Destination is a single cell, source can be any shape
// --> Source shape is duplicated starting at destination
if (nselD = 1)
and (ActiveWorksheet.GetSelection[0].Col1 = ActiveWorksheet.GetSelection[0].Col2)
and (ActiveWorksheet.GetSelection[0].Row1 = ActiveWorksheet.GetSelection[0].Row2)
// Find offset of active cell to left/top cell in clipboard sheet
dr := LongInt(ActiveWorksheet.ActiveCellRow) - clipsheet.ActiveCellRow;
dc := LongInt(ActiveWorksheet.ActiveCellCol) - clipsheet.ActiveCellCol;
// Copy cells from clipboard sheet to active worksheet
// Shift them such that top/left of clipboard sheet is at active cell
for srcCell in clipsheet.Cells do
r := LongInt(srcCell^.Row) + dr;
c := LongInt(srcCell^.Col) + dc;
destcell := ActiveWorksheet.GetCell(r, c);
case AOperation of
coCopyCell : ActiveWorksheet.CopyCell(srcCell, destCell);
coCopyValue : ActiveWorksheet.CopyValue(srcCell, destCell);
coCopyFormat : ActiveWorksheet.CopyFormat(srcCell, destCell);
coCopyFormula : ActiveWorksheet.CopyFormula(srcCell, destCell);
// Select all copied cells
sel := Range(Cardinal(-1), Cardinal(-1), Cardinal(-1), Cardinal(-1));
SetLength(selArray, nselS);
for i := 0 to nselS-1 do
sel := clipsheet.GetSelection[i];
selArray[i].Row1 := LongInt(sel.Row1) + dr;
selArray[i].Col1 := LongInt(sel.Col1) + dc;
selArray[i].Row2 := LongInt(sel.Row2) + dr;
selArray[i].Col2 := LongInt(sel.Col2) + dc;
ActiveWorksheet.SetSelection(selArray);
// Select active cell. If not found in the file, let's use the last cell of the selections
if (clipsheet.ActiveCellRow <> 0) and (clipsheet.ActiveCellCol <> 0) then
r := clipsheet.ActiveCellRow;
c := clipsheet.ActiveCellCol;
r := LongInt(sel.Row2);
c := LongInt(sel.Col2);
if (r <> -1) and (c <> -1) then
ActiveWorksheet.SelectCell(r + dr, c + dc);
// Case (2): Source is a single block (not necessarily a cell), Dest can be
// any shape --> source is tiled into destination
// if nselS = 1 then
// size of source block
with clipsheet do
ncs := LongInt(GetLastColIndex(true)) - LongInt(GetFirstColIndex(true)) + 1;
nrs := LongInt(GetLastRowIndex(true)) - LongInt(GetFirstRowIndex(true)) + 1;
// Iterate over all destination blocks
for i := 0 to nselD-1 do
// size of currently selected block at destination
with ActiveWorksheet.GetSelection[i] do
ncd := Integer(Col2) - Integer(Col1) + 1;
nrd := Integer(Row2) - Integer(Row1) + 1;
r := ActiveWorksheet.GetSelection[i].Row1;
while r <= longint(ActiveWorksheet.GetSelection[i].Row2) do begin
c := ActiveWorksheet.GetSelection[i].Col1;
while c <= longint(ActiveWorksheet.GetSelection[i].Col2) do begin
dr := r - clipsheet.GetFirstRowIndex;
dc := c - clipsheet.GetFirstColIndex;
for srccell in clipsheet.Cells do
rdest := longint(srccell^.Row) + dr;
if rdest > integer(ActiveWorksheet.GetSelection[i].Row2) then
Continue;
cdest := longint(srcCell^.Col) + dc;
if cdest > integer(ActiveWorksheet.GetSelection[i].Col2) then
destcell := ActiveWorksheet.GetCell(
LongInt(srcCell^.Row) + dr,
LongInt(srcCell^.Col) + dc
end; // for srcCell
inc(c, ncs);
end; // while c...
inc(r, nrs);
end; // while r...
end; // for i
// No need to select copied cells - they already are.
end ;
// Other arrangements of source and destination are not supported
raise Exception.Create('This arrangement of source and destination '+
'cells in not supported for copy & paste');
Creates a new "embedded" stream and loads the specified file.
Returns the index of the embedded file item.
Image dimensions are converted to workbook units.
function TsWorkbook.AddEmbeddedObj(const AFileName: String): Integer;
obj: TsEmbeddedObj = nil;
AddErrorMsg(rsFileNotFound, [AFileName]);
obj := TsEmbeddedObj.Create;
if obj.LoadFromFile(AFileName) then
obj.ImageWidth := ConvertUnits(obj.ImageWidth, suInches, FUnits);
obj.ImageHeight := ConvertUnits(obj.ImageHeight, suInches, FUnits);
Result := FEmbeddedObjList.Add(obj)
AddErrorMsg(rsFileFormatNotSupported, [AFileName]);
obj.Free;
Creates a new "embedded" stream and copies the specified stream to it.
Returns the index of the embedded object.
function TsWorkbook.AddEmbeddedObj(AStream: TStream;
const AName: String = ''; ASize: Int64 = -1): Integer;
if obj.LoadFromStream(AStream, AName, ASize) then
AddErrorMsg(rsImageFormatNotSupported);
Checks whether an embedded object with the specified file name already exists.
If yes, returns its index in the object list, or -1 if no.
function TsWorkbook.FindEmbeddedObj(const AFileName: String): Integer;
for Result:=0 to FEmbeddedObjList.Count-1 do
obj := TsEmbeddedObj(FEmbeddedObjList[Result]);
if obj.FileName = AFileName then
Returns the embedded object stored in the embedded object list at the
function TsWorkbook.GetEmbeddedObj(AIndex: Integer): TsEmbeddedObj;
Result := TsEmbeddedObj(FEmbeddedObjList[AIndex]);
Returns the count of embedded objects
function TsWorkbook.GetEmbeddedObjCount: Integer;
Result := FEmbeddedObjList.Count;
Returns true if there is at least one worksheet with an embedded images.
function TsWorkbook.HasEmbeddedSheetImages: Boolean;
if sheet.GetImageCount > 0 then
Removes all embedded objects
procedure TsWorkbook.RemoveAllEmbeddedObj;
for i:= 0 to FEmbeddedObjList.Count-1 do
TsEmbeddedObj(FEmbeddedObjList[i]).Free;
FEmbeddedObjList.Clear;
Converts a fpspreadsheet color into into a string RRGGBB.
Note that colors are written to xls files as ABGR (where A is 0).
if the color is scRGBColor the color value is taken from the argument
ARGBColor, otherwise from the palette entry for the color index.
function TsWorkbook.FPSColorToHexString(AColor: TsColor;
ARGBColor: TFPColor): string;
TRgba = packed record Red, Green, Blue, A: Byte end;
colorvalue: TsColorValue;
r,g,b: Byte;
if AColor = scRGBColor then
r := ARGBColor.Red div $100;
g := ARGBColor.Green div $100;
b := ARGBColor.Blue div $100;
colorvalue := GetPaletteColor(AColor);
r := TRgba(colorvalue).Red;
g := TRgba(colorvalue).Green;
b := TRgba(colorvalue).Blue;
Result := Format('%.2x%.2x%.2x', [r, g, b]);
Returns the name of the color pointed to by the given color index.
If the name is not known the hex string is returned as RRGGBB.
@param AColorIndex Palette index of the color considered
@return String identifying the color (a color name or, if unknown, a
string showing the rgb components
function TsWorkbook.GetColorName(AColorIndex: TsColor): string;
case AColorIndex of
scTransparent:
Result := 'transparent';
scNotDefined:
Result := 'not defined';
GetColorName(GetPaletteColor(AColorIndex), Result);
Returns the name of an rgb color value.
@param AColorValue rgb value of the color considered
@param AName String identifying the color (a color name or, if
unknown, a string showing the rgb components
procedure TsWorkbook.GetColorName(AColorValue: TsColorValue; out AName: String);
TRgba = packed record R,G,B,A: Byte; end;
// Find color value in default palette
for i:=0 to High(DEFAULT_PALETTE) do
// if found: get the color name from the default color names array
if DEFAULT_PALETTE[i] = AColorValue then
AName := DEFAULT_COLORNAMES[i];
// if not found: construct a string from rgb byte values.
with TRgba(AColorValue) do
AName := Format('%.2x%.2x%.2x', [R, G, B]);
Converts the palette color of the given index to a string that can be used
in HTML code. For ODS.
@param AColorIndex Index of the color considered
@return A HTML-compatible string identifying the color.
"Red", for example, is returned as '#FF0000';
function TsWorkbook.GetPaletteColorAsHTMLStr(AColorIndex: TsColor): String;
Result := ColorToHTMLColorStr(GetPaletteColor(AColorIndex));
Instructs the workbook to take colors from the default palette. Is called
from ODS reader because ODS does not have a palette. Without a palette the
color constants (scRed etc.) would not be correct any more.
procedure TsWorkbook.UseDefaultPalette;
UsePalette(@DEFAULT_PALETTE, Length(DEFAULT_PALETTE), false);
Instructs the Workbook to take colors from the palette pointed to by the
parameter APalette
This palette is only used for writing. When reading the palette found in the
file is used.
@param APalette Pointer to the array of TsColorValue numbers which will
become the new palette
@param APaletteCount Count of numbers in the source palette
@param ABigEnding If true, indicates that the source palette is in
big-endian notation. The methods inverts the rgb
components to little-endian which is used by
fpspreadsheet internally.
procedure TsWorkbook.UsePalette(APalette: PsPalette; APaletteCount: Word;
ABigEndian: Boolean);
if APaletteCount > 64 then
raise EFPSpreadsheet.Create('Due to Excel-compatibility, palettes cannot have more then 64 colors.');
{$IFOPT R+}
{$DEFINE RNGCHECK}
{$ENDIF}
SetLength(FPalette, APaletteCount);
if ABigEndian then
for i:=0 to APaletteCount-1 do
{$IFDEF RNGCHECK}
{$R-}
FPalette[i] := LongRGBToExcelPhysical(APalette^[i])
{$R+}
FPalette[i] := APalette^[i];
if Assigned(FOnChangePalette) then FOnChangePalette(self);
Checks whether a given color is used somewhere within the entire workbook
@param AColorIndex Palette index of the color
@result True if the color is used by at least one cell, false if not.
function TsWorkbook.UsesColor(AColorIndex: TsColor): Boolean;
for cell in sheet.Cells do
fmt := GetPointerToCellFormat(cell^.FormatIndex);
if fmt^.Background.BgColor = AColorIndex then exit;
if fmt^.Background.FgColor = AColorIndex then exit;
for b in TsCellBorders do
if fmt^.BorderStyles[b].Color = AColorIndex then
fnt := GetFont(fmt^.FontIndex);
if fnt.Color = AColorIndex then
end. {** End Unit: fpspreadsheet }