PageRenderTime 64ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/functions/PEAR/Spreadsheet/Excel/Writer/Workbook.php

https://bitbucket.org/bertramtruong/phpipam
PHP | 1591 lines | 865 code | 190 blank | 536 comment | 126 complexity | f37d2eb8648d3901a64746de81d13732 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
  4. *
  5. * The majority of this is _NOT_ my code. I simply ported it from the
  6. * PERL Spreadsheet::WriteExcel module.
  7. *
  8. * The author of the Spreadsheet::WriteExcel module is John McNamara
  9. * <jmcnamara@cpan.org>
  10. *
  11. * I _DO_ maintain this code, and John McNamara has nothing to do with the
  12. * porting of this code to PHP. Any questions directly related to this
  13. * class library should be directed to me.
  14. *
  15. * License Information:
  16. *
  17. * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
  18. * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
  19. *
  20. * This library is free software; you can redistribute it and/or
  21. * modify it under the terms of the GNU Lesser General Public
  22. * License as published by the Free Software Foundation; either
  23. * version 2.1 of the License, or (at your option) any later version.
  24. *
  25. * This library is distributed in the hope that it will be useful,
  26. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  27. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  28. * Lesser General Public License for more details.
  29. *
  30. * You should have received a copy of the GNU Lesser General Public
  31. * License along with this library; if not, write to the Free Software
  32. * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  33. */
  34. require_once '../../functions/PEAR/Spreadsheet/Excel/Writer/Format.php';
  35. require_once '../../functions/PEAR/Spreadsheet/Excel/Writer/BIFFwriter.php';
  36. require_once '../../functions/PEAR/Spreadsheet/Excel/Writer/Worksheet.php';
  37. require_once '../../functions/PEAR/Spreadsheet/Excel/Writer/Parser.php';
  38. require_once '../../functions/PEAR/OLE/PPS/Root.php';
  39. require_once '../../functions/PEAR/OLE/PPS/File.php';
  40. /**
  41. * Class for generating Excel Spreadsheets
  42. *
  43. * @author Xavier Noguer <xnoguer@rezebra.com>
  44. * @category FileFormats
  45. * @package Spreadsheet_Excel_Writer
  46. */
  47. class Spreadsheet_Excel_Writer_Workbook extends Spreadsheet_Excel_Writer_BIFFwriter
  48. {
  49. /**
  50. * Filename for the Workbook
  51. * @var string
  52. */
  53. var $_filename;
  54. /**
  55. * Formula parser
  56. * @var object Parser
  57. */
  58. var $_parser;
  59. /**
  60. * Flag for 1904 date system (0 => base date is 1900, 1 => base date is 1904)
  61. * @var integer
  62. */
  63. var $_1904;
  64. /**
  65. * The active worksheet of the workbook (0 indexed)
  66. * @var integer
  67. */
  68. var $_activesheet;
  69. /**
  70. * 1st displayed worksheet in the workbook (0 indexed)
  71. * @var integer
  72. */
  73. var $_firstsheet;
  74. /**
  75. * Number of workbook tabs selected
  76. * @var integer
  77. */
  78. var $_selected;
  79. /**
  80. * Index for creating adding new formats to the workbook
  81. * @var integer
  82. */
  83. var $_xf_index;
  84. /**
  85. * Flag for preventing close from being called twice.
  86. * @var integer
  87. * @see close()
  88. */
  89. var $_fileclosed;
  90. /**
  91. * The BIFF file size for the workbook.
  92. * @var integer
  93. * @see _calcSheetOffsets()
  94. */
  95. var $_biffsize;
  96. /**
  97. * The default sheetname for all sheets created.
  98. * @var string
  99. */
  100. var $_sheetname;
  101. /**
  102. * The default XF format.
  103. * @var object Format
  104. */
  105. var $_tmp_format;
  106. /**
  107. * Array containing references to all of this workbook's worksheets
  108. * @var array
  109. */
  110. var $_worksheets;
  111. /**
  112. * Array of sheetnames for creating the EXTERNSHEET records
  113. * @var array
  114. */
  115. var $_sheetnames;
  116. /**
  117. * Array containing references to all of this workbook's formats
  118. * @var array
  119. */
  120. var $_formats;
  121. /**
  122. * Array containing the colour palette
  123. * @var array
  124. */
  125. var $_palette;
  126. /**
  127. * The default format for URLs.
  128. * @var object Format
  129. */
  130. var $_url_format;
  131. /**
  132. * The codepage indicates the text encoding used for strings
  133. * @var integer
  134. */
  135. var $_codepage;
  136. /**
  137. * The country code used for localization
  138. * @var integer
  139. */
  140. var $_country_code;
  141. /**
  142. * number of bytes for sizeinfo of strings
  143. * @var integer
  144. */
  145. var $_string_sizeinfo_size;
  146. /**
  147. * Class constructor
  148. *
  149. * @param string filename for storing the workbook. "-" for writing to stdout.
  150. * @access public
  151. */
  152. function Spreadsheet_Excel_Writer_Workbook($filename)
  153. {
  154. // It needs to call its parent's constructor explicitly
  155. $this->Spreadsheet_Excel_Writer_BIFFwriter();
  156. $this->_filename = $filename;
  157. $this->_parser = new Spreadsheet_Excel_Writer_Parser($this->_byte_order, $this->_BIFF_version);
  158. $this->_1904 = 0;
  159. $this->_activesheet = 0;
  160. $this->_firstsheet = 0;
  161. $this->_selected = 0;
  162. $this->_xf_index = 16; // 15 style XF's and 1 cell XF.
  163. $this->_fileclosed = 0;
  164. $this->_biffsize = 0;
  165. $this->_sheetname = 'Sheet';
  166. $this->_tmp_format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version);
  167. $this->_worksheets = array();
  168. $this->_sheetnames = array();
  169. $this->_formats = array();
  170. $this->_palette = array();
  171. $this->_codepage = 0x04E4; // FIXME: should change for BIFF8
  172. $this->_country_code = -1;
  173. $this->_string_sizeinfo = 3;
  174. // Add the default format for hyperlinks
  175. $this->_url_format = $this->addFormat(array('color' => 'blue', 'underline' => 1));
  176. $this->_str_total = 0;
  177. $this->_str_unique = 0;
  178. $this->_str_table = array();
  179. $this->_setPaletteXl97();
  180. }
  181. /**
  182. * Calls finalization methods.
  183. * This method should always be the last one to be called on every workbook
  184. *
  185. * @access public
  186. * @return mixed true on success. PEAR_Error on failure
  187. */
  188. function close()
  189. {
  190. if ($this->_fileclosed) { // Prevent close() from being called twice.
  191. return true;
  192. }
  193. $res = $this->_storeWorkbook();
  194. if ($this->isError($res)) {
  195. return $this->raiseError($res->getMessage());
  196. }
  197. $this->_fileclosed = 1;
  198. return true;
  199. }
  200. /**
  201. * An accessor for the _worksheets[] array
  202. * Returns an array of the worksheet objects in a workbook
  203. * It actually calls to worksheets()
  204. *
  205. * @access public
  206. * @see worksheets()
  207. * @return array
  208. */
  209. function sheets()
  210. {
  211. return $this->worksheets();
  212. }
  213. /**
  214. * An accessor for the _worksheets[] array.
  215. * Returns an array of the worksheet objects in a workbook
  216. *
  217. * @access public
  218. * @return array
  219. */
  220. function worksheets()
  221. {
  222. return $this->_worksheets;
  223. }
  224. /**
  225. * Sets the BIFF version.
  226. * This method exists just to access experimental functionality
  227. * from BIFF8. It will be deprecated !
  228. * Only possible value is 8 (Excel 97/2000).
  229. * For any other value it fails silently.
  230. *
  231. * @access public
  232. * @param integer $version The BIFF version
  233. */
  234. function setVersion($version)
  235. {
  236. if ($version == 8) { // only accept version 8
  237. $version = 0x0600;
  238. $this->_BIFF_version = $version;
  239. // change BIFFwriter limit for CONTINUE records
  240. $this->_limit = 8228;
  241. $this->_tmp_format->_BIFF_version = $version;
  242. $this->_url_format->_BIFF_version = $version;
  243. $this->_parser->_BIFF_version = $version;
  244. $this->_codepage = 0x04B0;
  245. $total_worksheets = count($this->_worksheets);
  246. // change version for all worksheets too
  247. for ($i = 0; $i < $total_worksheets; $i++) {
  248. $this->_worksheets[$i]->_BIFF_version = $version;
  249. }
  250. $total_formats = count($this->_formats);
  251. // change version for all formats too
  252. for ($i = 0; $i < $total_formats; $i++) {
  253. $this->_formats[$i]->_BIFF_version = $version;
  254. }
  255. }
  256. }
  257. /**
  258. * Set the country identifier for the workbook
  259. *
  260. * @access public
  261. * @param integer $code Is the international calling country code for the
  262. * chosen country.
  263. */
  264. function setCountry($code)
  265. {
  266. $this->_country_code = $code;
  267. }
  268. /**
  269. * Add a new worksheet to the Excel workbook.
  270. * If no name is given the name of the worksheet will be Sheeti$i, with
  271. * $i in [1..].
  272. *
  273. * @access public
  274. * @param string $name the optional name of the worksheet
  275. * @return mixed reference to a worksheet object on success, PEAR_Error
  276. * on failure
  277. */
  278. function &addWorksheet($name = '')
  279. {
  280. $index = count($this->_worksheets);
  281. $sheetname = $this->_sheetname;
  282. if ($name == '') {
  283. $name = $sheetname.($index+1);
  284. }
  285. // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
  286. if ($this->_BIFF_version != 0x0600)
  287. {
  288. if (strlen($name) > 31) {
  289. return $this->raiseError("Sheetname $name must be <= 31 chars");
  290. }
  291. }
  292. // Check that the worksheet name doesn't already exist: a fatal Excel error.
  293. $total_worksheets = count($this->_worksheets);
  294. for ($i = 0; $i < $total_worksheets; $i++) {
  295. if ($this->_worksheets[$i]->getName() == $name) {
  296. return $this->raiseError("Worksheet '$name' already exists");
  297. }
  298. }
  299. $worksheet = new Spreadsheet_Excel_Writer_Worksheet($this->_BIFF_version,
  300. $name, $index,
  301. $this->_activesheet, $this->_firstsheet,
  302. $this->_str_total, $this->_str_unique,
  303. $this->_str_table, $this->_url_format,
  304. $this->_parser, $this->_tmp_dir);
  305. $this->_worksheets[$index] = &$worksheet; // Store ref for iterator
  306. $this->_sheetnames[$index] = $name; // Store EXTERNSHEET names
  307. $this->_parser->setExtSheet($name, $index); // Register worksheet name with parser
  308. return $worksheet;
  309. }
  310. /**
  311. * Add a new format to the Excel workbook.
  312. * Also, pass any properties to the Format constructor.
  313. *
  314. * @access public
  315. * @param array $properties array with properties for initializing the format.
  316. * @return &Spreadsheet_Excel_Writer_Format reference to an Excel Format
  317. */
  318. function &addFormat($properties = array())
  319. {
  320. $format = new Spreadsheet_Excel_Writer_Format($this->_BIFF_version, $this->_xf_index, $properties);
  321. $this->_xf_index += 1;
  322. $this->_formats[] = &$format;
  323. return $format;
  324. }
  325. /**
  326. * Create new validator.
  327. *
  328. * @access public
  329. * @return &Spreadsheet_Excel_Writer_Validator reference to a Validator
  330. */
  331. function &addValidator()
  332. {
  333. include_once 'Spreadsheet/Excel/Writer/Validator.php';
  334. /* FIXME: check for successful inclusion*/
  335. $valid = new Spreadsheet_Excel_Writer_Validator($this->_parser);
  336. return $valid;
  337. }
  338. /**
  339. * Change the RGB components of the elements in the colour palette.
  340. *
  341. * @access public
  342. * @param integer $index colour index
  343. * @param integer $red red RGB value [0-255]
  344. * @param integer $green green RGB value [0-255]
  345. * @param integer $blue blue RGB value [0-255]
  346. * @return integer The palette index for the custom color
  347. */
  348. function setCustomColor($index, $red, $green, $blue)
  349. {
  350. // Match a HTML #xxyyzz style parameter
  351. /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
  352. @_ = ($_[0], hex $1, hex $2, hex $3);
  353. }*/
  354. // Check that the colour index is the right range
  355. if ($index < 8 or $index > 64) {
  356. // TODO: assign real error codes
  357. return $this->raiseError("Color index $index outside range: 8 <= index <= 64");
  358. }
  359. // Check that the colour components are in the right range
  360. if (($red < 0 or $red > 255) ||
  361. ($green < 0 or $green > 255) ||
  362. ($blue < 0 or $blue > 255))
  363. {
  364. return $this->raiseError("Color component outside range: 0 <= color <= 255");
  365. }
  366. $index -= 8; // Adjust colour index (wingless dragonfly)
  367. // Set the RGB value
  368. $this->_palette[$index] = array($red, $green, $blue, 0);
  369. return($index + 8);
  370. }
  371. /**
  372. * Sets the colour palette to the Excel 97+ default.
  373. *
  374. * @access private
  375. */
  376. function _setPaletteXl97()
  377. {
  378. $this->_palette = array(
  379. array(0x00, 0x00, 0x00, 0x00), // 8
  380. array(0xff, 0xff, 0xff, 0x00), // 9
  381. array(0xff, 0x00, 0x00, 0x00), // 10
  382. array(0x00, 0xff, 0x00, 0x00), // 11
  383. array(0x00, 0x00, 0xff, 0x00), // 12
  384. array(0xff, 0xff, 0x00, 0x00), // 13
  385. array(0xff, 0x00, 0xff, 0x00), // 14
  386. array(0x00, 0xff, 0xff, 0x00), // 15
  387. array(0x80, 0x00, 0x00, 0x00), // 16
  388. array(0x00, 0x80, 0x00, 0x00), // 17
  389. array(0x00, 0x00, 0x80, 0x00), // 18
  390. array(0x80, 0x80, 0x00, 0x00), // 19
  391. array(0x80, 0x00, 0x80, 0x00), // 20
  392. array(0x00, 0x80, 0x80, 0x00), // 21
  393. array(0xc0, 0xc0, 0xc0, 0x00), // 22
  394. array(0x80, 0x80, 0x80, 0x00), // 23
  395. array(0x99, 0x99, 0xff, 0x00), // 24
  396. array(0x99, 0x33, 0x66, 0x00), // 25
  397. array(0xff, 0xff, 0xcc, 0x00), // 26
  398. array(0xcc, 0xff, 0xff, 0x00), // 27
  399. array(0x66, 0x00, 0x66, 0x00), // 28
  400. array(0xff, 0x80, 0x80, 0x00), // 29
  401. array(0x00, 0x66, 0xcc, 0x00), // 30
  402. array(0xcc, 0xcc, 0xff, 0x00), // 31
  403. array(0x00, 0x00, 0x80, 0x00), // 32
  404. array(0xff, 0x00, 0xff, 0x00), // 33
  405. array(0xff, 0xff, 0x00, 0x00), // 34
  406. array(0x00, 0xff, 0xff, 0x00), // 35
  407. array(0x80, 0x00, 0x80, 0x00), // 36
  408. array(0x80, 0x00, 0x00, 0x00), // 37
  409. array(0x00, 0x80, 0x80, 0x00), // 38
  410. array(0x00, 0x00, 0xff, 0x00), // 39
  411. array(0x00, 0xcc, 0xff, 0x00), // 40
  412. array(0xcc, 0xff, 0xff, 0x00), // 41
  413. array(0xcc, 0xff, 0xcc, 0x00), // 42
  414. array(0xff, 0xff, 0x99, 0x00), // 43
  415. array(0x99, 0xcc, 0xff, 0x00), // 44
  416. array(0xff, 0x99, 0xcc, 0x00), // 45
  417. array(0xcc, 0x99, 0xff, 0x00), // 46
  418. array(0xff, 0xcc, 0x99, 0x00), // 47
  419. array(0x33, 0x66, 0xff, 0x00), // 48
  420. array(0x33, 0xcc, 0xcc, 0x00), // 49
  421. array(0x99, 0xcc, 0x00, 0x00), // 50
  422. array(0xff, 0xcc, 0x00, 0x00), // 51
  423. array(0xff, 0x99, 0x00, 0x00), // 52
  424. array(0xff, 0x66, 0x00, 0x00), // 53
  425. array(0x66, 0x66, 0x99, 0x00), // 54
  426. array(0x96, 0x96, 0x96, 0x00), // 55
  427. array(0x00, 0x33, 0x66, 0x00), // 56
  428. array(0x33, 0x99, 0x66, 0x00), // 57
  429. array(0x00, 0x33, 0x00, 0x00), // 58
  430. array(0x33, 0x33, 0x00, 0x00), // 59
  431. array(0x99, 0x33, 0x00, 0x00), // 60
  432. array(0x99, 0x33, 0x66, 0x00), // 61
  433. array(0x33, 0x33, 0x99, 0x00), // 62
  434. array(0x33, 0x33, 0x33, 0x00), // 63
  435. );
  436. }
  437. /**
  438. * Assemble worksheets into a workbook and send the BIFF data to an OLE
  439. * storage.
  440. *
  441. * @access private
  442. * @return mixed true on success. PEAR_Error on failure
  443. */
  444. public function _storeWorkbook()
  445. {
  446. if (count($this->_worksheets) == 0) {
  447. return true;
  448. }
  449. // Ensure that at least one worksheet has been selected.
  450. if ($this->_activesheet == 0) {
  451. $this->_worksheets[0]->selected = 1;
  452. }
  453. // Calculate the number of selected worksheet tabs and call the finalization
  454. // methods for each worksheet
  455. $total_worksheets = count($this->_worksheets);
  456. for ($i = 0; $i < $total_worksheets; $i++) {
  457. if ($this->_worksheets[$i]->selected) {
  458. $this->_selected++;
  459. }
  460. $this->_worksheets[$i]->close($this->_sheetnames);
  461. }
  462. // Add Workbook globals
  463. $this->_storeBof(0x0005);
  464. $this->_storeCodepage();
  465. if ($this->_BIFF_version == 0x0600) {
  466. $this->_storeWindow1();
  467. }
  468. if ($this->_BIFF_version == 0x0500) {
  469. $this->_storeExterns(); // For print area and repeat rows
  470. }
  471. $this->_storeNames(); // For print area and repeat rows
  472. if ($this->_BIFF_version == 0x0500) {
  473. $this->_storeWindow1();
  474. }
  475. $this->_storeDatemode();
  476. $this->_storeAllFonts();
  477. $this->_storeAllNumFormats();
  478. $this->_storeAllXfs();
  479. $this->_storeAllStyles();
  480. $this->_storePalette();
  481. $this->_calcSheetOffsets();
  482. // Add BOUNDSHEET records
  483. for ($i = 0; $i < $total_worksheets; $i++) {
  484. $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
  485. }
  486. if ($this->_country_code != -1) {
  487. $this->_storeCountry();
  488. }
  489. if ($this->_BIFF_version == 0x0600) {
  490. //$this->_storeSupbookInternal();
  491. /* TODO: store external SUPBOOK records and XCT and CRN records
  492. in case of external references for BIFF8 */
  493. //$this->_storeExternsheetBiff8();
  494. $this->_storeSharedStringsTable();
  495. }
  496. // End Workbook globals
  497. $this->_storeEof();
  498. // Store the workbook in an OLE container
  499. $res = $this->_storeOLEFile();
  500. if ($this->isError($res)) {
  501. return $this->raiseError($res->getMessage());
  502. }
  503. return true;
  504. }
  505. /**
  506. * Store the workbook in an OLE container
  507. *
  508. * @access private
  509. * @return mixed true on success. PEAR_Error on failure
  510. */
  511. function _storeOLEFile()
  512. {
  513. if($this->_BIFF_version == 0x0600) {
  514. $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Workbook'));
  515. } else {
  516. $OLE = new OLE_PPS_File(OLE::Asc2Ucs('Book'));
  517. }
  518. if ($this->_tmp_dir != '') {
  519. $OLE->setTempDir($this->_tmp_dir);
  520. }
  521. $res = $OLE->init();
  522. if ($this->isError($res)) {
  523. return $this->raiseError("OLE Error: ".$res->getMessage());
  524. }
  525. $OLE->append($this->_data);
  526. $total_worksheets = count($this->_worksheets);
  527. for ($i = 0; $i < $total_worksheets; $i++) {
  528. while ($tmp = $this->_worksheets[$i]->getData()) {
  529. $OLE->append($tmp);
  530. }
  531. }
  532. $root = new OLE_PPS_Root(time(), time(), array($OLE));
  533. if ($this->_tmp_dir != '') {
  534. $root->setTempDir($this->_tmp_dir);
  535. }
  536. $res = $root->save($this->_filename);
  537. if ($this->isError($res)) {
  538. return $this->raiseError("OLE Error: ".$res->getMessage());
  539. }
  540. return true;
  541. }
  542. /**
  543. * Calculate offsets for Worksheet BOF records.
  544. *
  545. * @access private
  546. */
  547. function _calcSheetOffsets()
  548. {
  549. if ($this->_BIFF_version == 0x0600) {
  550. $boundsheet_length = 12; // fixed length for a BOUNDSHEET record
  551. } else {
  552. $boundsheet_length = 11;
  553. }
  554. $EOF = 4;
  555. $offset = $this->_datasize;
  556. if ($this->_BIFF_version == 0x0600) {
  557. // add the length of the SST
  558. /* TODO: check this works for a lot of strings (> 8224 bytes) */
  559. $offset += $this->_calculateSharedStringsSizes();
  560. if ($this->_country_code != -1) {
  561. $offset += 8; // adding COUNTRY record
  562. }
  563. // add the lenght of SUPBOOK, EXTERNSHEET and NAME records
  564. //$offset += 8; // FIXME: calculate real value when storing the records
  565. }
  566. $total_worksheets = count($this->_worksheets);
  567. // add the length of the BOUNDSHEET records
  568. for ($i = 0; $i < $total_worksheets; $i++) {
  569. $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
  570. }
  571. $offset += $EOF;
  572. for ($i = 0; $i < $total_worksheets; $i++) {
  573. $this->_worksheets[$i]->offset = $offset;
  574. $offset += $this->_worksheets[$i]->_datasize;
  575. }
  576. $this->_biffsize = $offset;
  577. }
  578. /**
  579. * Store the Excel FONT records.
  580. *
  581. * @access private
  582. */
  583. function _storeAllFonts()
  584. {
  585. // tmp_format is added by the constructor. We use this to write the default XF's
  586. $format = $this->_tmp_format;
  587. $font = $format->getFont();
  588. // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
  589. // so the following fonts are 0, 1, 2, 3, 5
  590. //
  591. for ($i = 1; $i <= 5; $i++){
  592. $this->_append($font);
  593. }
  594. // Iterate through the XF objects and write a FONT record if it isn't the
  595. // same as the default FONT and if it hasn't already been used.
  596. //
  597. $fonts = array();
  598. $index = 6; // The first user defined FONT
  599. $key = $format->getFontKey(); // The default font from _tmp_format
  600. $fonts[$key] = 0; // Index of the default font
  601. $total_formats = count($this->_formats);
  602. for ($i = 0; $i < $total_formats; $i++) {
  603. $key = $this->_formats[$i]->getFontKey();
  604. if (isset($fonts[$key])) {
  605. // FONT has already been used
  606. $this->_formats[$i]->font_index = $fonts[$key];
  607. } else {
  608. // Add a new FONT record
  609. $fonts[$key] = $index;
  610. $this->_formats[$i]->font_index = $index;
  611. $index++;
  612. $font = $this->_formats[$i]->getFont();
  613. $this->_append($font);
  614. }
  615. }
  616. }
  617. /**
  618. * Store user defined numerical formats i.e. FORMAT records
  619. *
  620. * @access private
  621. */
  622. function _storeAllNumFormats()
  623. {
  624. // Leaning num_format syndrome
  625. $hash_num_formats = array();
  626. $num_formats = array();
  627. $index = 164;
  628. // Iterate through the XF objects and write a FORMAT record if it isn't a
  629. // built-in format type and if the FORMAT string hasn't already been used.
  630. $total_formats = count($this->_formats);
  631. for ($i = 0; $i < $total_formats; $i++) {
  632. $num_format = $this->_formats[$i]->_num_format;
  633. // Check if $num_format is an index to a built-in format.
  634. // Also check for a string of zeros, which is a valid format string
  635. // but would evaluate to zero.
  636. //
  637. if (!preg_match("/^0+\d/", $num_format)) {
  638. if (preg_match("/^\d+$/", $num_format)) { // built-in format
  639. continue;
  640. }
  641. }
  642. if (isset($hash_num_formats[$num_format])) {
  643. // FORMAT has already been used
  644. $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
  645. } else{
  646. // Add a new FORMAT
  647. $hash_num_formats[$num_format] = $index;
  648. $this->_formats[$i]->_num_format = $index;
  649. array_push($num_formats,$num_format);
  650. $index++;
  651. }
  652. }
  653. // Write the new FORMAT records starting from 0xA4
  654. $index = 164;
  655. foreach ($num_formats as $num_format) {
  656. $this->_storeNumFormat($num_format,$index);
  657. $index++;
  658. }
  659. }
  660. /**
  661. * Write all XF records.
  662. *
  663. * @access private
  664. */
  665. function _storeAllXfs()
  666. {
  667. // _tmp_format is added by the constructor. We use this to write the default XF's
  668. // The default font index is 0
  669. //
  670. $format = $this->_tmp_format;
  671. for ($i = 0; $i <= 14; $i++) {
  672. $xf = $format->getXf('style'); // Style XF
  673. $this->_append($xf);
  674. }
  675. $xf = $format->getXf('cell'); // Cell XF
  676. $this->_append($xf);
  677. // User defined XFs
  678. $total_formats = count($this->_formats);
  679. for ($i = 0; $i < $total_formats; $i++) {
  680. $xf = $this->_formats[$i]->getXf('cell');
  681. $this->_append($xf);
  682. }
  683. }
  684. /**
  685. * Write all STYLE records.
  686. *
  687. * @access private
  688. */
  689. function _storeAllStyles()
  690. {
  691. $this->_storeStyle();
  692. }
  693. /**
  694. * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
  695. * the NAME records.
  696. *
  697. * @access private
  698. */
  699. function _storeExterns()
  700. {
  701. // Create EXTERNCOUNT with number of worksheets
  702. $this->_storeExterncount(count($this->_worksheets));
  703. // Create EXTERNSHEET for each worksheet
  704. foreach ($this->_sheetnames as $sheetname) {
  705. $this->_storeExternsheet($sheetname);
  706. }
  707. }
  708. /**
  709. * Write the NAME record to define the print area and the repeat rows and cols.
  710. *
  711. * @access private
  712. */
  713. function _storeNames()
  714. {
  715. // Create the print area NAME records
  716. $total_worksheets = count($this->_worksheets);
  717. for ($i = 0; $i < $total_worksheets; $i++) {
  718. // Write a Name record if the print area has been defined
  719. if (isset($this->_worksheets[$i]->print_rowmin)) {
  720. $this->_storeNameShort(
  721. $this->_worksheets[$i]->index,
  722. 0x06, // NAME type
  723. $this->_worksheets[$i]->print_rowmin,
  724. $this->_worksheets[$i]->print_rowmax,
  725. $this->_worksheets[$i]->print_colmin,
  726. $this->_worksheets[$i]->print_colmax
  727. );
  728. }
  729. }
  730. // Create the print title NAME records
  731. $total_worksheets = count($this->_worksheets);
  732. for ($i = 0; $i < $total_worksheets; $i++) {
  733. $rowmin = $this->_worksheets[$i]->title_rowmin;
  734. $rowmax = $this->_worksheets[$i]->title_rowmax;
  735. $colmin = $this->_worksheets[$i]->title_colmin;
  736. $colmax = $this->_worksheets[$i]->title_colmax;
  737. // Determine if row + col, row, col or nothing has been defined
  738. // and write the appropriate record
  739. //
  740. if (isset($rowmin) && isset($colmin)) {
  741. // Row and column titles have been defined.
  742. // Row title has been defined.
  743. $this->_storeNameLong(
  744. $this->_worksheets[$i]->index,
  745. 0x07, // NAME type
  746. $rowmin,
  747. $rowmax,
  748. $colmin,
  749. $colmax
  750. );
  751. } elseif (isset($rowmin)) {
  752. // Row title has been defined.
  753. $this->_storeNameShort(
  754. $this->_worksheets[$i]->index,
  755. 0x07, // NAME type
  756. $rowmin,
  757. $rowmax,
  758. 0x00,
  759. 0xff
  760. );
  761. } elseif (isset($colmin)) {
  762. // Column title has been defined.
  763. $this->_storeNameShort(
  764. $this->_worksheets[$i]->index,
  765. 0x07, // NAME type
  766. 0x0000,
  767. 0x3fff,
  768. $colmin,
  769. $colmax
  770. );
  771. } else {
  772. // Print title hasn't been defined.
  773. }
  774. }
  775. }
  776. /******************************************************************************
  777. *
  778. * BIFF RECORDS
  779. *
  780. */
  781. /**
  782. * Stores the CODEPAGE biff record.
  783. *
  784. * @access private
  785. */
  786. function _storeCodepage()
  787. {
  788. $record = 0x0042; // Record identifier
  789. $length = 0x0002; // Number of bytes to follow
  790. $cv = $this->_codepage; // The code page
  791. $header = pack('vv', $record, $length);
  792. $data = pack('v', $cv);
  793. $this->_append($header . $data);
  794. }
  795. /**
  796. * Write Excel BIFF WINDOW1 record.
  797. *
  798. * @access private
  799. */
  800. function _storeWindow1()
  801. {
  802. $record = 0x003D; // Record identifier
  803. $length = 0x0012; // Number of bytes to follow
  804. $xWn = 0x0000; // Horizontal position of window
  805. $yWn = 0x0000; // Vertical position of window
  806. $dxWn = 0x25BC; // Width of window
  807. $dyWn = 0x1572; // Height of window
  808. $grbit = 0x0038; // Option flags
  809. $ctabsel = $this->_selected; // Number of workbook tabs selected
  810. $wTabRatio = 0x0258; // Tab to scrollbar ratio
  811. $itabFirst = $this->_firstsheet; // 1st displayed worksheet
  812. $itabCur = $this->_activesheet; // Active worksheet
  813. $header = pack("vv", $record, $length);
  814. $data = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
  815. $grbit,
  816. $itabCur, $itabFirst,
  817. $ctabsel, $wTabRatio);
  818. $this->_append($header . $data);
  819. }
  820. /**
  821. * Writes Excel BIFF BOUNDSHEET record.
  822. * FIXME: inconsistent with BIFF documentation
  823. *
  824. * @param string $sheetname Worksheet name
  825. * @param integer $offset Location of worksheet BOF
  826. * @access private
  827. */
  828. function _storeBoundsheet($sheetname,$offset)
  829. {
  830. $record = 0x0085; // Record identifier
  831. if ($this->_BIFF_version == 0x0600) {
  832. $length = 0x08 + strlen($sheetname); // Number of bytes to follow
  833. } else {
  834. $length = 0x07 + strlen($sheetname); // Number of bytes to follow
  835. }
  836. $grbit = 0x0000; // Visibility and sheet type
  837. $cch = strlen($sheetname); // Length of sheet name
  838. $header = pack("vv", $record, $length);
  839. if ($this->_BIFF_version == 0x0600) {
  840. $data = pack("Vvv", $offset, $grbit, $cch);
  841. } else {
  842. $data = pack("VvC", $offset, $grbit, $cch);
  843. }
  844. $this->_append($header.$data.$sheetname);
  845. }
  846. /**
  847. * Write Internal SUPBOOK record
  848. *
  849. * @access private
  850. */
  851. function _storeSupbookInternal()
  852. {
  853. $record = 0x01AE; // Record identifier
  854. $length = 0x0004; // Bytes to follow
  855. $header = pack("vv", $record, $length);
  856. $data = pack("vv", count($this->_worksheets), 0x0104);
  857. $this->_append($header . $data);
  858. }
  859. /**
  860. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  861. * formulas.
  862. *
  863. * @param string $sheetname Worksheet name
  864. * @access private
  865. */
  866. function _storeExternsheetBiff8()
  867. {
  868. $total_references = count($this->_parser->_references);
  869. $record = 0x0017; // Record identifier
  870. $length = 2 + 6 * $total_references; // Number of bytes to follow
  871. $supbook_index = 0; // FIXME: only using internal SUPBOOK record
  872. $header = pack("vv", $record, $length);
  873. $data = pack('v', $total_references);
  874. for ($i = 0; $i < $total_references; $i++) {
  875. $data .= $this->_parser->_references[$i];
  876. }
  877. $this->_append($header . $data);
  878. }
  879. /**
  880. * Write Excel BIFF STYLE records.
  881. *
  882. * @access private
  883. */
  884. function _storeStyle()
  885. {
  886. $record = 0x0293; // Record identifier
  887. $length = 0x0004; // Bytes to follow
  888. $ixfe = 0x8000; // Index to style XF
  889. $BuiltIn = 0x00; // Built-in style
  890. $iLevel = 0xff; // Outline style level
  891. $header = pack("vv", $record, $length);
  892. $data = pack("vCC", $ixfe, $BuiltIn, $iLevel);
  893. $this->_append($header . $data);
  894. }
  895. /**
  896. * Writes Excel FORMAT record for non "built-in" numerical formats.
  897. *
  898. * @param string $format Custom format string
  899. * @param integer $ifmt Format index code
  900. * @access private
  901. */
  902. function _storeNumFormat($format, $ifmt)
  903. {
  904. $record = 0x041E; // Record identifier
  905. if ($this->_BIFF_version == 0x0600) {
  906. $length = 5 + strlen($format); // Number of bytes to follow
  907. $encoding = 0x0;
  908. } elseif ($this->_BIFF_version == 0x0500) {
  909. $length = 3 + strlen($format); // Number of bytes to follow
  910. }
  911. $cch = strlen($format); // Length of format string
  912. $header = pack("vv", $record, $length);
  913. if ($this->_BIFF_version == 0x0600) {
  914. $data = pack("vvC", $ifmt, $cch, $encoding);
  915. } elseif ($this->_BIFF_version == 0x0500) {
  916. $data = pack("vC", $ifmt, $cch);
  917. }
  918. $this->_append($header . $data . $format);
  919. }
  920. /**
  921. * Write DATEMODE record to indicate the date system in use (1904 or 1900).
  922. *
  923. * @access private
  924. */
  925. function _storeDatemode()
  926. {
  927. $record = 0x0022; // Record identifier
  928. $length = 0x0002; // Bytes to follow
  929. $f1904 = $this->_1904; // Flag for 1904 date system
  930. $header = pack("vv", $record, $length);
  931. $data = pack("v", $f1904);
  932. $this->_append($header . $data);
  933. }
  934. /**
  935. * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  936. * references in the workbook.
  937. *
  938. * Excel only stores references to external sheets that are used in NAME.
  939. * The workbook NAME record is required to define the print area and the repeat
  940. * rows and columns.
  941. *
  942. * A similar method is used in Worksheet.php for a slightly different purpose.
  943. *
  944. * @param integer $cxals Number of external references
  945. * @access private
  946. */
  947. function _storeExterncount($cxals)
  948. {
  949. $record = 0x0016; // Record identifier
  950. $length = 0x0002; // Number of bytes to follow
  951. $header = pack("vv", $record, $length);
  952. $data = pack("v", $cxals);
  953. $this->_append($header . $data);
  954. }
  955. /**
  956. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  957. * formulas. NAME record is required to define the print area and the repeat
  958. * rows and columns.
  959. *
  960. * A similar method is used in Worksheet.php for a slightly different purpose.
  961. *
  962. * @param string $sheetname Worksheet name
  963. * @access private
  964. */
  965. function _storeExternsheet($sheetname)
  966. {
  967. $record = 0x0017; // Record identifier
  968. $length = 0x02 + strlen($sheetname); // Number of bytes to follow
  969. $cch = strlen($sheetname); // Length of sheet name
  970. $rgch = 0x03; // Filename encoding
  971. $header = pack("vv", $record, $length);
  972. $data = pack("CC", $cch, $rgch);
  973. $this->_append($header . $data . $sheetname);
  974. }
  975. /**
  976. * Store the NAME record in the short format that is used for storing the print
  977. * area, repeat rows only and repeat columns only.
  978. *
  979. * @param integer $index Sheet index
  980. * @param integer $type Built-in name type
  981. * @param integer $rowmin Start row
  982. * @param integer $rowmax End row
  983. * @param integer $colmin Start colum
  984. * @param integer $colmax End column
  985. * @access private
  986. */
  987. function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  988. {
  989. $record = 0x0018; // Record identifier
  990. $length = 0x0024; // Number of bytes to follow
  991. $grbit = 0x0020; // Option flags
  992. $chKey = 0x00; // Keyboard shortcut
  993. $cch = 0x01; // Length of text name
  994. $cce = 0x0015; // Length of text definition
  995. $ixals = $index + 1; // Sheet index
  996. $itab = $ixals; // Equal to ixals
  997. $cchCustMenu = 0x00; // Length of cust menu text
  998. $cchDescription = 0x00; // Length of description text
  999. $cchHelptopic = 0x00; // Length of help topic text
  1000. $cchStatustext = 0x00; // Length of status bar text
  1001. $rgch = $type; // Built-in name type
  1002. $unknown03 = 0x3b;
  1003. $unknown04 = 0xffff-$index;
  1004. $unknown05 = 0x0000;
  1005. $unknown06 = 0x0000;
  1006. $unknown07 = 0x1087;
  1007. $unknown08 = 0x8005;
  1008. $header = pack("vv", $record, $length);
  1009. $data = pack("v", $grbit);
  1010. $data .= pack("C", $chKey);
  1011. $data .= pack("C", $cch);
  1012. $data .= pack("v", $cce);
  1013. $data .= pack("v", $ixals);
  1014. $data .= pack("v", $itab);
  1015. $data .= pack("C", $cchCustMenu);
  1016. $data .= pack("C", $cchDescription);
  1017. $data .= pack("C", $cchHelptopic);
  1018. $data .= pack("C", $cchStatustext);
  1019. $data .= pack("C", $rgch);
  1020. $data .= pack("C", $unknown03);
  1021. $data .= pack("v", $unknown04);
  1022. $data .= pack("v", $unknown05);
  1023. $data .= pack("v", $unknown06);
  1024. $data .= pack("v", $unknown07);
  1025. $data .= pack("v", $unknown08);
  1026. $data .= pack("v", $index);
  1027. $data .= pack("v", $index);
  1028. $data .= pack("v", $rowmin);
  1029. $data .= pack("v", $rowmax);
  1030. $data .= pack("C", $colmin);
  1031. $data .= pack("C", $colmax);
  1032. $this->_append($header . $data);
  1033. }
  1034. /**
  1035. * Store the NAME record in the long format that is used for storing the repeat
  1036. * rows and columns when both are specified. This shares a lot of code with
  1037. * _storeNameShort() but we use a separate method to keep the code clean.
  1038. * Code abstraction for reuse can be carried too far, and I should know. ;-)
  1039. *
  1040. * @param integer $index Sheet index
  1041. * @param integer $type Built-in name type
  1042. * @param integer $rowmin Start row
  1043. * @param integer $rowmax End row
  1044. * @param integer $colmin Start colum
  1045. * @param integer $colmax End column
  1046. * @access private
  1047. */
  1048. function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  1049. {
  1050. $record = 0x0018; // Record identifier
  1051. $length = 0x003d; // Number of bytes to follow
  1052. $grbit = 0x0020; // Option flags
  1053. $chKey = 0x00; // Keyboard shortcut
  1054. $cch = 0x01; // Length of text name
  1055. $cce = 0x002e; // Length of text definition
  1056. $ixals = $index + 1; // Sheet index
  1057. $itab = $ixals; // Equal to ixals
  1058. $cchCustMenu = 0x00; // Length of cust menu text
  1059. $cchDescription = 0x00; // Length of description text
  1060. $cchHelptopic = 0x00; // Length of help topic text
  1061. $cchStatustext = 0x00; // Length of status bar text
  1062. $rgch = $type; // Built-in name type
  1063. $unknown01 = 0x29;
  1064. $unknown02 = 0x002b;
  1065. $unknown03 = 0x3b;
  1066. $unknown04 = 0xffff-$index;
  1067. $unknown05 = 0x0000;
  1068. $unknown06 = 0x0000;
  1069. $unknown07 = 0x1087;
  1070. $unknown08 = 0x8008;
  1071. $header = pack("vv", $record, $length);
  1072. $data = pack("v", $grbit);
  1073. $data .= pack("C", $chKey);
  1074. $data .= pack("C", $cch);
  1075. $data .= pack("v", $cce);
  1076. $data .= pack("v", $ixals);
  1077. $data .= pack("v", $itab);
  1078. $data .= pack("C", $cchCustMenu);
  1079. $data .= pack("C", $cchDescription);
  1080. $data .= pack("C", $cchHelptopic);
  1081. $data .= pack("C", $cchStatustext);
  1082. $data .= pack("C", $rgch);
  1083. $data .= pack("C", $unknown01);
  1084. $data .= pack("v", $unknown02);
  1085. // Column definition
  1086. $data .= pack("C", $unknown03);
  1087. $data .= pack("v", $unknown04);
  1088. $data .= pack("v", $unknown05);
  1089. $data .= pack("v", $unknown06);
  1090. $data .= pack("v", $unknown07);
  1091. $data .= pack("v", $unknown08);
  1092. $data .= pack("v", $index);
  1093. $data .= pack("v", $index);
  1094. $data .= pack("v", 0x0000);
  1095. $data .= pack("v", 0x3fff);
  1096. $data .= pack("C", $colmin);
  1097. $data .= pack("C", $colmax);
  1098. // Row definition
  1099. $data .= pack("C", $unknown03);
  1100. $data .= pack("v", $unknown04);
  1101. $data .= pack("v", $unknown05);
  1102. $data .= pack("v", $unknown06);
  1103. $data .= pack("v", $unknown07);
  1104. $data .= pack("v", $unknown08);
  1105. $data .= pack("v", $index);
  1106. $data .= pack("v", $index);
  1107. $data .= pack("v", $rowmin);
  1108. $data .= pack("v", $rowmax);
  1109. $data .= pack("C", 0x00);
  1110. $data .= pack("C", 0xff);
  1111. // End of data
  1112. $data .= pack("C", 0x10);
  1113. $this->_append($header . $data);
  1114. }
  1115. /**
  1116. * Stores the COUNTRY record for localization
  1117. *
  1118. * @access private
  1119. */
  1120. function _storeCountry()
  1121. {
  1122. $record = 0x008C; // Record identifier
  1123. $length = 4; // Number of bytes to follow
  1124. $header = pack('vv', $record, $length);
  1125. /* using the same country code always for simplicity */
  1126. $data = pack('vv', $this->_country_code, $this->_country_code);
  1127. $this->_append($header . $data);
  1128. }
  1129. /**
  1130. * Stores the PALETTE biff record.
  1131. *
  1132. * @access private
  1133. */
  1134. function _storePalette()
  1135. {
  1136. $aref = $this->_palette;
  1137. $record = 0x0092; // Record identifier
  1138. $length = 2 + 4 * count($aref); // Number of bytes to follow
  1139. $ccv = count($aref); // Number of RGB values to follow
  1140. $data = ''; // The RGB data
  1141. // Pack the RGB data
  1142. foreach ($aref as $color) {
  1143. foreach ($color as $byte) {
  1144. $data .= pack("C",$byte);
  1145. }
  1146. }
  1147. $header = pack("vvv", $record, $length, $ccv);
  1148. $this->_append($header . $data);
  1149. }
  1150. /**
  1151. * Calculate
  1152. * Handling of the SST continue blocks is complicated by the need to include an
  1153. * additional continuation byte depending on whether the string is split between
  1154. * blocks or whether it starts at the beginning of the block. (There are also
  1155. * additional complications that will arise later when/if Rich Strings are
  1156. * supported).
  1157. *
  1158. * @access private
  1159. */
  1160. function _calculateSharedStringsSizes()
  1161. {
  1162. /* Iterate through the strings to calculate the CONTINUE block sizes.
  1163. For simplicity we use the same size for the SST and CONTINUE records:
  1164. 8228 : Maximum Excel97 block size
  1165. -4 : Length of block header
  1166. -8 : Length of additional SST header information
  1167. -8 : Arbitrary number to keep within _add_continue() limit = 8208
  1168. */
  1169. $continue_limit = 8208;
  1170. $block_length = 0;
  1171. $written = 0;
  1172. $this->_block_sizes = array();
  1173. $continue = 0;
  1174. foreach (array_keys($this->_str_table) as $string) {
  1175. $string_length = strlen($string);
  1176. $headerinfo = unpack("vlength/Cencoding", $string);
  1177. $encoding = $headerinfo["encoding"];
  1178. $split_string = 0;
  1179. // Block length is the total length of the strings that will be
  1180. // written out in a single SST or CONTINUE block.
  1181. $block_length += $string_length;
  1182. // We can write the string if it doesn't cross a CONTINUE boundary
  1183. if ($block_length < $continue_limit) {
  1184. $written += $string_length;
  1185. continue;
  1186. }
  1187. // Deal with the cases where the next string to be written will exceed
  1188. // the CONTINUE boundary. If the string is very long it may need to be
  1189. // written in more than one CONTINUE record.
  1190. while ($block_length >= $continue_limit) {
  1191. // We need to avoid the case where a string is continued in the first
  1192. // n bytes that contain the string header information.
  1193. $header_length = 3; // Min string + header size -1
  1194. $space_remaining = $continue_limit - $written - $continue;
  1195. /* TODO: Unicode data should only be split on char (2 byte)
  1196. boundaries. Therefore, in some cases we need to reduce the
  1197. amount of available
  1198. */
  1199. $align = 0;
  1200. // Only applies to Unicode strings
  1201. if ($encoding == 1) {
  1202. // Min string + header size -1
  1203. $header_length = 4;
  1204. if ($space_remaining > $header_length) {
  1205. // String contains 3 byte header => split on odd boundary
  1206. if (!$split_string && $space_remaining % 2 != 1) {
  1207. $space_remaining--;
  1208. $align = 1;
  1209. }
  1210. // Split section without header => split on even boundary
  1211. else if ($split_string && $space_remaining % 2 == 1) {
  1212. $space_remaining--;
  1213. $align = 1;
  1214. }
  1215. $split_string = 1;
  1216. }
  1217. }
  1218. if ($space_remaining > $header_length) {
  1219. // Write as much as possible of the string in the current block
  1220. $written += $space_remaining;
  1221. // Reduce the current block length by the amount written
  1222. $block_length -= $continue_limit - $continue - $align;
  1223. // Store the max size for this block
  1224. $this->_block_sizes[] = $continue_limit - $align;
  1225. // I…

Large files files are truncated, but you can click here to view the full file