{ fpspreadsheet.pas Writes an spreadsheet document AUTHORS: Felipe Monteiro de Carvalho } unit fpspreadsheet; {$ifdef fpc} {$mode delphi} {$endif} interface uses Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding; type {@@ File formats suppored by fpspreadsheet } TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8, sfOOXML, sfOpenDocument, sfCSV, sfWikiTable_Pipes, sfWikiTable_WikiMedia); const { Default extensions } STR_EXCEL_EXTENSION = '.xls'; STR_OOXML_EXCEL_EXTENSION = '.xlsx'; STR_OPENDOCUMENT_CALC_EXTENSION = '.ods'; STR_COMMA_SEPARATED_EXTENSION = '.csv'; STR_WIKITABLE_PIPES = '.wikitable_pipes'; STR_WIKITABLE_WIKIMEDIA = '.wikitable_wikimedia'; type {@@ Possible encodings for a non-unicode encoded text } TsEncoding = ( seLatin1, seLatin2, seCyrillic, seGreek, seTurkish, seHebrew, seArabic ); {@@ Describes a formula Supported syntax:
=A1+B1+C1/D2... - Array with simple mathematical operations =SUM(A1:D1) - SUM operation in a interval} TsFormula = record FormulaStr: string; DoubleValue: double; end; {@@ Tokens to identify the elements in an expanded formula. See http://www.techonthenet.com/excel/formulas/ for an explanation of meaning and parameters of each formula NOTE: When adding or rearranging items: - make sure that the subtypes TOperandTokens, TBasicOperationTokens and TFuncTokens are complete - make sure to keep the FEProps table in sync - make sure to keep the TokenID table in TsSpreadBIFFWriter.FormulaElementKindToExcelTokenID, unit xlscommon, in sync } TFEKind = ( { Basic operands } fekCell, fekCellRef, fekCellRange, fekNum, fekInteger, fekString, fekBool, fekErr, fekMissingArg, { Basic operations } fekAdd, fekSub, fekMul, fekDiv, fekPercent, fekPower, fekUMinus, fekUPlus, fekConcat, // string concatenation fekEqual, fekGreater, fekGreaterEqual, fekLess, fekLessEqual, fekNotEqual, fekParen, { Built-in/Worksheet Functions} // math fekABS, fekACOS, fekACOSH, fekASIN, fekASINH, fekATAN, fekATANH, fekCOS, fekCOSH, fekDEGREES, fekEXP, fekINT, fekLN, fekLOG, fekLOG10, fekPI, fekRADIANS, fekRAND, fekROUND, fekSIGN, fekSIN, fekSINH, fekSQRT, fekTAN, fekTANH, // date/time fekDATE, fekDATEDIF, fekDATEVALUE, fekDAY, fekHOUR, fekMINUTE, fekMONTH, fekNOW, fekSECOND, fekTIME, fekTIMEVALUE, fekTODAY, fekWEEKDAY, fekYEAR, // statistical fekAVEDEV, fekAVERAGE, fekBETADIST, fekBETAINV, fekBINOMDIST, fekCHIDIST, fekCHIINV, fekCOUNT, fekCOUNTA, fekCOUNTBLANK, fekCOUNTIF, fekMAX, fekMEDIAN, fekMIN, fekPERMUT, fekPOISSON, fekPRODUCT, fekSTDEV, fekSTDEVP, fekSUM, fekSUMIF, fekSUMSQ, fekVAR, fekVARP, // financial fekFV, fekNPER, fekPMT, fekPV, fekRATE, // logical fekAND, fekFALSE, fekIF, fekNOT, fekOR, fekTRUE, // string fekCHAR, fekCODE, fekLEFT, fekLOWER, fekMID, fekPROPER, fekREPLACE, fekRIGHT, fekSUBSTITUTE, fekTRIM, fekUPPER, // lookup/reference fekCOLUMN, fekCOLUMNS, fekROW, fekROWS, // info fekCELLINFO, fekINFO, fekIsBLANK, fekIsERR, fekIsERROR, fekIsLOGICAL, fekIsNA, fekIsNONTEXT, fekIsNUMBER, fekIsRef, fekIsTEXT, fekValue, { Other operations } fekOpSUM {Unary sum operation. Note: CANNOT be used for summing sell contents; use fekSUM} ); {@@ These tokens identify operands in RPN formulas. } TOperandTokens = fekCell..fekMissingArg; {@@ These tokens identify basic operations in RPN formulas. } TBasicOperationTokens = fekAdd..fekParen; {@@ These tokens identify spreadsheet functions in RPN formulas. } TFuncTokens = fekAbs..fekOpSum; {@@ Flags to mark the address or a cell or a range of cells to be absolute or relative. They are used in the set TsRelFlags. } TsRelFlag = (rfRelRow, rfRelCol, rfRelRow2, rfRelCol2); {@@ Flags to mark the address of a cell or a range of cells to be absolute or relative. It is a set consisting of TsRelFlag elements. } TsRelFlags = set of TsRelFlag; {@@ Elements of an expanded formula. } TsFormulaElement = record ElementKind: TFEKind; Row, Row2: Word; // zero-based Col, Col2: Word; // zero-based Param1, Param2: Word; // Extra parameters DoubleValue: double; IntValue: Word; StringValue: String; RelFlags: TsRelFlags; // store info on relative/absolute addresses ParamsNum: Byte; end; {@@ Expanded formula. Used by backend modules. Provides more information than the text only. Consists of TsFormulaElements. } TsExpandedFormula = array of TsFormulaElement; {@@ RPN formula. Similar to the expanded formula, but in RPN notation. Simplifies the task of format writers which need RPN } TsRPNFormula = array of TsFormulaElement; {@@ Describes the type of content in a cell of a TsWorksheet } TCellContentType = (cctEmpty, cctFormula, cctRPNFormula, cctNumber, cctUTF8String, cctDateTime, cctBool, cctError); {@@ Error code values } TsErrorValue = ( errOK, // no error errEmptyIntersection, // #NULL! errDivideByZero, // #DIV/0! errWrongType, // #VALUE! errIllegalRef, // #REF! errWrongName, // #NAME? errOverflow, // #NUM! errArgError, // #N/A // --- no Excel errors -- errFormulaNotSupported ); {@@ List of possible formatting fields } TsUsedFormattingField = (uffTextRotation, uffFont, uffBold, uffBorder, uffBackgroundColor, uffNumberFormat, uffWordWrap, uffHorAlign, uffVertAlign ); {@@ Describes which formatting fields are active } TsUsedFormattingFields = set of TsUsedFormattingField; {@@ Number/cell formatting. Only uses a subset of the default formats, enough to be able to read/write date/time values. nfCustom allows to apply a format string directly. } TsNumberFormat = ( // general-purpose for all numbers nfGeneral, // numbers nfFixed, nfFixedTh, nfExp, nfSci, nfPercentage, // currency nfCurrency, nfCurrencyRed, nfAccounting, nfAccountingRed, // dates and times nfShortDateTime, {nfFmtDateTime, }nfShortDate, nfLongDate, nfShortTime, nfLongTime, nfShortTimeAM, nfLongTimeAM, nfTimeInterval, // other (format string goes directly into the file) nfCustom); const { @@ Codes for curreny format according to FormatSettings.CurrencyFormat: "C" = currency symbol, "V" = currency value, "S" = space character For the negative value formats, we use also: "B" = bracket, "M" = Minus The order of these characters represents the order of these items. Example: 1000 dollars --> "$1000" for pCV, or "1000 $" for pVsC -1000 dollars --> "($1000)" for nbCVb, or "-$ 1000" for nMCSV Assignment taken from "sysstr.inc" } pcfDefault = -1; // use value from Worksheet.FormatSettings.CurrencyFormat pcfCV = 0; // $1000 pcfVC = 1; // 1000$ pcfCSV = 2; // $ 1000 pcfVSC = 3; // 1000 $ ncfDefault = -1; // use value from Worksheet.FormatSettings.NegCurrFormat ncfBCVB = 0; // ($1000) ncfMCV = 1; // -$1000 ncfCMV = 2; // $-1000 ncfCVM = 3; // $1000- ncfBVCB = 4; // (1000$) ccfMVC = 5; // -1000$ ncfVMC = 6; // 1000-$ ncfVCM = 7; // 1000$- ncfMVSC = 8; // -1000 $ ncfMCSV = 9; // -$ 1000 ncfVSCM = 10; // 1000 $- ncfCSVM = 11; // $ 1000- ncfCSMV = 12; // $ -1000 ncfVMSC = 13; // 1000- $ ncfBCSVB = 14; // ($ 1000) ncfBVSCB = 15; // (1000 $) type {@@ Text rotation formatting. The text is rotated relative to the standard orientation, which is from left to right horizontal:
---> ABCSo 90 degrees clockwise means that the text will be:
| A | B v CAnd 90 degree counter clockwise will be:
^ C | B | ADue to limitations of the text mode the characters are not rotated here. There is, however, also a "stacked" variant which looks exactly like the former case. } TsTextRotation = (trHorizontal, rt90DegreeClockwiseRotation, rt90DegreeCounterClockwiseRotation, rtStacked); {@@ Indicates horizontal text alignment in cells } TsHorAlignment = (haDefault, haLeft, haCenter, haRight); {@@ Indicates vertical text alignment in cells } TsVertAlignment = (vaDefault, vaTop, vaCenter, vaBottom); {@@ Colors in fpspreadsheet are given as indices into a palette. Use the workbook's GetPaletteColor to determine the color rgb value as little-endian (with "r" being the low-value byte, in agreement with TColor). The data type for rgb values is TsColorValue. } TsColor = Word; {@@ These are some constants for color indices into the default palette. Note, however, that if a different palette is used there may be more colors, and the names of the color constants may no longer be correct. } const scBlack = $00; scWhite = $01; scRed = $02; scGreen = $03; scBlue = $04; scYellow = $05; scMagenta = $06; scCyan = $07; scDarkRed = $08; scDarkGreen = $09; scDarkBlue = $0A; scNavy = $0A; scOlive = $0B; scPurple = $0C; scTeal = $0D; scSilver = $0E; scGrey = $0F; scGray = $0F; // redefine to allow different kinds of writing scGrey10pct = $10; scGray10pct = $10; scGrey20pct = $11; scGray20pct = $11; scOrange = $12; scDarkbrown = $13; scBrown = $14; scBeige = $15; scWheat = $16; // not sure - but I think the mechanism with scRGBColor is not working... // Will be removed sooner or later... scRGBColor = $FFFF; scNotDefined = $FFFF; type {@@ Data type for rgb color values } TsColorValue = DWord; {@@ Palette of color values. A "color value" is a DWord value containing rgb colors. } TsPalette = array[0..0] of TsColorValue; PsPalette = ^TsPalette; {@@ Font style (redefined to avoid usage of "Graphics" } TsFontStyle = (fssBold, fssItalic, fssStrikeOut, fssUnderline); {@@ Set of font styles } TsFontStyles = set of TsFontStyle; {@@ Font record used in fpspreadsheet. Contains the font name, the font size (in points), the font style, and the font color. } TsFont = class {@@ Name of the font face, such as 'Arial' or 'Times New Roman' } FontName: String; {@@ Size of the font in points } Size: Single; // in "points" {@@ Font style, such as bold, italics etc. - see TsFontStyle} Style: TsFontStyles; {@@ Text color given by the index into the workbook's color palette } Color: TsColor; end; {@@ Indicates the border for a cell. If included in the CellBorders set the corresponding border is drawn in the style defined by the CellBorderStyle. } TsCellBorder = (cbNorth, cbWest, cbEast, cbSouth); {@@ Indicates the border for a cell } TsCellBorders = set of TsCellBorder; {@@ Line style (for cell borders) } TsLineStyle = (lsThin, lsMedium, lsDashed, lsDotted, lsThick, lsDouble, lsHair); {@@ The Cell border style reocrd contains the linestyle and color of a cell border. There is a CellBorderStyle for each border. } TsCellBorderStyle = record LineStyle: TsLineStyle; Color: TsColor; end; {@@ The cell border styles of each cell border are collected in this array. } TsCellBorderStyles = array[TsCellBorder] of TsCellBorderStyle; {@@ Border styles for each cell border used by default: a thin, black, solid line } const DEFAULT_BORDERSTYLES: TsCellBorderStyles = ( (LineStyle: lsThin; Color: scBlack), (LineStyle: lsThin; Color: scBlack), (LineStyle: lsThin; Color: scBlack), (LineStyle: lsThin; Color: scBlack) ); type {@@ Cell structure for TsWorksheet The cell record contains information on the location of the cell (row and column index), on the value contained (number, date, text, ...), and on formatting. Never suppose that all *Value fields are valid, only one of the ContentTypes is valid. For other fields use TWorksheet.ReadAsUTF8Text and similar methods @see TWorksheet.ReadAsUTF8Text } TCell = record Col: Cardinal; // zero-based Row: Cardinal; // zero-based ContentType: TCellContentType; { Possible values for the cells } FormulaValue: TsFormula; RPNFormulaValue: TsRPNFormula; NumberValue: double; UTF8StringValue: ansistring; DateTimeValue: TDateTime; BoolValue: Boolean; ErrorValue: TsErrorValue; { Formatting fields } { When adding/deleting formatting fields don't forget to update CopyFormat! } UsedFormattingFields: TsUsedFormattingFields; FontIndex: Integer; TextRotation: TsTextRotation; HorAlignment: TsHorAlignment; VertAlignment: TsVertAlignment; Border: TsCellBorders; BorderStyles: TsCelLBorderStyles; BackgroundColor: TsColor; NumberFormat: TsNumberFormat; NumberFormatStr: String; RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR end; {@@ Pointer to a TCell record } PCell = ^TCell; const // Takes account of effect of cell margins on row height by adding this // value to the nominal row height. Note that this is an empirical value and may be wrong. ROW_HEIGHT_CORRECTION = 0.2; type {@@ The record TRow collects information of a spreadsheet row: @param Row The index of the row (beginning with 0) @param Height The height of the row (expressed as lines count of the default font) Only rows with heights that cannot be derived from the font height have a row record. } TRow = record Row: Cardinal; Height: Single; // in "lines" end; {@@ Pointer to a TRow record } PRow = ^TRow; {@@ The record TCol collects information on a spreadsheet column: @param Col The index of the column (beginning with 0) @param Width The width of the column (expressed in character count of the "0" character of the default font. Only columns with non-default widths have a column record. } TCol = record Col: Cardinal; Width: Single; // in "characters". Excel uses the width of char "0" in 1st font end; {@@ Pointer to a TCol record } PCol = ^TCol; {@@ User interface options: @param soShowGridLines Show or hide the grid lines in the spreadsheet @param soShowHeaders Show or hide the column or row headers of the spreadsheet @param soHasFrozenPanes If set a number of rows and columns of the spreadsheet is fixed and does not scroll. The number is defined by LeftPaneWidth and TopPaneHeight. @param soSelected (currently not used) } TsSheetOption = (soShowGridLines, soShowHeaders, soHasFrozenPanes, soSelected); {@@ Set of user interface options @ see TsSheetOption } TsSheetOptions = set of TsSheetOption; type TsCustomSpreadReader = class; TsCustomSpreadWriter = class; TsWorkbook = class; { TsWorksheet } {@@ This event fires whenever a cell value or cell formatting changes. It is handled by TsWorksheetGrid to update the grid. } TsCellEvent = procedure (Sender: TObject; ARow, ACol: Cardinal) 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 private FWorkbook: TsWorkbook; FCells: TAvlTree; // Items are TCell FCurrentNode: TAVLTreeNode; // For GetFirstCell and GetNextCell FRows, FCols: TIndexedAVLTree; // This lists contain only rows or cols with styles different from default FLeftPaneWidth: Integer; FTopPaneHeight: Integer; FOptions: TsSheetOptions; FOnChangeCell: TsCellEvent; FOnChangeFont: TsCellEvent; function GetFormatSettings: TFormatSettings; procedure RemoveCallback(data, arg: pointer); protected procedure ChangedCell(ARow, ACol: Cardinal); procedure ChangedFont(ARow, ACol: Cardinal); public {@@ Name of the sheet. In the popular spreadsheet applications this is displayed at the tab of the sheet. } Name: string; { Base methods } constructor Create; destructor Destroy; override; { Utils } class function CellPosToText(ARow, ACol: Cardinal): string; procedure RemoveAllCells; { Reading of values } function ReadAsUTF8Text(ARow, ACol: Cardinal): ansistring; overload; function ReadAsUTF8Text(ACell: PCell): ansistring; overload; 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): String; function ReadRPNFormulaAsString(ACell: PCell): String; function ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields; function ReadBackgroundColor(ARow, ACol: Cardinal): TsColor; { Reading of cell attributes } function GetNumberFormatAttributes(ACell: PCell; out ADecimals: Byte; out ACurrencySymbol: String): Boolean; { Writing of values } procedure WriteBlank(ARow, ACol: Cardinal); procedure WriteBoolValue(ARow, ACol: Cardinal; AValue: Boolean); procedure WriteCurrency(ARow, ACol: Cardinal; AValue: Double; AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = 2; ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1; ANegCurrFormat: Integer = -1); overload; procedure WriteCurrency(ACell: PCell; AValue: Double; AFormat: TsNumberFormat = nfCurrency; ADecimals: Integer = -1; ACurrencySymbol: String = '?'; APosCurrFormat: Integer = -1; ANegCurrFormat: Integer = -1); overload; procedure WriteCurrency(ARow, ACol: Cardinal; AValue: Double; AFormat: TsNumberFormat; AFormatString: String); overload; procedure WriteCurrency(ACell: PCell; AValue: Double; AFormat: TsNumberFormat; AFormatString: String); overload; procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload; procedure WriteDateTime(ACell: PCell; AValue: TDateTime; AFormat: TsNumberFormat = nfShortDateTime; AFormatStr: String = ''); overload; procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime; AFormatStr: String); overload; procedure WriteDateTime(ACell: PCell; AValue: TDateTime; AFormatStr: String); overload; procedure WriteErrorValue(ARow, ACol: Cardinal; AValue: TsErrorValue); overload; procedure WriteErrorValue(ACell: PCell; AValue: TsErrorValue); overload; procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula); procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2); overload; procedure WriteNumber(ACell: PCell; ANumber: Double; AFormat: TsNumberFormat = nfGeneral; ADecimals: Byte = 2); overload; procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double; AFormat: TsNumberFormat; AFormatString: String); overload; procedure WriteNumber(ACell: PCell; ANumber: Double; AFormat: TsNumberFormat; AFormatString: String); overload; procedure WriteRPNFormula(ARow, ACol: Cardinal; AFormula: TsRPNFormula); procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring); overload; procedure WriteUTF8Text(ACell: PCell; AText: ansistring); overload; { Writing of cell attributes } procedure WriteBackgroundColor(ARow, ACol: Cardinal; AColor: TsColor); procedure WriteBorderColor(ARow, ACol: Cardinal; ABorder: TsCellBorder; AColor: TsColor); procedure WriteBorderLineStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder; ALineStyle: TsLineStyle); procedure WriteBorders(ARow, ACol: Cardinal; ABorders: TsCellBorders); procedure WriteBorderStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder; AStyle: TsCellBorderStyle); overload; procedure WriteBorderStyle(ARow, ACol: Cardinal; ABorder: TsCellBorder; ALineStyle: TsLineStyle; AColor: TsColor); overload; procedure WriteBorderStyles(ARow, ACol: Cardinal; const AStyles: TsCellBorderStyles); procedure WriteDecimals(ARow, ACol: Cardinal; ADecimals: byte); overload; procedure WriteDecimals(ACell: PCell; ADecimals: Byte); overload; function WriteFont(ARow, ACol: Cardinal; const AFontName: String; AFontSize: Single; AFontStyle: TsFontStyles; AFontColor: TsColor): Integer; overload; procedure WriteFont(ARow, ACol: Cardinal; AFontIndex: Integer); overload; function WriteFontColor(ARow, ACol: Cardinal; AFontColor: TsColor): Integer; function WriteFontName(ARow, ACol: Cardinal; AFontName: String): Integer; function WriteFontSize(ARow, ACol: Cardinal; ASize: Single): Integer; function WriteFontStyle(ARow, ACol: Cardinal; AStyle: TsFontStyles): Integer; procedure WriteHorAlignment(ARow, ACol: Cardinal; AValue: TsHorAlignment); procedure WriteNumberFormat(ARow, ACol: Cardinal; ANumberFormat: TsNumberFormat; const AFormatString: String = ''); overload; procedure WriteNumberFormat(ACell: PCell; ANumberFormat: TsNumberFormat; const AFormatString: String = ''); overload; procedure WriteTextRotation(ARow, ACol: Cardinal; ARotation: TsTextRotation); procedure WriteUsedFormatting(ARow, ACol: Cardinal; AUsedFormatting: TsUsedFormattingFields); procedure WriteVertAlignment(ARow, ACol: Cardinal; AValue: TsVertAlignment); procedure WriteWordwrap(ARow, ACol: Cardinal; AValue: boolean); { Data manipulation methods - For Cells } procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet); procedure CopyFormat(AFormat: PCell; AToRow, AToCol: Cardinal); overload; procedure CopyFormat(AFromCell, AToCell: PCell); overload; function FindCell(ARow, ACol: Cardinal): PCell; function GetCell(ARow, ACol: Cardinal): PCell; function GetCellCount: Cardinal; function GetFirstCell(): PCell; function GetNextCell(): PCell; function GetFirstCellOfRow(ARow: Cardinal): PCell; function GetLastCellOfRow(ARow: Cardinal): PCell; function GetLastColIndex: Cardinal; function GetLastColNumber: Cardinal; deprecated 'Use GetLastColIndex'; function GetLastRowIndex: Cardinal; function GetLastRowNumber: Cardinal; deprecated 'Use GetLastRowIndex'; { Data manipulation methods - For Rows and Cols } function CalcAutoRowHeight(ARow: Cardinal): Single; 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 GetRowHeight(ARow: Cardinal): Single; function GetCol(ACol: Cardinal): PCol; function GetColWidth(ACol: Cardinal): Single; procedure RemoveAllRows; procedure RemoveAllCols; procedure WriteRowInfo(ARow: Cardinal; AData: TRow); procedure WriteRowHeight(ARow: Cardinal; AHeight: Single); procedure WriteColInfo(ACol: Cardinal; AData: TCol); procedure WriteColWidth(ACol: Cardinal; AWidth: Single); { Properties } {@@ List of cells of the worksheet. Only cells with contents or with formatting are listed } property Cells: TAVLTree read FCells; {@@ List of all column records of the worksheet having a non-standard column width } property Cols: TIndexedAVLTree read FCols; {@@ FormatSettings for localization of some formatting strings } property FormatSettings: TFormatSettings read GetFormatSettings; {@@ List of all row records of the worksheet having a non-standard row height } property Rows: TIndexedAVLTree read FRows; {@@ Workbook to which the worksheet belongs } property Workbook: TsWorkbook read FWorkbook; // These are properties to interface to TsWorksheetGrid {@@ Parameters controlling visibility of grid lines and row/column headers, usage of frozen panes etc. } property Options: TsSheetOptions read FOptions write FOptions; {@@ 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; {@@ Event fired when cell contents or formatting changes } property OnChangeCell: TsCellEvent read FOnChangeCell write FOnChangeCell; {@@ Event fired when the font size in a cell changes } property OnChangeFont: TsCellEvent read FOnChangeFont write FOnChangeFont; end; { TsWorkbook } TsWorkbook = class private { Internal data } FWorksheets: TFPList; FEncoding: TsEncoding; FFormat: TsSpreadsheetFormat; FFontList: TFPList; FBuiltinFontCount: Integer; FPalette: array of TsColorValue; FReadFormulas: Boolean; FDefaultColWidth: Single; // in "characters". Excel uses the width of char "0" in 1st font FDefaultRowHeight: Single; // in "character heights", i.e. line count { Internal methods } procedure RemoveWorksheetsCallback(data, arg: pointer); public FormatSettings: TFormatSettings; { Base methods } constructor Create; destructor Destroy; override; class function GetFormatFromFileName(const AFileName: TFileName; out SheetType: TsSpreadsheetFormat): Boolean; function CreateSpreadReader(AFormat: TsSpreadsheetFormat): TsCustomSpreadReader; function CreateSpreadWriter(AFormat: TsSpreadsheetFormat): TsCustomSpreadWriter; procedure ReadFromFile(AFileName: string; AFormat: TsSpreadsheetFormat); overload; procedure ReadFromFile(AFileName: string); overload; procedure ReadFromFileIgnoringExtension(AFileName: string); procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat); procedure WriteToFile(const AFileName: string; const AFormat: TsSpreadsheetFormat; const AOverwriteExisting: Boolean = False); overload; procedure WriteToFile(const AFileName: String; const AOverwriteExisting: Boolean = False); overload; procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat); { Worksheet list handling methods } function AddWorksheet(AName: string): TsWorksheet; function GetFirstWorksheet: TsWorksheet; function GetWorksheetByIndex(AIndex: Cardinal): TsWorksheet; function GetWorksheetByName(AName: String): TsWorksheet; function GetWorksheetCount: Cardinal; procedure RemoveAllWorksheets; { Font handling } function AddFont(const AFontName: String; ASize: Single; AStyle: TsFontStyles; AColor: TsColor): Integer; overload; function AddFont(const AFont: TsFont): Integer; overload; procedure CopyFontList(ASource: TFPList); function FindFont(const AFontName: String; ASize: Single; AStyle: TsFontStyles; AColor: TsColor): Integer; function GetDefaultFont: TsFont; function GetDefaultFontSize: Single; function GetFont(AIndex: Integer): TsFont; function GetFontCount: Integer; procedure InitFonts; procedure RemoveAllFonts; procedure SetDefaultFont(const AFontName: String; ASize: Single); { Color handling } function AddColorToPalette(AColorValue: TsColorValue): TsColor; function FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): String; function GetColorName(AColorIndex: TsColor): string; function GetPaletteColor(AColorIndex: TsColor): TsColorValue; function GetPaletteColorAsHTMLStr(AColorIndex: TsColor): String; procedure SetPaletteColor(AColorIndex: TsColor; AColorValue: TsColorValue); function GetPaletteSize: Integer; procedure UseDefaultPalette; procedure UsePalette(APalette: PsPalette; APaletteCount: Word; ABigEndian: Boolean = false); {@@ The default column width given in "character units" (width of the character "0" in the default font) } property DefaultColWidth: Single read FDefaultColWidth; {@@ The default row height is given in "line count" (height of the default font } property DefaultRowHeight: Single read FDefaultRowHeight; {@@ This property is only used for formats which don't support unicode and support a single encoding for the whole document, like Excel 2 to 5 } property Encoding: TsEncoding read FEncoding write FEncoding; {@@ Identifies the file format which was detected when reading the file } property FileFormat: TsSpreadsheetFormat read FFormat; {@@ This property allows to turn off reading of rpn formulas; this is a precaution since formulas not correctly implemented by fpspreadsheet could crash the reading operation. } property ReadFormulas: Boolean read FReadFormulas write FReadFormulas; end; {@@ Contents of a number format record } TsNumFormatData = class public {@@ Excel refers to a number format by means of the format "index". } Index: Integer; {@@ OpenDocument refers to a number format by means of the format "name". } Name: String; {@@ Identifier of a built-in number format, see TsNumberFormat } NumFormat: TsNumberFormat; {@@ String of format codes, such as '#,##0.00', or 'hh:nn'. } FormatString: string; end; {@@ Specialized list for number format items } TsCustomNumFormatList = class(TFPList) private function GetItem(AIndex: Integer): TsNumFormatData; procedure SetItem(AIndex: Integer; AValue: TsNumFormatData); protected {@@ Workbook from which the number formats are collected in the list. It is mainly needed to get access to the FormatSettings for easy localization of some formatting strings. } FWorkbook: TsWorkbook; {@@ Identifies the first number format item that is written to the file. Items having a smaller index are not written. } FFirstFormatIndexInFile: Integer; {@@ Identifies the index of the next Excel number format item to be written. Needed for auto-creating of the user-defined Excel number format indexes } FNextFormatIndex: Integer; procedure AddBuiltinFormats; virtual; procedure RemoveFormat(AIndex: Integer); public constructor Create(AWorkbook: TsWorkbook); destructor Destroy; override; function AddFormat(AFormatCell: PCell): Integer; overload; function AddFormat(AFormatIndex: Integer; AFormatName, AFormatString: String; ANumFormat: TsNumberFormat): Integer; overload; function AddFormat(AFormatIndex: Integer; AFormatString: String; ANumFormat: TsNumberFormat): Integer; overload; function AddFormat(AFormatName, AFormatString: String; ANumFormat: TsNumberFormat): Integer; overload; function AddFormat(AFormatString: String; ANumFormat: TsNumberFormat): Integer; overload; procedure AnalyzeAndAdd(AFormatIndex: Integer; AFormatString: String); procedure Clear; procedure ConvertAfterReading(AFormatIndex: Integer; var AFormatString: String; var ANumFormat: TsNumberFormat); virtual; procedure ConvertBeforeWriting(var AFormatString: String; var ANumFormat: TsNumberFormat); virtual; procedure Delete(AIndex: Integer); function Find(ANumFormat: TsNumberFormat; AFormatString: String): Integer; overload; function Find(AFormatString: String): Integer; overload; function FindByIndex(AFormatIndex: Integer): Integer; function FindByName(AFormatName: String): Integer; function FindFormatOf(AFormatCell: PCell): integer; virtual; function FormatStringForWriting(AIndex: Integer): String; virtual; procedure Sort; {@@ Workbook from which the number formats are collected in the list. It is mainly needed to get access to the FormatSettings for easy localization of some formatting strings. } property Workbook: TsWorkbook read FWorkbook; {@@ Identifies the first number format item that is written to the file. Items having a smaller index are not written. } property FirstFormatIndexInFile: Integer read FFirstFormatIndexInFile; {@@ Number format items contained in the list } property Items[AIndex: Integer]: TsNumFormatData read GetItem write SetItem; default; end; {@@ TsSpreadReader class reference type } TsSpreadReaderClass = class of TsCustomSpreadReader; { TsCustomSpreadReader } TsCustomSpreadReader = class protected FWorkbook: TsWorkbook; FWorksheet: TsWorksheet; FNumFormatList: TsCustomNumFormatList; procedure CreateNumFormatList; virtual; { Record reading methods } procedure ReadBlank(AStream: TStream); virtual; abstract; procedure ReadFormula(AStream: TStream); virtual; abstract; procedure ReadLabel(AStream: TStream); virtual; abstract; procedure ReadNumber(AStream: TStream); virtual; abstract; public constructor Create(AWorkbook: TsWorkbook); virtual; // To allow descendents to override it destructor Destroy; override; { General writing methods } procedure ReadFromFile(AFileName: string; AData: TsWorkbook); virtual; procedure ReadFromStream(AStream: TStream; AData: TsWorkbook); virtual; procedure ReadFromStrings(AStrings: TStrings; AData: TsWorkbook); virtual; property Workbook: TsWorkbook read FWorkbook; property NumFormatList: TsCustomNumFormatList read FNumFormatList; end; {@@ TsSpreadWriter class reference type } TsSpreadWriterClass = class of TsCustomSpreadWriter; TCellsCallback = procedure (ACell: PCell; AStream: TStream) of object; { TsCustomSpreadWriter } TsCustomSpreadWriter = class private FWorkbook: TsWorkbook; protected FNumFormatList: TsCustomNumFormatList; { Helper routines } procedure AddDefaultFormats(); virtual; procedure CreateNumFormatList; virtual; function ExpandFormula(AFormula: TsFormula): TsExpandedFormula; function FindFormattingInList(AFormat: PCell): Integer; procedure FixFormat(ACell: PCell); virtual; procedure ListAllFormattingStylesCallback(ACell: PCell; AStream: TStream); procedure ListAllFormattingStyles; virtual; procedure ListAllNumFormatsCallback(ACell: PCell; AStream: TStream); procedure ListAllNumFormats; virtual; { Helpers for writing } procedure WriteCellCallback(ACell: PCell; AStream: TStream); procedure WriteCellsToStream(AStream: TStream; ACells: TAVLTree); { Record writing methods } procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; ACell: PCell); virtual; abstract; procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell); virtual; abstract; procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsFormula; ACell: PCell); virtual; procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); virtual; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); virtual; abstract; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); virtual; abstract; public {@@ An array with cells which are models for the used styles In this array the Row property holds the Index to the corresponding XF field } FFormattingStyles: array of TCell; NextXFIndex: Integer; // Indicates which should be the next XF (Style) Index when filling the styles list constructor Create(AWorkbook: TsWorkbook); virtual; // To allow descendents to override it destructor Destroy; override; { General writing methods } procedure IterateThroughCells(AStream: TStream; ACells: TAVLTree; ACallback: TCellsCallback); procedure WriteToFile(const AFileName: string; const AOverwriteExisting: Boolean = False); virtual; procedure WriteToStream(AStream: TStream); virtual; procedure WriteToStrings(AStrings: TStrings); virtual; property Workbook: TsWorkbook read FWorkbook; property NumFormatList: TsCustomNumFormatList read FNumFormatList; end; {@@ List of registered formats } TsSpreadFormatData = record ReaderClass: TsSpreadReaderClass; WriterClass: TsSpreadWriterClass; Format: TsSpreadsheetFormat; end; {@@ Helper for simplification of RPN formula creation } PRPNItem = ^TRPNItem; TRPNItem = record FE: TsFormulaElement; Next: PRPNItem; end; {@@ Simple creation an RPNFormula array to be used in fpspreadsheet. For each formula element, use one of the RPNxxxx functions implemented here. They are designed to be nested into each other. Terminate the chain by using nil. Example: The RPN formula for the string expression "$A1+2" can be created as follows:
var f: TsRPNFormula; f := CreateRPNFormula( RPNCellValue('A1', RPNNumber(2, RPNFunc(fekAdd, nil))));} function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula; procedure DestroyRPNFormula(AItem: PRPNItem); function RPNBool(AValue: Boolean; ANext: PRPNItem): PRPNItem; function RPNCellValue(ACellAddress: String; ANext: PRPNItem): PRPNItem; overload; function RPNCellValue(ARow, ACol: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; function RPNCellRef(ACellAddress: String; ANext: PRPNItem): PRPNItem; overload; function RPNCellRef(ARow, ACol: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; function RPNCellRange(ACellRangeAddress: String; ANext: PRPNItem): PRPNItem; overload; function RPNCellRange(ARow, ACol, ARow2, ACol2: Integer; AFlags: TsRelFlags; ANext: PRPNItem): PRPNItem; overload; function RPNErr(AErrCode: Byte; ANext: PRPNItem): PRPNItem; function RPNInteger(AValue: Word; ANext: PRPNItem): PRPNItem; function RPNMissingArg(ANext: PRPNItem): PRPNItem; function RPNNumber(AValue: Double; ANext: PRPNItem): PRPNItem; function RPNParenthesis(ANext: PRPNItem): PRPNItem; function RPNString(AValue: String; ANext: PRPNItem): PRPNItem; function RPNFunc(AToken: TFEKind; ANext: PRPNItem): PRPNItem; overload; function RPNFunc(AToken: TFEKind; ANumParams: Byte; ANext: PRPNItem): PRPNItem; overload; function FixedParamCount(AElementKind: TFEKind): Boolean; var GsSpreadFormats: array of TsSpreadFormatData; procedure RegisterSpreadFormat( AReaderClass: TsSpreadReaderClass; AWriterClass: TsSpreadWriterClass; AFormat: TsSpreadsheetFormat); procedure CopyCellFormat(AFromCell, AToCell: PCell); function GetFileFormatName(AFormat: TsSpreadsheetFormat): String; procedure MakeLEPalette(APalette: PsPalette; APaletteSize: Integer); implementation uses Math, StrUtils, TypInfo, fpsUtils, fpsNumFormatParser; { Translatable strings } resourcestring lpUnsupportedReadFormat = 'Tried to read a spreadsheet using an unsupported format'; lpUnsupportedWriteFormat = 'Tried to write a spreadsheet using an unsupported format'; lpNoValidSpreadsheetFile = '"%s" is not a valid spreadsheet file'; lpUnknownSpreadsheetFormat = 'unknown format'; lpInvalidFontIndex = 'Invalid font index'; lpInvalidNumberFormat = 'Trying to use an incompatible number format.'; lpInvalidDateTimeFormat = 'Trying to use an incompatible date/time format.'; lpNoValidNumberFormatString = 'No valid number format string.'; lpNoValidDateTimeFormatString = 'No valid date/time format string.'; lpNoValidCellAddress = '"%s" is not a valid cell address.'; lpNoValidCellRangeAddress = '"%s" is not a valid cell range address.'; lpIllegalNumberFormat = 'Illegal number format.'; lpSpecifyNumberOfParams = 'Specify number of parameters for function %s'; lpIncorrectParamCount = 'Funtion %s requires at least %d and at most %d parameters.'; lpTRUE = 'TRUE'; lpFALSE = 'FALSE'; lpErrEmptyIntersection = '#NULL!'; lpErrDivideByZero = '#DIV/0!'; lpErrWrongType = '#VALUE!'; lpErrIllegalRef = '#REF!'; lpErrWrongName = '#NAME?'; lpErrOverflow = '#NUM!'; lpErrArgError = '#N/A'; lpErrFormulaNotSupported = '
var f: TsRPNFormula; begin f := CreateRPNFormula( RPNCellValue('$A1', RPNNumber(2, RPNFunc(fekAdd, nil))));} function CreateRPNFormula(AItem: PRPNItem): TsRPNFormula; var item: PRPNItem; nextitem: PRPNItem; n: Integer; begin // Determine count of RPN elements n := 0; item := AItem; while item <> nil do begin inc(n); item := item^.Next; end; // Set array length of TsRPNFormula result SetLength(Result, n); // Copy FormulaElements to result and free temporary RPNItems item := AItem; n := 0; while item <> nil do begin nextitem := item^.Next; Result[n] := item^.FE; inc(n); DisposeRPNItem(item); item := nextitem; end; end; {@@ Destroys the RPN formula starting with the given RPN item. @param AItem Pointer to the first RPN items representing the formula. Each item contains a pointer to the next item in the list. The list is terminated by nil. } procedure DestroyRPNFormula(AItem: PRPNItem); var nextitem: PRPNItem; begin while AItem <> nil do begin nextitem := AItem^.Next; DisposeRPNItem(AItem); AItem := nextitem; end; end; initialization MakeLEPalette(@DEFAULT_PALETTE, Length(DEFAULT_PALETTE)); finalization SetLength(GsSpreadFormats, 0); end. { Strategy for handling of number formats: Problem: For number formats, fpspreadsheet uses a syntax which is slightly different from the syntax that Excel uses in the xls files. Moreover, the file syntax can be different from file type to file type (biff2, for example, allows only a few predefined formats, while the number of allowed formats is unlimited (?) for biff8. Number format handling in fpspreadsheet is implemented with the following concept in mind: - Formats written into TsWorksheet cells always follow the fpspreadsheet syntax. - For writing, the writer creates a TsNumFormatList which stores all formats in file syntax. - The built-in formats of the file types are coded in the fpc syntax. - The method "ConvertBeforeWriting" converts the cell formats from the fpspreadsheet to the file syntax. - For reading, the reader creates another TsNumFormatList. - The built-in formats of the file types are coded again in fpc syntax. - After reading, the formats are converted to fpc syntax by means of "ConvertAfterReading". - Format conversion is done internally by means of the TsNumFormatParser. }