PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/BankConvert/3rdparty/fpspreadsheet.pas

https://bitbucket.org/reiniero/smalltools
Pascal | 1736 lines | 1045 code | 220 blank | 471 comment | 75 complexity | 5ad0286ad2c72bc06a7922ed3a195173 MD5 | raw file
  1. {
  2. fpspreadsheet.pas
  3. Writes an spreadsheet document
  4. AUTHORS: Felipe Monteiro de Carvalho
  5. }
  6. unit fpspreadsheet;
  7. {$ifdef fpc}
  8. {$mode delphi}
  9. {$endif}
  10. interface
  11. uses
  12. Classes, SysUtils, fpimage, AVL_Tree, avglvltree, lconvencoding;
  13. type
  14. TsSpreadsheetFormat = (sfExcel2, sfExcel3, sfExcel4, sfExcel5, sfExcel8,
  15. sfOOXML, sfOpenDocument, sfCSV, sfWikiTable_Pipes, sfWikiTable_WikiMedia);
  16. const
  17. { Default extensions }
  18. STR_EXCEL_EXTENSION = '.xls';
  19. STR_OOXML_EXCEL_EXTENSION = '.xlsx';
  20. STR_OPENDOCUMENT_CALC_EXTENSION = '.ods';
  21. STR_COMMA_SEPARATED_EXTENSION = '.csv';
  22. STR_WIKITABLE_PIPES = '.wikitable_pipes';
  23. STR_WIKITABLE_WIKIMEDIA = '.wikitable_wikimedia';
  24. type
  25. {@@ Possible encodings for a non-unicode encoded text }
  26. TsEncoding = (
  27. seLatin1,
  28. seLatin2,
  29. seCyrillic,
  30. seGreek,
  31. seTurkish,
  32. seHebrew,
  33. seArabic
  34. );
  35. {@@ Describes a formula
  36. Supported syntax:
  37. =A1+B1+C1/D2... - Array with simple mathematical operations
  38. =SUM(A1:D1) - SUM operation in a interval
  39. }
  40. TsFormula = record
  41. FormulaStr: string;
  42. DoubleValue: double;
  43. end;
  44. {@@ Expanded formula. Used by backend modules. Provides more information then the text only }
  45. TFEKind = (
  46. { Basic operands }
  47. fekCell, fekCellRange, fekNum,
  48. { Basic operations }
  49. fekAdd, fekSub, fekDiv, fekMul,
  50. { Built-in/Worksheet Functions}
  51. fekABS, fekDATE, fekROUND, fekTIME,
  52. { Other operations }
  53. fekOpSUM
  54. );
  55. TsFormulaElement = record
  56. ElementKind: TFEKind;
  57. Row, Row2: Word; // zero-based
  58. Col, Col2: Byte; // zero-based
  59. Param1, Param2: Word; // Extra parameters
  60. DoubleValue: double;
  61. end;
  62. TsExpandedFormula = array of TsFormulaElement;
  63. {@@ RPN formula. Similar to the expanded formula, but in RPN notation.
  64. Simplifies the task of format writers which need RPN }
  65. TsRPNFormula = array of TsFormulaElement;
  66. {@@ Describes the type of content of a cell on a TsWorksheet }
  67. TCellContentType = (cctEmpty, cctFormula, cctRPNFormula, cctNumber,
  68. cctUTF8String, cctDateTime);
  69. {@@ List of possible formatting fields }
  70. TsUsedFormattingField = (uffTextRotation, uffBold, uffBorder, uffBackgroundColor,
  71. uffNumberFormat, uffWordWrap);
  72. {@@ Describes which formatting fields are active }
  73. TsUsedFormattingFields = set of TsUsedFormattingField;
  74. {@@ Number/cell formatting. Only uses a subset of the default formats,
  75. enough to be able to read/write date values.
  76. }
  77. TsNumberFormat = (nfGeneral, nfShortDate, nfShortDateTime);
  78. {@@ Text rotation formatting. The text is rotated relative to the standard
  79. orientation, which is from left to right horizontal: --->
  80. ABC
  81. So 90 degrees clockwise means that the text will be:
  82. | A
  83. | B
  84. \|/ C
  85. And 90 degree counter clockwise will be:
  86. /|\ C
  87. | B
  88. | A
  89. }
  90. TsTextRotation = (trHorizontal, rt90DegreeClockwiseRotation,
  91. rt90DegreeCounterClockwiseRotation);
  92. {@@ Indicates the border for a cell }
  93. TsCellBorder = (cbNorth, cbWest, cbEast, cbSouth);
  94. {@@ Indicates the border for a cell }
  95. TsCellBorders = set of TsCellBorder;
  96. {@@ Colors in FPSpreadsheet as given by a palette to be compatible with Excel }
  97. TsColor = ( // R G B color value:
  98. scBlack , // 000000H
  99. scWhite, // FFFFFFH
  100. scRed, // FF0000H
  101. scGREEN, // 00FF00H
  102. scBLUE, // 0000FFH
  103. scYELLOW, // FFFF00H
  104. scMAGENTA, // FF00FFH
  105. scCYAN, // 00FFFFH
  106. scDarkRed, // 800000H
  107. scDarkGreen,// 008000H
  108. scDarkBlue, // 000080H
  109. scOLIVE, // 808000H
  110. scPURPLE, // 800080H
  111. scTEAL, // 008080H
  112. scSilver, // C0C0C0H
  113. scGrey, // 808080H
  114. //
  115. scGrey10pct,// E6E6E6H
  116. scGrey20pct,// CCCCCCH
  117. scOrange, // ffa500H
  118. scDarkBrown,// a0522dH
  119. scBrown, // cd853fH
  120. scBeige, // f5f5dcH
  121. scWheat, // f5deb3H
  122. //
  123. scRGBCOLOR // Defined via TFPColor
  124. );
  125. {@@ Cell structure for TsWorksheet
  126. Never suppose that all *Value fields are valid,
  127. only one of the ContentTypes is valid. For other fields
  128. use TWorksheet.ReadAsUTF8Text and similar methods
  129. @see TWorksheet.ReadAsUTF8Text
  130. }
  131. TCell = record
  132. Col: Byte; // zero-based
  133. Row: Cardinal; // zero-based
  134. ContentType: TCellContentType;
  135. { Possible values for the cells }
  136. FormulaValue: TsFormula;
  137. RPNFormulaValue: TsRPNFormula;
  138. NumberValue: double;
  139. UTF8StringValue: ansistring;
  140. DateTimeValue: TDateTime;
  141. { Formatting fields }
  142. UsedFormattingFields: TsUsedFormattingFields;
  143. TextRotation: TsTextRotation;
  144. Border: TsCellBorders;
  145. BackgroundColor: TsColor;
  146. NumberFormat: TsNumberFormat;
  147. RGBBackgroundColor: TFPColor; // only valid if BackgroundColor=scRGBCOLOR
  148. end;
  149. PCell = ^TCell;
  150. TRow = record
  151. Row: Cardinal;
  152. Height: Single; // in milimeters
  153. end;
  154. PRow = ^TRow;
  155. TCol = record
  156. Col: Byte;
  157. Width: Single; // in milimeters
  158. end;
  159. PCol = ^TCol;
  160. type
  161. TsCustomSpreadReader = class;
  162. TsCustomSpreadWriter = class;
  163. { TsWorksheet }
  164. TsWorksheet = class
  165. private
  166. FCells: TAvlTree; // Items are TCell
  167. FCurrentNode: TAVLTreeNode; // For GetFirstCell and GetNextCell
  168. FRows, FCols: TIndexedAVLTree; // This lists contain only rows or cols with styles different from the standard
  169. procedure RemoveCallback(data, arg: pointer);
  170. public
  171. Name: string;
  172. { Base methods }
  173. constructor Create;
  174. destructor Destroy; override;
  175. { Utils }
  176. class function CellPosToText(ARow, ACol: Cardinal): string;
  177. { Data manipulation methods - For Cells }
  178. procedure CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal; AFromWorksheet: TsWorksheet);
  179. function FindCell(ARow, ACol: Cardinal): PCell;
  180. function GetCell(ARow, ACol: Cardinal): PCell;
  181. function GetCellCount: Cardinal;
  182. function GetFirstCell(): PCell;
  183. function GetNextCell(): PCell;
  184. function GetLastColNumber: Cardinal;
  185. function GetLastRowNumber: Cardinal;
  186. function ReadAsUTF8Text(ARow, ACol: Cardinal): ansistring;
  187. function ReadAsNumber(ARow, ACol: Cardinal): Double;
  188. function ReadAsDateTime(ARow, ACol: Cardinal; out AResult: TDateTime): Boolean;
  189. function ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields;
  190. function ReadBackgroundColor(ARow, ACol: Cardinal): TsColor;
  191. procedure RemoveAllCells;
  192. procedure WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring);
  193. procedure WriteNumber(ARow, ACol: Cardinal; ANumber: double);
  194. procedure WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime);
  195. procedure WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula);
  196. procedure WriteNumberFormat(ARow, ACol: Cardinal; ANumberFormat: TsNumberFormat);
  197. procedure WriteRPNFormula(ARow, ACol: Cardinal; AFormula: TsRPNFormula);
  198. procedure WriteTextRotation(ARow, ACol: Cardinal; ARotation: TsTextRotation);
  199. procedure WriteUsedFormatting(ARow, ACol: Cardinal; AUsedFormatting: TsUsedFormattingFields);
  200. procedure WriteBackgroundColor(ARow, ACol: Cardinal; AColor: TsColor);
  201. { Data manipulation methods - For Rows and Cols }
  202. function FindRow(ARow: Cardinal): PRow;
  203. function FindCol(ACol: Cardinal): PCol;
  204. function GetRow(ARow: Cardinal): PRow;
  205. function GetCol(ACol: Cardinal): PCol;
  206. procedure RemoveAllRows;
  207. procedure RemoveAllCols;
  208. procedure WriteRowInfo(ARow: Cardinal; AData: TRow);
  209. procedure WriteColInfo(ACol: Cardinal; AData: TCol);
  210. { Properties }
  211. property Cells: TAVLTree read FCells;
  212. end;
  213. { TsWorkbook }
  214. TsWorkbook = class
  215. private
  216. { Internal data }
  217. FWorksheets: TFPList;
  218. FEncoding: TsEncoding;
  219. { Internal methods }
  220. procedure RemoveCallback(data, arg: pointer);
  221. public
  222. { Base methods }
  223. constructor Create;
  224. destructor Destroy; override;
  225. class function GetFormatFromFileName(const AFileName: TFileName; var SheetType: TsSpreadsheetFormat): Boolean;
  226. function CreateSpreadReader(AFormat: TsSpreadsheetFormat): TsCustomSpreadReader;
  227. function CreateSpreadWriter(AFormat: TsSpreadsheetFormat): TsCustomSpreadWriter;
  228. procedure ReadFromFile(AFileName: string; AFormat: TsSpreadsheetFormat); overload;
  229. procedure ReadFromFile(AFileName: string); overload;
  230. procedure ReadFromFileIgnoringExtension(AFileName: string);
  231. procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
  232. procedure WriteToFile(const AFileName: string;
  233. const AFormat: TsSpreadsheetFormat;
  234. const AOverwriteExisting: Boolean = False); overload;
  235. procedure WriteToFile(const AFileName: String; const AOverwriteExisting: Boolean = False); overload;
  236. procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
  237. { Worksheet list handling methods }
  238. function AddWorksheet(AName: string): TsWorksheet;
  239. function GetFirstWorksheet: TsWorksheet;
  240. function GetWorksheetByIndex(AIndex: Cardinal): TsWorksheet;
  241. function GetWorksheetByName(AName: String): TsWorksheet;
  242. function GetWorksheetCount: Cardinal;
  243. procedure RemoveAllWorksheets;
  244. {@@ This property is only used for formats which don't support unicode
  245. and support a single encoding for the whole document, like Excel 2 to 5 }
  246. property Encoding: TsEncoding read FEncoding write FEncoding;
  247. end;
  248. {@@ TsSpreadReader class reference type }
  249. TsSpreadReaderClass = class of TsCustomSpreadReader;
  250. { TsCustomSpreadReader }
  251. TsCustomSpreadReader = class
  252. protected
  253. FWorkbook: TsWorkbook;
  254. FCurrentWorksheet: TsWorksheet;
  255. public
  256. constructor Create; virtual; // To allow descendents to override it
  257. { General writing methods }
  258. procedure ReadFromFile(AFileName: string; AData: TsWorkbook); virtual;
  259. procedure ReadFromStream(AStream: TStream; AData: TsWorkbook); virtual;
  260. procedure ReadFromStrings(AStrings: TStrings; AData: TsWorkbook); virtual;
  261. { Record reading methods }
  262. procedure ReadFormula(AStream: TStream); virtual; abstract;
  263. procedure ReadLabel(AStream: TStream); virtual; abstract;
  264. procedure ReadNumber(AStream: TStream); virtual; abstract;
  265. end;
  266. {@@ TsSpreadWriter class reference type }
  267. TsSpreadWriterClass = class of TsCustomSpreadWriter;
  268. TCellsCallback = procedure (ACell: PCell; AStream: TStream) of object;
  269. { TsCustomSpreadWriter }
  270. TsCustomSpreadWriter = class
  271. public
  272. {@@
  273. An array with cells which are models for the used styles
  274. In this array the Row property holds the Index to the corresponding XF field
  275. }
  276. FFormattingStyles: array of TCell;
  277. NextXFIndex: Integer; // Indicates which should be the next XF (Style) Index when filling the styles list
  278. constructor Create; virtual; // To allow descendents to override it
  279. { Helper routines }
  280. function FindFormattingInList(AFormat: PCell): Integer;
  281. procedure AddDefaultFormats(); virtual;
  282. procedure ListAllFormattingStylesCallback(ACell: PCell; AStream: TStream);
  283. procedure ListAllFormattingStyles(AData: TsWorkbook);
  284. function ExpandFormula(AFormula: TsFormula): TsExpandedFormula;
  285. function FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): string;
  286. { General writing methods }
  287. procedure WriteCellCallback(ACell: PCell; AStream: TStream);
  288. procedure WriteCellsToStream(AStream: TStream; ACells: TAVLTree);
  289. procedure IterateThroughCells(AStream: TStream; ACells: TAVLTree; ACallback: TCellsCallback);
  290. procedure WriteToFile(const AFileName: string; AData: TsWorkbook;
  291. const AOverwriteExisting: Boolean = False); virtual;
  292. procedure WriteToStream(AStream: TStream; AData: TsWorkbook); virtual;
  293. procedure WriteToStrings(AStrings: TStrings; AData: TsWorkbook); virtual;
  294. { Record writing methods }
  295. procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal; const AValue: TDateTime; ACell: PCell); virtual; abstract;
  296. procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsFormula; ACell: PCell); virtual;
  297. procedure WriteRPNFormula(AStream: TStream; const ARow, ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell); virtual;
  298. procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; const AValue: string; ACell: PCell); virtual; abstract;
  299. procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; const AValue: double; ACell: PCell); virtual; abstract;
  300. end;
  301. {@@ List of registered formats }
  302. TsSpreadFormatData = record
  303. ReaderClass: TsSpreadReaderClass;
  304. WriterClass: TsSpreadWriterClass;
  305. Format: TsSpreadsheetFormat;
  306. end;
  307. var
  308. GsSpreadFormats: array of TsSpreadFormatData;
  309. procedure RegisterSpreadFormat(
  310. AReaderClass: TsSpreadReaderClass;
  311. AWriterClass: TsSpreadWriterClass;
  312. AFormat: TsSpreadsheetFormat);
  313. implementation
  314. uses
  315. Math;
  316. var
  317. { Translatable strings }
  318. lpUnsupportedReadFormat, lpUnsupportedWriteFormat: string;
  319. {@@
  320. Registers a new reader/writer pair for a format
  321. }
  322. procedure RegisterSpreadFormat(
  323. AReaderClass: TsSpreadReaderClass;
  324. AWriterClass: TsSpreadWriterClass;
  325. AFormat: TsSpreadsheetFormat);
  326. var
  327. len: Integer;
  328. begin
  329. len := Length(GsSpreadFormats);
  330. SetLength(GsSpreadFormats, len + 1);
  331. GsSpreadFormats[len].ReaderClass := AReaderClass;
  332. GsSpreadFormats[len].WriterClass := AWriterClass;
  333. GsSpreadFormats[len].Format := AFormat;
  334. end;
  335. { TsWorksheet }
  336. {@@
  337. Helper method for clearing the records in a spreadsheet.
  338. }
  339. procedure TsWorksheet.RemoveCallback(data, arg: pointer);
  340. begin
  341. { The UTF8STring must be manually reseted to nil content, because
  342. FreeMem only frees the record mem, without checking its content }
  343. PCell(data).UTF8StringValue:='';
  344. FreeMem(data);
  345. end;
  346. function CompareCells(Item1, Item2: Pointer): Integer;
  347. begin
  348. result := PCell(Item1).Row - PCell(Item2).Row;
  349. if Result = 0 then
  350. Result := PCell(Item1).Col - PCell(Item2).Col;
  351. end;
  352. function CompareRows(Item1, Item2: Pointer): Integer;
  353. begin
  354. result := PRow(Item1).Row - PRow(Item2).Row;
  355. end;
  356. function CompareCols(Item1, Item2: Pointer): Integer;
  357. begin
  358. result := PCol(Item1).Col - PCol(Item2).Col;
  359. end;
  360. {@@
  361. Constructor.
  362. }
  363. constructor TsWorksheet.Create;
  364. begin
  365. inherited Create;
  366. FCells := TAVLTree.Create(@CompareCells);
  367. FRows := TIndexedAVLTree.Create(@CompareRows);
  368. FCols := TIndexedAVLTree.Create(@CompareCols);
  369. end;
  370. {@@
  371. Destructor.
  372. }
  373. destructor TsWorksheet.Destroy;
  374. begin
  375. RemoveAllCells;
  376. RemoveAllRows;
  377. RemoveAllCols;
  378. FCells.Free;
  379. FRows.Free;
  380. FCols.Free;
  381. inherited Destroy;
  382. end;
  383. {@@ Converts a FPSpreadsheet cell position, which is Row, Col in numbers
  384. and zero based, to a textual representation which is [Col][Row],
  385. being that the Col is in letters and the row is in 1-based numbers }
  386. class function TsWorksheet.CellPosToText(ARow, ACol: Cardinal): string;
  387. var
  388. lStr: string;
  389. begin
  390. lStr := '';
  391. if ACol < 26 then lStr := Char(ACol+65);
  392. Result := Format('%s%d', [lStr, ARow+1]);
  393. end;
  394. procedure TsWorksheet.CopyCell(AFromRow, AFromCol, AToRow, AToCol: Cardinal;
  395. AFromWorksheet: TsWorksheet);
  396. var
  397. lCurStr: String;
  398. lCurUsedFormatting: TsUsedFormattingFields;
  399. lCurColor: TsColor;
  400. begin
  401. lCurStr := AFromWorksheet.ReadAsUTF8Text(AFromRow, AFromCol);
  402. lCurUsedFormatting := AFromWorksheet.ReadUsedFormatting(AFromRow, AFromCol);
  403. lCurColor := AFromWorksheet.ReadBackgroundColor(AFromRow, AFromCol);
  404. WriteUTF8Text(AToRow, AToCol, lCurStr);
  405. WriteUsedFormatting(AToRow, AToCol, lCurUsedFormatting);
  406. if uffBackgroundColor in lCurUsedFormatting then
  407. begin
  408. WriteBackgroundColor(AToRow, AToCol, lCurColor);
  409. end;
  410. end;
  411. {@@
  412. Tries to locate a Cell in the list of already
  413. written Cells
  414. @param ARow The row of the cell
  415. @param ACol The column of the cell
  416. @return Nil if no existing cell was found,
  417. otherwise a pointer to the desired Cell
  418. @see TCell
  419. }
  420. function TsWorksheet.FindCell(ARow, ACol: Cardinal): PCell;
  421. var
  422. LCell: TCell;
  423. AVLNode: TAVLTreeNode;
  424. begin
  425. Result := nil;
  426. LCell.Row := ARow;
  427. LCell.Col := ACol;
  428. AVLNode := FCells.Find(@LCell);
  429. if Assigned(AVLNode) then
  430. result := PCell(AVLNode.Data);
  431. end;
  432. {@@
  433. Obtains an allocated cell at the desired location.
  434. If the Cell already exists, a pointer to it will
  435. be returned.
  436. If not, then new memory for the cell will be allocated,
  437. a pointer to it will be returned and it will be added
  438. to the list of Cells.
  439. @param ARow The row of the cell
  440. @param ACol The column of the cell
  441. @return A pointer to the Cell on the desired location.
  442. @see TCell
  443. }
  444. function TsWorksheet.GetCell(ARow, ACol: Cardinal): PCell;
  445. begin
  446. Result := FindCell(ARow, ACol);
  447. if (Result = nil) then
  448. begin
  449. Result := GetMem(SizeOf(TCell));
  450. FillChar(Result^, SizeOf(TCell), #0);
  451. Result^.Row := ARow;
  452. Result^.Col := ACol;
  453. Cells.Add(Result);
  454. end;
  455. end;
  456. {@@
  457. Returns the number of cells in the worksheet with contents.
  458. This routine is used together with GetFirstCell and GetNextCell
  459. to iterate througth all cells in a worksheet efficiently.
  460. @return The number of cells with contents in the worksheet
  461. @see TCell
  462. @see GetFirstCell
  463. @see GetNextCell
  464. }
  465. function TsWorksheet.GetCellCount: Cardinal;
  466. begin
  467. Result := FCells.Count;
  468. end;
  469. {@@
  470. Returns the first Cell.
  471. Use together with GetCellCount and GetNextCell
  472. to iterate througth all cells in a worksheet efficiently.
  473. @return The first cell if any exists, nil otherwise
  474. @see TCell
  475. @see GetCellCount
  476. @see GetNextCell
  477. }
  478. function TsWorksheet.GetFirstCell(): PCell;
  479. begin
  480. FCurrentNode := FCells.FindLowest();
  481. if FCurrentNode <> nil then
  482. Result := PCell(FCurrentNode.Data)
  483. else Result := nil;
  484. end;
  485. {@@
  486. Returns the next Cell.
  487. Should always be used either after GetFirstCell or
  488. after GetNextCell.
  489. Use together with GetCellCount and GetFirstCell
  490. to iterate througth all cells in a worksheet efficiently.
  491. @return The first cell if any exists, nil otherwise
  492. @see TCell
  493. @see GetCellCount
  494. @see GetFirstCell
  495. }
  496. function TsWorksheet.GetNextCell(): PCell;
  497. begin
  498. FCurrentNode := FCells.FindSuccessor(FCurrentNode);
  499. if FCurrentNode <> nil then
  500. Result := PCell(FCurrentNode.Data)
  501. else Result := nil;
  502. end;
  503. {@@
  504. Returns the 0-based number of the last column with a cell with contents.
  505. If no cells have contents, zero will be returned, which is also a valid value.
  506. Use GetCellCount to verify if there is at least one cell with contents in the
  507. worksheet.
  508. @see GetCellCount
  509. }
  510. function TsWorksheet.GetLastColNumber: Cardinal;
  511. var
  512. AVLNode: TAVLTreeNode;
  513. begin
  514. Result := 0;
  515. // Traverse the tree from lowest to highest.
  516. // Since tree primary sort order is on Row
  517. // highest Col could exist anywhere.
  518. AVLNode := FCells.FindLowest;
  519. While Assigned(AVLNode) do
  520. begin
  521. Result := Math.Max(Result, PCell(AVLNode.Data)^.Col);
  522. AVLNode := FCells.FindSuccessor(AVLNode);
  523. end;
  524. end;
  525. {@@
  526. Returns the 0-based number of the last row with a cell with contents.
  527. If no cells have contents, zero will be returned, which is also a valid value.
  528. Use GetCellCount to verify if there is at least one cell with contents in the
  529. worksheet.
  530. @see GetCellCount
  531. }
  532. function TsWorksheet.GetLastRowNumber: Cardinal;
  533. var
  534. AVLNode: TAVLTreeNode;
  535. begin
  536. Result := 0;
  537. AVLNode := FCells.FindHighest;
  538. if Assigned(AVLNode) then
  539. Result := PCell(AVLNode.Data).Row;
  540. end;
  541. {@@
  542. Reads the contents of a cell and returns an user readable text
  543. representing the contents of the cell.
  544. The resulting ansistring is UTF-8 encoded.
  545. @param ARow The row of the cell
  546. @param ACol The column of the cell
  547. @return The text representation of the cell
  548. }
  549. function TsWorksheet.ReadAsUTF8Text(ARow, ACol: Cardinal): ansistring;
  550. var
  551. ACell: PCell;
  552. function FloatToStrNoNaN(const Value: Double): ansistring;
  553. begin
  554. if IsNan(Value) then Result:='' else Result:=FloatToStr(Value);
  555. end;
  556. begin
  557. ACell := FindCell(ARow, ACol);
  558. if ACell = nil then
  559. begin
  560. Result := '';
  561. Exit;
  562. end;
  563. case ACell^.ContentType of
  564. //cctFormula
  565. cctNumber: Result := FloatToStrNoNaN(ACell^.NumberValue);
  566. cctUTF8String: Result := ACell^.UTF8StringValue;
  567. cctDateTime: Result := SysUtils.DateToStr(ACell^.DateTimeValue);
  568. else
  569. Result := '';
  570. end;
  571. end;
  572. function TsWorksheet.ReadAsNumber(ARow, ACol: Cardinal): Double;
  573. var
  574. ACell: PCell;
  575. Str: string;
  576. begin
  577. ACell := FindCell(ARow, ACol);
  578. if ACell = nil then
  579. begin
  580. Result := 0.0;
  581. Exit;
  582. end;
  583. case ACell^.ContentType of
  584. //cctFormula
  585. cctDateTime : Result := ACell^.DateTimeValue; //this is in FPC TDateTime format, not Excel
  586. cctNumber : Result := ACell^.NumberValue;
  587. cctUTF8String:
  588. begin
  589. // The try is necessary to catch errors while converting the string
  590. // to a number, an operation which may fail
  591. try
  592. Str := ACell^.UTF8StringValue;
  593. Result := StrToFloat(Str);
  594. except
  595. Result := 0.0;
  596. end;
  597. end;
  598. else
  599. Result := 0.0;
  600. end;
  601. end;
  602. {@@
  603. Reads the contents of a cell and returns the date/time value of the cell.
  604. @param ARow The row of the cell
  605. @param ACol The column of the cell
  606. @return True if the cell is a datetime value, false otherwise
  607. }
  608. function TsWorksheet.ReadAsDateTime(ARow, ACol: Cardinal; out AResult: TDateTime): Boolean;
  609. var
  610. ACell: PCell;
  611. Str: string;
  612. begin
  613. ACell := FindCell(ARow, ACol);
  614. if (ACell = nil) or (ACell^.ContentType <> cctDateTime) then
  615. begin
  616. AResult := 0;
  617. Result := False;
  618. Exit;
  619. end;
  620. AResult := ACell^.DateTimeValue;
  621. Result := True;
  622. end;
  623. function TsWorksheet.ReadUsedFormatting(ARow, ACol: Cardinal): TsUsedFormattingFields;
  624. var
  625. ACell: PCell;
  626. begin
  627. ACell := FindCell(ARow, ACol);
  628. if ACell = nil then
  629. begin
  630. Result := [];
  631. Exit;
  632. end;
  633. Result := ACell^.UsedFormattingFields;
  634. end;
  635. function TsWorksheet.ReadBackgroundColor(ARow, ACol: Cardinal): TsColor;
  636. var
  637. ACell: PCell;
  638. begin
  639. ACell := FindCell(ARow, ACol);
  640. if ACell = nil then
  641. begin
  642. Result := scWhite;
  643. Exit;
  644. end;
  645. Result := ACell^.BackgroundColor;
  646. end;
  647. {@@
  648. Clears the list of Cells and releases their memory.
  649. }
  650. procedure TsWorksheet.RemoveAllCells;
  651. var
  652. Node: TAVLTreeNode;
  653. begin
  654. Node:=FCells.FindLowest;
  655. while Assigned(Node) do begin
  656. RemoveCallback(Node.Data,nil);
  657. Node.Data:=nil;
  658. Node:=FCells.FindSuccessor(Node);
  659. end;
  660. FCells.Clear;
  661. end;
  662. {@@
  663. Writes UTF-8 encoded text to a determined cell.
  664. On formats that don't support unicode, the text will be converted
  665. to ISO Latin 1.
  666. @param ARow The row of the cell
  667. @param ACol The column of the cell
  668. @param AText The text to be written encoded in utf-8
  669. }
  670. procedure TsWorksheet.WriteUTF8Text(ARow, ACol: Cardinal; AText: ansistring);
  671. var
  672. ACell: PCell;
  673. begin
  674. ACell := GetCell(ARow, ACol);
  675. ACell^.ContentType := cctUTF8String;
  676. ACell^.UTF8StringValue := AText;
  677. end;
  678. {@@
  679. Writes a floating-point number to a determined cell
  680. @param ARow The row of the cell
  681. @param ACol The column of the cell
  682. @param ANumber The number to be written
  683. }
  684. procedure TsWorksheet.WriteNumber(ARow, ACol: Cardinal; ANumber: double);
  685. var
  686. ACell: PCell;
  687. begin
  688. ACell := GetCell(ARow, ACol);
  689. ACell^.ContentType := cctNumber;
  690. ACell^.NumberValue := ANumber;
  691. end;
  692. {@@
  693. Writes a date/time value to a determined cell
  694. @param ARow The row of the cell
  695. @param ACol The column of the cell
  696. @param AValue The date/time/datetime to be written
  697. Note: at least Excel xls does not recognize a separate datetime cell type:
  698. a datetime is stored as a (floating point) Number, and the cell is formatted
  699. as a date (either built-in or a custom format).
  700. This procedure automatically sets the cell format to short date/time. You may
  701. change this format to another date/time format, but changing it to another
  702. format (e.g. General) will likely lead to the cell being written out as a
  703. plain number.
  704. }
  705. procedure TsWorksheet.WriteDateTime(ARow, ACol: Cardinal; AValue: TDateTime);
  706. var
  707. ACell: PCell;
  708. begin
  709. ACell := GetCell(ARow, ACol);
  710. ACell^.ContentType := cctDateTime;
  711. ACell^.DateTimeValue := AValue;
  712. // Date/time is actually a number field in Excel.
  713. // To make sure it gets saved correctly, set a date format (instead of General).
  714. // The user can choose another date format if he wants to
  715. if not(uffNumberFormat in ACell^.UsedFormattingFields) or
  716. ((uffNumberFormat in ACell^.UsedFormattingFields) and (ACell^.NumberFormat = nfGeneral)) then
  717. begin
  718. Include(ACell^.UsedFormattingFields, uffNumberFormat);
  719. ACell^.NumberFormat := nfShortDateTime;
  720. end;
  721. end;
  722. {@@
  723. Writes a formula to a determined cell
  724. @param ARow The row of the cell
  725. @param ACol The column of the cell
  726. @param AFormula The formula to be written
  727. }
  728. procedure TsWorksheet.WriteFormula(ARow, ACol: Cardinal; AFormula: TsFormula);
  729. var
  730. ACell: PCell;
  731. begin
  732. ACell := GetCell(ARow, ACol);
  733. ACell^.ContentType := cctFormula;
  734. ACell^.FormulaValue := AFormula;
  735. end;
  736. {@@
  737. Adds number format to the formatting of a cell
  738. @param ARow The row of the cell
  739. @param ACol The column of the cell
  740. @param TsNumberFormat What format to apply
  741. @see TsNumberFormat
  742. }
  743. procedure TsWorksheet.WriteNumberFormat(ARow, ACol: Cardinal;
  744. ANumberFormat: TsNumberFormat);
  745. var
  746. ACell: PCell;
  747. begin
  748. ACell := GetCell(ARow, ACol);
  749. Include(ACell^.UsedFormattingFields, uffNumberFormat);
  750. ACell^.NumberFormat := ANumberFormat;
  751. end;
  752. procedure TsWorksheet.WriteRPNFormula(ARow, ACol: Cardinal;
  753. AFormula: TsRPNFormula);
  754. var
  755. ACell: PCell;
  756. begin
  757. ACell := GetCell(ARow, ACol);
  758. ACell^.ContentType := cctRPNFormula;
  759. ACell^.RPNFormulaValue := AFormula;
  760. end;
  761. {@@
  762. Adds text rotation to the formatting of a cell
  763. @param ARow The row of the cell
  764. @param ACol The column of the cell
  765. @param ARotation How to rotate the text
  766. @see TsTextRotation
  767. }
  768. procedure TsWorksheet.WriteTextRotation(ARow, ACol: Cardinal;
  769. ARotation: TsTextRotation);
  770. var
  771. ACell: PCell;
  772. begin
  773. ACell := GetCell(ARow, ACol);
  774. Include(ACell^.UsedFormattingFields, uffTextRotation);
  775. ACell^.TextRotation := ARotation;
  776. end;
  777. procedure TsWorksheet.WriteUsedFormatting(ARow, ACol: Cardinal;
  778. AUsedFormatting: TsUsedFormattingFields);
  779. var
  780. ACell: PCell;
  781. begin
  782. ACell := GetCell(ARow, ACol);
  783. ACell^.UsedFormattingFields := AUsedFormatting;
  784. end;
  785. procedure TsWorksheet.WriteBackgroundColor(ARow, ACol: Cardinal;
  786. AColor: TsColor);
  787. var
  788. ACell: PCell;
  789. begin
  790. ACell := GetCell(ARow, ACol);
  791. ACell^.UsedFormattingFields := ACell^.UsedFormattingFields + [uffBackgroundColor];
  792. ACell^.BackgroundColor := AColor;
  793. end;
  794. function TsWorksheet.FindRow(ARow: Cardinal): PRow;
  795. var
  796. LElement: TRow;
  797. AVLNode: TAVGLVLTreeNode;
  798. begin
  799. Result := nil;
  800. LElement.Row := ARow;
  801. AVLNode := FRows.Find(@LElement);
  802. if Assigned(AVLNode) then
  803. result := PRow(AVLNode.Data);
  804. end;
  805. function TsWorksheet.FindCol(ACol: Cardinal): PCol;
  806. var
  807. LElement: TCol;
  808. AVLNode: TAVGLVLTreeNode;
  809. begin
  810. Result := nil;
  811. LElement.Col := ACol;
  812. AVLNode := FRows.Find(@LElement);
  813. if Assigned(AVLNode) then
  814. result := PCol(AVLNode.Data);
  815. end;
  816. function TsWorksheet.GetRow(ARow: Cardinal): PRow;
  817. begin
  818. Result := FindRow(ARow);
  819. if (Result = nil) then
  820. begin
  821. Result := GetMem(SizeOf(TRow));
  822. FillChar(Result^, SizeOf(TRow), #0);
  823. Result^.Row := ARow;
  824. FRows.Add(Result);
  825. end;
  826. end;
  827. function TsWorksheet.GetCol(ACol: Cardinal): PCol;
  828. begin
  829. Result := FindCol(ACol);
  830. if (Result = nil) then
  831. begin
  832. Result := GetMem(SizeOf(TCol));
  833. FillChar(Result^, SizeOf(TCol), #0);
  834. Result^.Col := ACol;
  835. FCols.Add(Result);
  836. end;
  837. end;
  838. procedure TsWorksheet.RemoveAllRows;
  839. var
  840. Node: TAVGLVLTreeNode;
  841. i: Integer;
  842. begin
  843. for i := 0 to FRows.Count-1 do
  844. begin
  845. Node:=FRows.Items[0];
  846. FreeMem(PRow(Node.Data));
  847. end;
  848. FRows.Clear;
  849. end;
  850. procedure TsWorksheet.RemoveAllCols;
  851. var
  852. Node: TAVGLVLTreeNode;
  853. i: Integer;
  854. begin
  855. for i := 0 to FCols.Count-1 do
  856. begin
  857. Node:=FCols.Items[0];
  858. FreeMem(PCol(Node.Data));
  859. end;
  860. FCols.Clear;
  861. end;
  862. procedure TsWorksheet.WriteRowInfo(ARow: Cardinal; AData: TRow);
  863. var
  864. AElement: PRow;
  865. begin
  866. AElement := GetRow(ARow);
  867. AElement^.Height := AData.Height;
  868. end;
  869. procedure TsWorksheet.WriteColInfo(ACol: Cardinal; AData: TCol);
  870. var
  871. AElement: PCol;
  872. begin
  873. AElement := GetCol(ACol);
  874. AElement^.Width := AData.Width;
  875. end;
  876. { TsWorkbook }
  877. {@@
  878. Helper method for clearing the spreadsheet list.
  879. }
  880. procedure TsWorkbook.RemoveCallback(data, arg: pointer);
  881. begin
  882. TsWorksheet(data).Free;
  883. end;
  884. {@@
  885. Constructor.
  886. }
  887. constructor TsWorkbook.Create;
  888. begin
  889. inherited Create;
  890. FWorksheets := TFPList.Create;
  891. // In the future: add support for translations
  892. lpUnsupportedReadFormat := 'Tried to read a spreadsheet using an unsupported format';
  893. lpUnsupportedWriteFormat := 'Tried to write a spreadsheet using an unsupported format';
  894. end;
  895. {@@
  896. Destructor.
  897. }
  898. destructor TsWorkbook.Destroy;
  899. begin
  900. RemoveAllWorksheets;
  901. FWorksheets.Free;
  902. inherited Destroy;
  903. end;
  904. {@@
  905. Helper method for determining the spreadsheet type from the file type extension
  906. Returns: True if the file matches any of the known formats, false otherwise
  907. }
  908. class function TsWorkbook.GetFormatFromFileName(const AFileName: TFileName; var SheetType: TsSpreadsheetFormat): Boolean;
  909. var
  910. suffix: String;
  911. begin
  912. Result := True;
  913. suffix := ExtractFileExt(AFileName);
  914. if suffix = STR_EXCEL_EXTENSION then SheetType := sfExcel8
  915. else if suffix = STR_OOXML_EXCEL_EXTENSION then SheetType := sfOOXML
  916. else if suffix = STR_OPENDOCUMENT_CALC_EXTENSION then SheetType := sfOpenDocument
  917. else if suffix = STR_COMMA_SEPARATED_EXTENSION then SheetType := sfCSV
  918. else if suffix = STR_WIKITABLE_PIPES then SheetType := sfWikiTable_Pipes
  919. else if suffix = STR_WIKITABLE_WIKIMEDIA then SheetType := sfWikiTable_WikiMedia
  920. else Result := False;
  921. end;
  922. {@@
  923. Convenience method which creates the correct
  924. reader object for a given spreadsheet format.
  925. }
  926. function TsWorkbook.CreateSpreadReader(AFormat: TsSpreadsheetFormat): TsCustomSpreadReader;
  927. var
  928. i: Integer;
  929. begin
  930. Result := nil;
  931. for i := 0 to Length(GsSpreadFormats) - 1 do
  932. if GsSpreadFormats[i].Format = AFormat then
  933. begin
  934. Result := GsSpreadFormats[i].ReaderClass.Create;
  935. Break;
  936. end;
  937. if Result = nil then raise Exception.Create(lpUnsupportedReadFormat);
  938. end;
  939. {@@
  940. Convenience method which creates the correct
  941. writer object for a given spreadsheet format.
  942. }
  943. function TsWorkbook.CreateSpreadWriter(AFormat: TsSpreadsheetFormat): TsCustomSpreadWriter;
  944. var
  945. i: Integer;
  946. begin
  947. Result := nil;
  948. for i := 0 to Length(GsSpreadFormats) - 1 do
  949. if GsSpreadFormats[i].Format = AFormat then
  950. begin
  951. Result := GsSpreadFormats[i].WriterClass.Create;
  952. Break;
  953. end;
  954. if Result = nil then raise Exception.Create(lpUnsupportedWriteFormat);
  955. end;
  956. {@@
  957. Reads the document from a file.
  958. }
  959. procedure TsWorkbook.ReadFromFile(AFileName: string;
  960. AFormat: TsSpreadsheetFormat);
  961. var
  962. AReader: TsCustomSpreadReader;
  963. begin
  964. AReader := CreateSpreadReader(AFormat);
  965. try
  966. AReader.ReadFromFile(AFileName, Self);
  967. finally
  968. AReader.Free;
  969. end;
  970. end;
  971. {@@
  972. Reads the document from a file. This method will try to guess the format from
  973. the extension. In the case of the ambiguous xls extension, it will simply
  974. assume that it is BIFF8. Note that it could be BIFF2, 3, 4 or 5 too.
  975. }
  976. procedure TsWorkbook.ReadFromFile(AFileName: string); overload;
  977. var
  978. SheetType: TsSpreadsheetFormat;
  979. valid: Boolean;
  980. lException: Exception = nil;
  981. begin
  982. valid := GetFormatFromFileName(AFileName, SheetType);
  983. if valid then
  984. begin
  985. if SheetType = sfExcel8 then
  986. begin
  987. while True do
  988. begin
  989. try
  990. ReadFromFile(AFileName, SheetType);
  991. valid := True;
  992. except
  993. on E: Exception do
  994. begin
  995. if SheetType = sfExcel8 then lException := E;
  996. valid := False
  997. end;
  998. end;
  999. if valid or (SheetType = sfExcel2) then Break;
  1000. SheetType := Pred(SheetType);
  1001. end;
  1002. // A failed attempt to read a file should bring an exception, so re-raise
  1003. // the exception if necessary. We re-raise the exception brought by Excel 8,
  1004. // since this is the most common format
  1005. if (not valid) and (lException <> nil) then raise lException;
  1006. end
  1007. else
  1008. ReadFromFile(AFileName, SheetType);
  1009. end;
  1010. end;
  1011. procedure TsWorkbook.ReadFromFileIgnoringExtension(AFileName: string);
  1012. var
  1013. SheetType: TsSpreadsheetFormat;
  1014. lException: Exception;
  1015. begin
  1016. while (SheetType in [sfExcel2..sfExcel8]) and (lException <> nil) do
  1017. begin
  1018. try
  1019. Dec(SheetType);
  1020. ReadFromFile(AFileName, SheetType);
  1021. lException := nil;
  1022. except
  1023. on E: Exception do
  1024. { do nothing } ;
  1025. end;
  1026. if lException = nil then Break;
  1027. end;
  1028. end;
  1029. {@@
  1030. Reads the document from a seekable stream.
  1031. }
  1032. procedure TsWorkbook.ReadFromStream(AStream: TStream;
  1033. AFormat: TsSpreadsheetFormat);
  1034. var
  1035. AReader: TsCustomSpreadReader;
  1036. begin
  1037. AReader := CreateSpreadReader(AFormat);
  1038. try
  1039. AReader.ReadFromStream(AStream, Self);
  1040. finally
  1041. AReader.Free;
  1042. end;
  1043. end;
  1044. {@@
  1045. Writes the document to a file.
  1046. If the file doesn't exist, it will be created.
  1047. }
  1048. procedure TsWorkbook.WriteToFile(const AFileName: string;
  1049. const AFormat: TsSpreadsheetFormat; const AOverwriteExisting: Boolean = False);
  1050. var
  1051. AWriter: TsCustomSpreadWriter;
  1052. begin
  1053. AWriter := CreateSpreadWriter(AFormat);
  1054. try
  1055. AWriter.WriteToFile(AFileName, Self, AOverwriteExisting);
  1056. finally
  1057. AWriter.Free;
  1058. end;
  1059. end;
  1060. {@@
  1061. Writes the document to file based on the extension. If this was an earlier sfExcel type file, it will be upgraded to sfExcel8,
  1062. }
  1063. procedure TsWorkbook.WriteToFile(const AFileName: String;
  1064. const AOverwriteExisting: Boolean);
  1065. var
  1066. SheetType: TsSpreadsheetFormat;
  1067. valid: Boolean;
  1068. begin
  1069. valid := GetFormatFromFileName(AFileName, SheetType);
  1070. if valid then WriteToFile(AFileName, SheetType, AOverwriteExisting)
  1071. else raise Exception.Create(Format(
  1072. '[TsWorkbook.WriteToFile] Attempted to save a spreadsheet by extension, but the extension %s is invalid.', [ExtractFileExt(AFileName)]));
  1073. end;
  1074. {@@
  1075. Writes the document to a stream
  1076. }
  1077. procedure TsWorkbook.WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
  1078. var
  1079. AWriter: TsCustomSpreadWriter;
  1080. begin
  1081. AWriter := CreateSpreadWriter(AFormat);
  1082. try
  1083. AWriter.WriteToStream(AStream, Self);
  1084. finally
  1085. AWriter.Free;
  1086. end;
  1087. end;
  1088. {@@
  1089. Adds a new worksheet to the workbook
  1090. It is added to the end of the list of worksheets
  1091. @param AName The name of the new worksheet
  1092. @return The instace of the newly created worksheet
  1093. @see TsWorkbook
  1094. }
  1095. function TsWorkbook.AddWorksheet(AName: string): TsWorksheet;
  1096. begin
  1097. Result := TsWorksheet.Create;
  1098. Result.Name := AName;
  1099. FWorksheets.Add(Pointer(Result));
  1100. end;
  1101. {@@
  1102. Quick helper routine which returns the first worksheet
  1103. @return A TsWorksheet instance if at least one is present.
  1104. nil otherwise.
  1105. @see TsWorkbook.GetWorksheetByIndex
  1106. @see TsWorkbook.GetWorksheetByName
  1107. @see TsWorksheet
  1108. }
  1109. function TsWorkbook.GetFirstWorksheet: TsWorksheet;
  1110. begin
  1111. Result := TsWorksheet(FWorksheets.First);
  1112. end;
  1113. {@@
  1114. Gets the worksheet with a given index
  1115. The index is zero-based, so the first worksheet
  1116. added has index 0, the second 1, etc.
  1117. @param AIndex The index of the worksheet (0-based)
  1118. @return A TsWorksheet instance if one is present at that index.
  1119. nil otherwise.
  1120. @see TsWorkbook.GetFirstWorksheet
  1121. @see TsWorkbook.GetWorksheetByName
  1122. @see TsWorksheet
  1123. }
  1124. function TsWorkbook.GetWorksheetByIndex(AIndex: Cardinal): TsWorksheet;
  1125. begin
  1126. if (integer(AIndex) < FWorksheets.Count) and (integer(AIndex)>=0) then Result := TsWorksheet(FWorksheets.Items[AIndex])
  1127. else Result := nil;
  1128. end;
  1129. {@@
  1130. Gets the worksheet with a given worksheet name
  1131. @param AName The name of the worksheet
  1132. @return A TsWorksheet instance if one is found with that name,
  1133. nil otherwise.
  1134. @see TsWorkbook.GetFirstWorksheet
  1135. @see TsWorkbook.GetWorksheetByIndex
  1136. @see TsWorksheet
  1137. }
  1138. function TsWorkbook.GetWorksheetByName(AName: String): TsWorksheet;
  1139. var
  1140. i:integer;
  1141. begin
  1142. Result := nil;
  1143. for i:=0 to FWorksheets.Count-1 do
  1144. begin
  1145. if TsWorkSheet(FWorkSheets.Items[i]).Name=AName then
  1146. begin
  1147. Result := TsWorksheet(FWorksheets.Items[i]);
  1148. exit;
  1149. end;
  1150. end;
  1151. end;
  1152. {@@
  1153. The number of worksheets on the workbook
  1154. @see TsWorksheet
  1155. }
  1156. function TsWorkbook.GetWorksheetCount: Cardinal;
  1157. begin
  1158. Result := FWorksheets.Count;
  1159. end;
  1160. {@@
  1161. Clears the list of Worksheets and releases their memory.
  1162. }
  1163. procedure TsWorkbook.RemoveAllWorksheets;
  1164. begin
  1165. FWorksheets.ForEachCall(RemoveCallback, nil);
  1166. end;
  1167. { TsCustomSpreadReader }
  1168. constructor TsCustomSpreadReader.Create;
  1169. begin
  1170. inherited Create;
  1171. end;
  1172. {@@
  1173. Default file reading method.
  1174. Opens the file and calls ReadFromStream
  1175. @param AFileName The input file name.
  1176. @param AData The Workbook to be filled with information from the file.
  1177. @see TsWorkbook
  1178. }
  1179. procedure TsCustomSpreadReader.ReadFromFile(AFileName: string; AData: TsWorkbook);
  1180. var
  1181. InputFile: TFileStream;
  1182. begin
  1183. InputFile := TFileStream.Create(AFileName, fmOpenRead);
  1184. try
  1185. ReadFromStream(InputFile, AData);
  1186. finally
  1187. InputFile.Free;
  1188. end;
  1189. end;
  1190. {@@
  1191. This routine should be overriden in descendent classes.
  1192. }
  1193. procedure TsCustomSpreadReader.ReadFromStream(AStream: TStream; AData: TsWorkbook);
  1194. var
  1195. AStringStream: TStringStream;
  1196. AStrings: TStringList;
  1197. begin
  1198. AStringStream := TStringStream.Create('');
  1199. AStrings := TStringList.Create;
  1200. try
  1201. AStringStream.CopyFrom(AStream, AStream.Size);
  1202. AStringStream.Seek(0, soFromBeginning);
  1203. AStrings.Text := AStringStream.DataString;
  1204. ReadFromStrings(AStrings, AData);
  1205. finally
  1206. AStringStream.Free;
  1207. AStrings.Free;
  1208. end;
  1209. end;
  1210. procedure TsCustomSpreadReader.ReadFromStrings(AStrings: TStrings;
  1211. AData: TsWorkbook);
  1212. begin
  1213. raise Exception.Create(lpUnsupportedReadFormat);
  1214. end;
  1215. { TsCustomSpreadWriter }
  1216. constructor TsCustomSpreadWriter.Create;
  1217. begin
  1218. inherited Create;
  1219. end;
  1220. {@@
  1221. Checks if the style of a cell is in the list FFormattingStyles and returns the index
  1222. or -1 if it isn't
  1223. }
  1224. function TsCustomSpreadWriter.FindFormattingInList(AFormat: PCell): Integer;
  1225. var
  1226. i: Integer;
  1227. begin
  1228. Result := -1;
  1229. for i := 0 to Length(FFormattingStyles) - 1 do
  1230. begin
  1231. if (FFormattingStyles[i].UsedFormattingFields <> AFormat^.UsedFormattingFields) then Continue;
  1232. if uffTextRotation in AFormat^.UsedFormattingFields then
  1233. if (FFormattingStyles[i].TextRotation <> AFormat^.TextRotation) then Continue;
  1234. if uffBorder in AFormat^.UsedFormattingFields then
  1235. if (FFormattingStyles[i].Border <> AFormat^.Border) then Continue;
  1236. if uffBackgroundColor in AFormat^.UsedFormattingFields then
  1237. if (FFormattingStyles[i].BackgroundColor <> AFormat^.BackgroundColor) then Continue;
  1238. if uffNumberFormat in AFormat^.UsedFormattingFields then
  1239. if (FFormattingStyles[i].NumberFormat <> AFormat^.NumberFormat) then Continue;
  1240. // If we arrived here it means that the styles match
  1241. Exit(i);
  1242. end;
  1243. end;
  1244. { Each descendent should define it's own default formats, if any.
  1245. Always add the normal, unformatted style first to speed up. }
  1246. procedure TsCustomSpreadWriter.AddDefaultFormats();
  1247. begin
  1248. SetLength(FFormattingStyles, 0);
  1249. NextXFIndex := 0;
  1250. end;
  1251. procedure TsCustomSpreadWriter.ListAllFormattingStylesCallback(ACell: PCell; AStream: TStream);
  1252. var
  1253. Len: Integer;
  1254. begin
  1255. if ACell^.UsedFormattingFields = [] then Exit;
  1256. if FindFormattingInList(ACell) <> -1 then Exit;
  1257. Len := Length(FFormattingStyles);
  1258. SetLength(FFormattingStyles, Len+1);
  1259. FFormattingStyles[Len] := ACell^;
  1260. FFormattingStyles[Len].Row := NextXFIndex;
  1261. Inc(NextXFIndex);
  1262. end;
  1263. procedure TsCustomSpreadWriter.ListAllFormattingStyles(AData: TsWorkbook);
  1264. var
  1265. i: Integer;
  1266. begin
  1267. SetLength(FFormattingStyles, 0);
  1268. AddDefaultFormats();
  1269. for i := 0 to AData.GetWorksheetCount - 1 do
  1270. begin
  1271. IterateThroughCells(nil, AData.GetWorksheetByIndex(i).Cells, ListAllFormattingStylesCallback);
  1272. end;
  1273. end;
  1274. {@@
  1275. Expands a formula, separating it in it's constituent parts,
  1276. so that it is already partially parsed and it is easier to
  1277. convert it into the format supported by the writer module
  1278. }
  1279. function TsCustomSpreadWriter.ExpandFormula(AFormula: TsFormula): TsExpandedFormula;
  1280. var
  1281. StrPos: Integer;
  1282. ResPos: Integer;
  1283. begin
  1284. ResPos := -1;
  1285. SetLength(Result, 0);
  1286. // The formula needs to start with a =
  1287. if AFormula.FormulaStr[1] <> '=' then raise Exception.Create('Formula doesn''t start with =');
  1288. StrPos := 2;
  1289. while Length(AFormula.FormulaStr) <= StrPos do
  1290. begin
  1291. // Checks for cell with the format [Letter][Number]
  1292. { if (AFormula.FormulaStr[StrPos] in [a..zA..Z]) and
  1293. (AFormula.FormulaStr[StrPos + 1] in [0..9]) then
  1294. begin
  1295. Inc(ResPos);
  1296. SetLength(Result, ResPos + 1);
  1297. Result[ResPos].ElementKind := fekCell;
  1298. // Result[ResPos].Col1 := fekCell;
  1299. Result[ResPos].Row1 := AFormula.FormulaStr[StrPos + 1];
  1300. Inc(StrPos);
  1301. end
  1302. // Checks for arithmetical operations
  1303. else} if AFormula.FormulaStr[StrPos] = '+' then
  1304. begin
  1305. Inc(ResPos);
  1306. SetLength(Result, ResPos + 1);
  1307. Result[ResPos].ElementKind := fekAdd;
  1308. end;
  1309. Inc(StrPos);
  1310. end;
  1311. end;
  1312. function TsCustomSpreadWriter.FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): string;
  1313. begin
  1314. case AColor of
  1315. scBlack: Result := '000000';
  1316. scWhite: Result := 'FFFFFF';
  1317. scRed: Result := 'FF0000';
  1318. scGREEN: Result := '00FF00';
  1319. scBLUE: Result := '0000FF';
  1320. scYELLOW: Result := 'FFFF00';
  1321. scMAGENTA: Result := 'FF00FF';
  1322. scCYAN: Result := '00FFFF';
  1323. scDarkRed: Result := '800000';
  1324. scDarkGreen:Result := '008000';
  1325. scDarkBlue: Result := '000080';
  1326. scOLIVE: Result := '808000';
  1327. scPURPLE: Result := '800080';
  1328. scTEAL: Result := '008080';
  1329. scSilver: Result := 'C0C0C0';
  1330. scGrey: Result := '808080';
  1331. //
  1332. scGrey10pct:Result := 'E6E6E6';
  1333. scGrey20pct:Result := 'CCCCCC';
  1334. scOrange: Result := 'FFA500';
  1335. scDarkBrown:Result := 'a0522d';
  1336. scBrown: Result := 'cd853f';
  1337. scBeige: Result := 'f5f5dc';
  1338. scWheat: Result := 'f5deb3';
  1339. //
  1340. scRGBCOLOR: Result := Format('%x%x%x', [ARGBColor.Red div $100, ARGBColor.Green div $100, ARGBColor.Blue div $100]);
  1341. end;
  1342. end;
  1343. {@@
  1344. Helper function for the spreadsheet writers.
  1345. @see TsCustomSpreadWriter.WriteCellsToStream
  1346. }
  1347. procedure TsCustomSpreadWriter.WriteCellCallback(ACell: PCell; AStream: TStream);
  1348. begin
  1349. case ACell.ContentType of
  1350. cctDateTime: WriteDateTime(AStream, ACell^.Row, ACell^.Col, ACell^.DateTimeValue, ACell);
  1351. cctNumber: WriteNumber(AStream, ACell^.Row, ACell^.Col, ACell^.NumberValue, ACell);
  1352. cctUTF8String: WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell);
  1353. cctFormula: WriteFormula(AStream, ACell^.Row, ACell^.Col, ACell^.FormulaValue, ACell);
  1354. cctRPNFormula: WriteRPNFormula(AStream, ACell^.Row, ACell^.Col, ACell^.RPNFormulaValue, ACell);
  1355. end;
  1356. end;
  1357. {@@
  1358. Helper function for the spreadsheet writers.
  1359. Iterates all cells on a list, calling the appropriate write method for them.
  1360. @param AStream The output stream.
  1361. @param ACells List of cells to be writeen
  1362. }
  1363. procedure TsCustomSpreadWriter.WriteCellsToStream(AStream: TStream; ACells: TAVLTree);
  1364. begin
  1365. IterateThroughCells(AStream, ACells, WriteCellCallback);
  1366. end;
  1367. {@@
  1368. A generic method to iterate through all cells in a worksheet and call a callback
  1369. routine for each cell.
  1370. @param AStream The output stream, passed to the callback routine.
  1371. @param ACells List of cells to be iterated
  1372. @param ACallback The callback routine
  1373. }
  1374. procedure TsCustomSpreadWriter.IterateThroughCells(AStream: TStream; ACells: TAVLTree; ACallback: TCellsCallback);
  1375. var
  1376. AVLNode: TAVLTreeNode;
  1377. begin
  1378. AVLNode := ACells.FindLowest;
  1379. While Assigned(AVLNode) do
  1380. begin
  1381. ACallback(PCell(AVLNode.Data), AStream);
  1382. AVLNode := ACells.FindSuccessor(AVLNode);
  1383. end;
  1384. end;
  1385. {@@
  1386. Default file writting method.
  1387. Opens the file and calls WriteToStream
  1388. @param AFileName The output file name.
  1389. If the file already exists it will be replaced.
  1390. @param AData The Workbook to be saved.
  1391. @see TsWorkbook
  1392. }
  1393. procedure TsCustomSpreadWriter.WriteToFile(const AFileName: string;
  1394. AData: TsWorkbook; const AOverwriteExisting: Boolean = False);
  1395. var
  1396. OutputFile: TFileStream;
  1397. lMode: Word;
  1398. begin
  1399. if AOverwriteExisting then lMode := fmCreate or fmOpenWrite
  1400. else lMode := fmCreate;
  1401. OutputFile := TFileStream.Create(AFileName, lMode);
  1402. try
  1403. WriteToStream(OutputFile, AData);
  1404. finally
  1405. OutputFile.Free;
  1406. end;
  1407. end;
  1408. {@@
  1409. This routine should be overriden in descendent classes.
  1410. }
  1411. procedure TsCustomSpreadWriter.WriteToStream(AStream: TStream; AData: TsWorkbook);
  1412. var
  1413. lStringList: TStringList;
  1414. begin
  1415. lStringList := TStringList.Create;
  1416. try
  1417. WriteToStrings(lStringList, AData);
  1418. lStringList.SaveToStream(AStream);
  1419. finally
  1420. lStringList.Free;
  1421. end;
  1422. end;
  1423. procedure TsCustomSpreadWriter.WriteToStrings(AStrings: TStrings;
  1424. AData: TsWorkbook);
  1425. begin
  1426. raise Exception.Create(lpUnsupportedWriteFormat);
  1427. end;
  1428. procedure TsCustomSpreadWriter.WriteFormula(AStream: TStream; const ARow,
  1429. ACol: Cardinal; const AFormula: TsFormula; ACell: PCell);
  1430. begin
  1431. // Silently dump the formula; child classes should implement their own support
  1432. end;
  1433. procedure TsCustomSpreadWriter.WriteRPNFormula(AStream: TStream; const ARow,
  1434. ACol: Cardinal; const AFormula: TsRPNFormula; ACell: PCell);
  1435. begin
  1436. // Silently dump the formula; child classes should implement their own support
  1437. end;
  1438. finalization
  1439. SetLength(GsSpreadFormats, 0);
  1440. end.