PageRenderTime 58ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/branches/v1.6.5/Classes/PHPExcel/Writer/Excel5/Workbook.php

#
PHP | 1734 lines | 927 code | 216 blank | 591 comment | 131 complexity | 4504ed90e17a46816ad2b8a54e48db81 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-2.0, LGPL-2.1, GPL-3.0, LGPL-3.0
  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. * PHPExcel_Writer_Excel5_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 'PHPExcel/Writer/Excel5/Format.php';
  35. require_once 'PHPExcel/Writer/Excel5/BIFFwriter.php';
  36. require_once 'PHPExcel/Writer/Excel5/Worksheet.php';
  37. require_once 'PHPExcel/Writer/Excel5/Parser.php';
  38. require_once 'PHPExcel/Shared/Date.php';
  39. require_once 'PHPExcel/Shared/OLE/OLE_Root.php';
  40. require_once 'PHPExcel/Shared/OLE/OLE_File.php';
  41. require_once 'PHPExcel/Shared/String.php';
  42. /**
  43. * Class for generating Excel Spreadsheets
  44. *
  45. * @author Xavier Noguer <xnoguer@rezebra.com>
  46. * @category PHPExcel
  47. * @package PHPExcel_Writer_Excel5
  48. */
  49. class PHPExcel_Writer_Excel5_Workbook extends PHPExcel_Writer_Excel5_BIFFwriter
  50. {
  51. /**
  52. * Filename for the Workbook
  53. * @var string
  54. */
  55. var $_filename;
  56. /**
  57. * Formula parser
  58. * @var object Parser
  59. */
  60. var $_parser;
  61. /**
  62. * The active worksheet of the workbook (0 indexed)
  63. * @var integer
  64. */
  65. var $_activesheet;
  66. /**
  67. * 1st displayed worksheet in the workbook (0 indexed)
  68. * @var integer
  69. */
  70. var $_firstsheet;
  71. /**
  72. * Number of workbook tabs selected
  73. * @var integer
  74. */
  75. var $_selected;
  76. /**
  77. * Index for creating adding new formats to the workbook
  78. * @var integer
  79. */
  80. var $_xf_index;
  81. /**
  82. * Flag for preventing close from being called twice.
  83. * @var integer
  84. * @see close()
  85. */
  86. var $_fileclosed;
  87. /**
  88. * The BIFF file size for the workbook.
  89. * @var integer
  90. * @see _calcSheetOffsets()
  91. */
  92. var $_biffsize;
  93. /**
  94. * The default sheetname for all sheets created.
  95. * @var string
  96. */
  97. var $_sheetname;
  98. /**
  99. * The default XF format.
  100. * @var object Format
  101. */
  102. var $_tmp_format;
  103. /**
  104. * Array containing references to all of this workbook's worksheets
  105. * @var array
  106. */
  107. var $_worksheets;
  108. /**
  109. * Array of sheetnames for creating the EXTERNSHEET records
  110. * @var array
  111. */
  112. var $_sheetnames;
  113. /**
  114. * Array containing references to all of this workbook's formats
  115. * @var array
  116. */
  117. var $_formats;
  118. /**
  119. * Array containing the colour palette
  120. * @var array
  121. */
  122. var $_palette;
  123. /**
  124. * The default format for URLs.
  125. * @var object Format
  126. */
  127. var $_url_format;
  128. /**
  129. * The codepage indicates the text encoding used for strings
  130. * @var integer
  131. */
  132. var $_codepage;
  133. /**
  134. * The country code used for localization
  135. * @var integer
  136. */
  137. var $_country_code;
  138. /**
  139. * The temporary dir for storing the OLE file
  140. * @var string
  141. */
  142. var $_tmp_dir;
  143. /**
  144. * number of bytes for sizeinfo of strings
  145. * @var integer
  146. */
  147. var $_string_sizeinfo_size;
  148. /**
  149. * Workbook
  150. * @var PHPExcel
  151. */
  152. private $_phpExcel;
  153. /**
  154. * Class constructor
  155. *
  156. * @param string filename for storing the workbook. "-" for writing to stdout.
  157. * @param PHPExcel $phpExcel The Workbook
  158. * @access public
  159. */
  160. function PHPExcel_Writer_Excel5_Workbook($filename, $phpExcel)
  161. {
  162. // It needs to call its parent's constructor explicitly
  163. $this->PHPExcel_Writer_Excel5_BIFFwriter();
  164. $this->_filename = $filename;
  165. $this->_parser = new PHPExcel_Writer_Excel5_Parser($this->_byte_order, $this->_BIFF_version);
  166. $this->_activesheet = 0;
  167. $this->_firstsheet = 0;
  168. $this->_selected = 0;
  169. $this->_xf_index = 16; // 15 style XF's and 1 cell XF.
  170. $this->_fileclosed = 0;
  171. $this->_biffsize = 0;
  172. $this->_sheetname = 'Sheet';
  173. $this->_tmp_format = new PHPExcel_Writer_Excel5_Format($this->_BIFF_version);
  174. $this->_tmp_format->setFontFamily($phpExcel->getSheet(0)->getDefaultStyle()->getFont()->getName());
  175. $this->_tmp_format->setSize($phpExcel->getSheet(0)->getDefaultStyle()->getFont()->getSize());
  176. $this->_worksheets = array();
  177. $this->_sheetnames = array();
  178. $this->_formats = array();
  179. $this->_palette = array();
  180. $this->_codepage = 0x04E4; // FIXME: should change for BIFF8
  181. $this->_country_code = -1;
  182. $this->_string_sizeinfo = 3;
  183. // Add the default format for hyperlinks
  184. $this->_url_format =& $this->addFormat(array('color' => 'blue', 'underline' => 1));
  185. $this->_str_total = 0;
  186. $this->_str_unique = 0;
  187. $this->_str_table = array();
  188. $this->_setPaletteXl97();
  189. $this->_tmp_dir = '';
  190. $this->_phpExcel = $phpExcel;
  191. }
  192. /**
  193. * Calls finalization methods.
  194. * This method should always be the last one to be called on every workbook
  195. *
  196. * @access public
  197. * @return mixed true on success
  198. */
  199. function close()
  200. {
  201. if ($this->_fileclosed) { // Prevent close() from being called twice.
  202. return true;
  203. }
  204. $res = $this->_storeWorkbook();
  205. foreach ($this->_worksheets as $sheet) {
  206. $sheet->cleanup();
  207. }
  208. $this->_fileclosed = 1;
  209. return true;
  210. }
  211. /**
  212. * An accessor for the _worksheets[] array
  213. * Returns an array of the worksheet objects in a workbook
  214. * It actually calls to worksheets()
  215. *
  216. * @access public
  217. * @see worksheets()
  218. * @return array
  219. */
  220. function sheets()
  221. {
  222. return $this->worksheets();
  223. }
  224. /**
  225. * An accessor for the _worksheets[] array.
  226. * Returns an array of the worksheet objects in a workbook
  227. *
  228. * @access public
  229. * @return array
  230. */
  231. function worksheets()
  232. {
  233. return $this->_worksheets;
  234. }
  235. /**
  236. * Sets the BIFF version.
  237. * This method exists just to access experimental functionality
  238. * from BIFF8. It will be deprecated !
  239. * Only possible value is 8 (Excel 97/2000).
  240. * For any other value it fails silently.
  241. *
  242. * @access public
  243. * @param integer $version The BIFF version
  244. */
  245. function setVersion($version)
  246. {
  247. if ($version == 8) { // only accept version 8
  248. $version = 0x0600;
  249. $this->_BIFF_version = $version;
  250. // change BIFFwriter limit for CONTINUE records
  251. $this->_limit = 8228;
  252. $this->_tmp_format->_BIFF_version = $version;
  253. $this->_url_format->_BIFF_version = $version;
  254. $this->_parser->_BIFF_version = $version;
  255. $this->_codepage = 0x04B0;
  256. $total_worksheets = count($this->_worksheets);
  257. // change version for all worksheets too
  258. for ($i = 0; $i < $total_worksheets; ++$i) {
  259. $this->_worksheets[$i]->_BIFF_version = $version;
  260. }
  261. $total_formats = count($this->_formats);
  262. // change version for all formats too
  263. for ($i = 0; $i < $total_formats; ++$i) {
  264. $this->_formats[$i]->_BIFF_version = $version;
  265. }
  266. }
  267. }
  268. /**
  269. * Set the country identifier for the workbook
  270. *
  271. * @access public
  272. * @param integer $code Is the international calling country code for the
  273. * chosen country.
  274. */
  275. function setCountry($code)
  276. {
  277. $this->_country_code = $code;
  278. }
  279. /**
  280. * Add a new worksheet to the Excel workbook.
  281. * If no name is given the name of the worksheet will be Sheeti$i, with
  282. * $i in [1..].
  283. *
  284. * @access public
  285. * @param string $name the optional name of the worksheet
  286. * @param PHPExcel_Worksheet $phpSheet
  287. * @return mixed reference to a worksheet object on success
  288. */
  289. function &addWorksheet($name = '', $phpSheet = null)
  290. {
  291. $index = count($this->_worksheets);
  292. $sheetname = $this->_sheetname;
  293. if ($name == '') {
  294. $name = $sheetname.($index+1);
  295. }
  296. // Check that sheetname is <= 31 chars (Excel limit before BIFF8).
  297. if ($this->_BIFF_version != 0x0600)
  298. {
  299. if (strlen($name) > 31) {
  300. throw new Exception("Sheetname $name must be <= 31 chars");
  301. }
  302. }
  303. // Check that the worksheet name doesn't already exist: a fatal Excel error.
  304. $total_worksheets = count($this->_worksheets);
  305. for ($i = 0; $i < $total_worksheets; ++$i) {
  306. if ($this->_worksheets[$i]->getName() == $name) {
  307. throw new Exception("Worksheet '$name' already exists");
  308. }
  309. }
  310. $worksheet = new PHPExcel_Writer_Excel5_Worksheet($this->_BIFF_version,
  311. $name, $index,
  312. $this->_activesheet, $this->_firstsheet,
  313. $this->_str_total, $this->_str_unique,
  314. $this->_str_table, $this->_url_format,
  315. $this->_parser, $this->_tmp_dir,
  316. $phpSheet);
  317. $this->_worksheets[$index] = &$worksheet; // Store ref for iterator
  318. $this->_sheetnames[$index] = $name; // Store EXTERNSHEET names
  319. $this->_parser->setExtSheet($name, $index); // Register worksheet name with parser
  320. // for BIFF8
  321. if ($this->_BIFF_version == 0x0600) {
  322. $supbook_index = 0x00;
  323. $ref = pack('vvv', $supbook_index, $total_worksheets, $total_worksheets);
  324. $this->_parser->_references[] = $ref; // Register reference with parser
  325. }
  326. return $worksheet;
  327. }
  328. /**
  329. * Add a new format to the Excel workbook.
  330. * Also, pass any properties to the Format constructor.
  331. *
  332. * @access public
  333. * @param array $properties array with properties for initializing the format.
  334. * @return &PHPExcel_Writer_Excel5_Format reference to an Excel Format
  335. */
  336. function &addFormat($properties = array())
  337. {
  338. $format = new PHPExcel_Writer_Excel5_Format($this->_BIFF_version, $this->_xf_index, $properties);
  339. $this->_xf_index += 1;
  340. $this->_formats[] = &$format;
  341. return $format;
  342. }
  343. /**
  344. * Change the RGB components of the elements in the colour palette.
  345. *
  346. * @access public
  347. * @param integer $index colour index
  348. * @param integer $red red RGB value [0-255]
  349. * @param integer $green green RGB value [0-255]
  350. * @param integer $blue blue RGB value [0-255]
  351. * @return integer The palette index for the custom color
  352. */
  353. function setCustomColor($index, $red, $green, $blue)
  354. {
  355. // Match a HTML #xxyyzz style parameter
  356. /*if (defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
  357. @_ = ($_[0], hex $1, hex $2, hex $3);
  358. }*/
  359. // Check that the colour index is the right range
  360. if ($index < 8 or $index > 64) {
  361. // TODO: assign real error codes
  362. throw new Exception("Color index $index outside range: 8 <= index <= 64");
  363. }
  364. // Check that the colour components are in the right range
  365. if (($red < 0 or $red > 255) ||
  366. ($green < 0 or $green > 255) ||
  367. ($blue < 0 or $blue > 255))
  368. {
  369. throw new Exception("Color component outside range: 0 <= color <= 255");
  370. }
  371. $index -= 8; // Adjust colour index (wingless dragonfly)
  372. // Set the RGB value
  373. $this->_palette[$index] = array($red, $green, $blue, 0);
  374. return($index + 8);
  375. }
  376. /**
  377. * Sets the colour palette to the Excel 97+ default.
  378. *
  379. * @access private
  380. */
  381. function _setPaletteXl97()
  382. {
  383. $this->_palette = array(
  384. array(0x00, 0x00, 0x00, 0x00), // 8
  385. array(0xff, 0xff, 0xff, 0x00), // 9
  386. array(0xff, 0x00, 0x00, 0x00), // 10
  387. array(0x00, 0xff, 0x00, 0x00), // 11
  388. array(0x00, 0x00, 0xff, 0x00), // 12
  389. array(0xff, 0xff, 0x00, 0x00), // 13
  390. array(0xff, 0x00, 0xff, 0x00), // 14
  391. array(0x00, 0xff, 0xff, 0x00), // 15
  392. array(0x80, 0x00, 0x00, 0x00), // 16
  393. array(0x00, 0x80, 0x00, 0x00), // 17
  394. array(0x00, 0x00, 0x80, 0x00), // 18
  395. array(0x80, 0x80, 0x00, 0x00), // 19
  396. array(0x80, 0x00, 0x80, 0x00), // 20
  397. array(0x00, 0x80, 0x80, 0x00), // 21
  398. array(0xc0, 0xc0, 0xc0, 0x00), // 22
  399. array(0x80, 0x80, 0x80, 0x00), // 23
  400. array(0x99, 0x99, 0xff, 0x00), // 24
  401. array(0x99, 0x33, 0x66, 0x00), // 25
  402. array(0xff, 0xff, 0xcc, 0x00), // 26
  403. array(0xcc, 0xff, 0xff, 0x00), // 27
  404. array(0x66, 0x00, 0x66, 0x00), // 28
  405. array(0xff, 0x80, 0x80, 0x00), // 29
  406. array(0x00, 0x66, 0xcc, 0x00), // 30
  407. array(0xcc, 0xcc, 0xff, 0x00), // 31
  408. array(0x00, 0x00, 0x80, 0x00), // 32
  409. array(0xff, 0x00, 0xff, 0x00), // 33
  410. array(0xff, 0xff, 0x00, 0x00), // 34
  411. array(0x00, 0xff, 0xff, 0x00), // 35
  412. array(0x80, 0x00, 0x80, 0x00), // 36
  413. array(0x80, 0x00, 0x00, 0x00), // 37
  414. array(0x00, 0x80, 0x80, 0x00), // 38
  415. array(0x00, 0x00, 0xff, 0x00), // 39
  416. array(0x00, 0xcc, 0xff, 0x00), // 40
  417. array(0xcc, 0xff, 0xff, 0x00), // 41
  418. array(0xcc, 0xff, 0xcc, 0x00), // 42
  419. array(0xff, 0xff, 0x99, 0x00), // 43
  420. array(0x99, 0xcc, 0xff, 0x00), // 44
  421. array(0xff, 0x99, 0xcc, 0x00), // 45
  422. array(0xcc, 0x99, 0xff, 0x00), // 46
  423. array(0xff, 0xcc, 0x99, 0x00), // 47
  424. array(0x33, 0x66, 0xff, 0x00), // 48
  425. array(0x33, 0xcc, 0xcc, 0x00), // 49
  426. array(0x99, 0xcc, 0x00, 0x00), // 50
  427. array(0xff, 0xcc, 0x00, 0x00), // 51
  428. array(0xff, 0x99, 0x00, 0x00), // 52
  429. array(0xff, 0x66, 0x00, 0x00), // 53
  430. array(0x66, 0x66, 0x99, 0x00), // 54
  431. array(0x96, 0x96, 0x96, 0x00), // 55
  432. array(0x00, 0x33, 0x66, 0x00), // 56
  433. array(0x33, 0x99, 0x66, 0x00), // 57
  434. array(0x00, 0x33, 0x00, 0x00), // 58
  435. array(0x33, 0x33, 0x00, 0x00), // 59
  436. array(0x99, 0x33, 0x00, 0x00), // 60
  437. array(0x99, 0x33, 0x66, 0x00), // 61
  438. array(0x33, 0x33, 0x99, 0x00), // 62
  439. array(0x33, 0x33, 0x33, 0x00), // 63
  440. );
  441. }
  442. /**
  443. * Assemble worksheets into a workbook and send the BIFF data to an OLE
  444. * storage.
  445. *
  446. * @access private
  447. * @return mixed true on success
  448. */
  449. function _storeWorkbook()
  450. {
  451. if (count($this->_worksheets) == 0) {
  452. return true;
  453. }
  454. // Ensure that at least one worksheet has been selected.
  455. if ($this->_activesheet == 0) {
  456. $this->_worksheets[0]->selected = 1;
  457. }
  458. // Calculate the number of selected worksheet tabs and call the finalization
  459. // methods for each worksheet
  460. $total_worksheets = count($this->_worksheets);
  461. for ($i = 0; $i < $total_worksheets; ++$i) {
  462. if ($this->_worksheets[$i]->selected) {
  463. $this->_selected++;
  464. }
  465. $this->_worksheets[$i]->close($this->_sheetnames);
  466. }
  467. // Add part 1 of the Workbook globals, what goes before the SHEET records
  468. $this->_storeBof(0x0005);
  469. $this->_storeCodepage();
  470. if ($this->_BIFF_version == 0x0600) {
  471. $this->_storeWindow1();
  472. }
  473. if ($this->_BIFF_version == 0x0500) {
  474. $this->_storeExterns(); // For print area and repeat rows
  475. $this->_storeNames(); // For print area and repeat rows
  476. }
  477. if ($this->_BIFF_version == 0x0500) {
  478. $this->_storeWindow1();
  479. }
  480. $this->_storeDatemode();
  481. $this->_storeAllFonts();
  482. $this->_storeAllNumFormats();
  483. $this->_storeAllXfs();
  484. $this->_storeAllStyles();
  485. $this->_storePalette();
  486. $this->_calculateSharedStringsSizes();
  487. // Prepare part 3 of the workbook global stream, what goes after the SHEET records
  488. $part3 = '';
  489. if ($this->_country_code != -1) {
  490. $part3 .= $this->writeCountry();
  491. }
  492. if ($this->_BIFF_version == 0x0600) {
  493. $part3 .= $this->writeSupbookInternal();
  494. /* TODO: store external SUPBOOK records and XCT and CRN records
  495. in case of external references for BIFF8 */
  496. $part3 .= $this->writeExternsheetBiff8();
  497. $part3 .= $this->writeAllDefinedNamesBiff8();
  498. $part3 .= $this->writeSharedStringsTable();
  499. }
  500. $part3 .= $this->writeEof();
  501. // Add part 2 of the Workbook globals, the SHEET records
  502. $this->_calcSheetOffsets();
  503. for ($i = 0; $i < $total_worksheets; ++$i) {
  504. $this->_storeBoundsheet($this->_worksheets[$i]->name,$this->_worksheets[$i]->offset);
  505. }
  506. // Add part 3 of the Workbook globals
  507. $this->_data .= $part3;
  508. // Store the workbook in an OLE container
  509. $res = $this->_storeOLEFile();
  510. return true;
  511. }
  512. /**
  513. * Sets the temp dir used for storing the OLE file
  514. *
  515. * @access public
  516. * @param string $dir The dir to be used as temp dir
  517. * @return true if given dir is valid, false otherwise
  518. */
  519. function setTempDir($dir)
  520. {
  521. if (is_dir($dir)) {
  522. $this->_tmp_dir = $dir;
  523. return true;
  524. }
  525. return false;
  526. }
  527. /**
  528. * Store the workbook in an OLE container
  529. *
  530. * @access private
  531. * @return mixed true on success
  532. */
  533. function _storeOLEFile()
  534. {
  535. $OLE = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs('Book'));
  536. if ($this->_tmp_dir != '') {
  537. $OLE->setTempDir($this->_tmp_dir);
  538. }
  539. $res = $OLE->init();
  540. $OLE->append($this->_data);
  541. $total_worksheets = count($this->_worksheets);
  542. for ($i = 0; $i < $total_worksheets; ++$i) {
  543. while ($tmp = $this->_worksheets[$i]->getData()) {
  544. $OLE->append($tmp);
  545. }
  546. }
  547. $root = new PHPExcel_Shared_OLE_PPS_Root(time(), time(), array($OLE));
  548. if ($this->_tmp_dir != '') {
  549. $root->setTempDir($this->_tmp_dir);
  550. }
  551. $res = $root->save($this->_filename);
  552. return true;
  553. }
  554. /**
  555. * Calculate offsets for Worksheet BOF records.
  556. *
  557. * @access private
  558. */
  559. function _calcSheetOffsets()
  560. {
  561. if ($this->_BIFF_version == 0x0600) {
  562. $boundsheet_length = 12; // fixed length for a BOUNDSHEET record
  563. } else {
  564. $boundsheet_length = 11;
  565. }
  566. // size of Workbook globals part 1 + 3
  567. $offset = $this->_datasize;
  568. // add size of Workbook globals part 2, the length of the SHEET records
  569. $total_worksheets = count($this->_worksheets);
  570. for ($i = 0; $i < $total_worksheets; ++$i) {
  571. if ($this->_BIFF_version == 0x0600) {
  572. if (function_exists('mb_strlen') and function_exists('mb_convert_encoding')) {
  573. // sheet name is stored in uncompressed notation
  574. $offset += $boundsheet_length + 2 * mb_strlen($this->_worksheets[$i]->name, 'UTF-8');
  575. } else {
  576. // sheet name is stored in compressed notation, and ASCII is assumed
  577. $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
  578. }
  579. } else {
  580. $offset += $boundsheet_length + strlen($this->_worksheets[$i]->name);
  581. }
  582. }
  583. // add the sizes of each of the Sheet substreams, respectively
  584. for ($i = 0; $i < $total_worksheets; ++$i) {
  585. $this->_worksheets[$i]->offset = $offset;
  586. $offset += $this->_worksheets[$i]->_datasize;
  587. }
  588. $this->_biffsize = $offset;
  589. }
  590. /**
  591. * Store the Excel FONT records.
  592. *
  593. * @access private
  594. */
  595. function _storeAllFonts()
  596. {
  597. // tmp_format is added by the constructor. We use this to write the default XF's
  598. $format = $this->_tmp_format;
  599. $font = $format->getFont();
  600. // Note: Fonts are 0-indexed. According to the SDK there is no index 4,
  601. // so the following fonts are 0, 1, 2, 3, 5
  602. //
  603. for ($i = 1; $i <= 5; ++$i){
  604. $this->_append($font);
  605. }
  606. // Iterate through the XF objects and write a FONT record if it isn't the
  607. // same as the default FONT and if it hasn't already been used.
  608. //
  609. $fonts = array();
  610. $index = 6; // The first user defined FONT
  611. $key = $format->getFontKey(); // The default font from _tmp_format
  612. $fonts[$key] = 0; // Index of the default font
  613. $total_formats = count($this->_formats);
  614. for ($i = 0; $i < $total_formats; ++$i) {
  615. $key = $this->_formats[$i]->getFontKey();
  616. if (isset($fonts[$key])) {
  617. // FONT has already been used
  618. $this->_formats[$i]->font_index = $fonts[$key];
  619. } else {
  620. // Add a new FONT record
  621. $fonts[$key] = $index;
  622. $this->_formats[$i]->font_index = $index;
  623. ++$index;
  624. $font = $this->_formats[$i]->getFont();
  625. $this->_append($font);
  626. }
  627. }
  628. }
  629. /**
  630. * Store user defined numerical formats i.e. FORMAT records
  631. *
  632. * @access private
  633. */
  634. function _storeAllNumFormats()
  635. {
  636. // Leaning num_format syndrome
  637. $hash_num_formats = array();
  638. $num_formats = array();
  639. $index = 164;
  640. // Iterate through the XF objects and write a FORMAT record if it isn't a
  641. // built-in format type and if the FORMAT string hasn't already been used.
  642. $total_formats = count($this->_formats);
  643. for ($i = 0; $i < $total_formats; ++$i) {
  644. $num_format = $this->_formats[$i]->_num_format;
  645. //////////////////////////////////////////////////////////////////////////////////////////
  646. // Removing this block for now. No true support for built-in number formats in PHPExcel //
  647. //////////////////////////////////////////////////////////////////////////////////////////
  648. /**
  649. // Check if $num_format is an index to a built-in format.
  650. // Also check for a string of zeros, which is a valid format string
  651. // but would evaluate to zero.
  652. //
  653. if (!preg_match("/^0+\d/", $num_format)) {
  654. if (preg_match("/^\d+$/", $num_format)) { // built-in format
  655. continue;
  656. }
  657. }
  658. **/
  659. if (isset($hash_num_formats[$num_format])) {
  660. // FORMAT has already been used
  661. $this->_formats[$i]->_num_format = $hash_num_formats[$num_format];
  662. } else{
  663. // Add a new FORMAT
  664. $hash_num_formats[$num_format] = $index;
  665. $this->_formats[$i]->_num_format = $index;
  666. $num_formats[] = $num_format;
  667. ++$index;
  668. }
  669. }
  670. // Write the new FORMAT records starting from 0xA4
  671. $index = 164;
  672. foreach ($num_formats as $num_format) {
  673. $this->_storeNumFormat($num_format,$index);
  674. ++$index;
  675. }
  676. }
  677. /**
  678. * Write all XF records.
  679. *
  680. * @access private
  681. */
  682. function _storeAllXfs()
  683. {
  684. // _tmp_format is added by the constructor. We use this to write the default XF's
  685. // The default font index is 0
  686. //
  687. $format = $this->_tmp_format;
  688. for ($i = 0; $i <= 14; ++$i) {
  689. $xf = $format->getXf('style'); // Style XF
  690. $this->_append($xf);
  691. }
  692. $xf = $format->getXf('cell'); // Cell XF
  693. $this->_append($xf);
  694. // User defined XFs
  695. $total_formats = count($this->_formats);
  696. for ($i = 0; $i < $total_formats; ++$i) {
  697. $xf = $this->_formats[$i]->getXf('cell');
  698. $this->_append($xf);
  699. }
  700. }
  701. /**
  702. * Write all STYLE records.
  703. *
  704. * @access private
  705. */
  706. function _storeAllStyles()
  707. {
  708. $this->_storeStyle();
  709. }
  710. /**
  711. * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
  712. * the NAME records.
  713. *
  714. * @access private
  715. */
  716. function _storeExterns()
  717. {
  718. // Create EXTERNCOUNT with number of worksheets
  719. $this->_storeExterncount(count($this->_worksheets));
  720. // Create EXTERNSHEET for each worksheet
  721. foreach ($this->_sheetnames as $sheetname) {
  722. $this->_storeExternsheet($sheetname);
  723. }
  724. }
  725. /**
  726. * Write the NAME record to define the print area and the repeat rows and cols.
  727. *
  728. * @access private
  729. */
  730. function _storeNames()
  731. {
  732. // Create the print area NAME records
  733. $total_worksheets = count($this->_worksheets);
  734. for ($i = 0; $i < $total_worksheets; ++$i) {
  735. // Write a Name record if the print area has been defined
  736. if (isset($this->_worksheets[$i]->print_rowmin)) {
  737. $this->_storeNameShort(
  738. $this->_worksheets[$i]->index,
  739. 0x06, // NAME type
  740. $this->_worksheets[$i]->print_rowmin,
  741. $this->_worksheets[$i]->print_rowmax,
  742. $this->_worksheets[$i]->print_colmin,
  743. $this->_worksheets[$i]->print_colmax
  744. );
  745. }
  746. }
  747. // Create the print title NAME records
  748. $total_worksheets = count($this->_worksheets);
  749. for ($i = 0; $i < $total_worksheets; ++$i) {
  750. $rowmin = $this->_worksheets[$i]->title_rowmin;
  751. $rowmax = $this->_worksheets[$i]->title_rowmax;
  752. $colmin = $this->_worksheets[$i]->title_colmin;
  753. $colmax = $this->_worksheets[$i]->title_colmax;
  754. // Determine if row + col, row, col or nothing has been defined
  755. // and write the appropriate record
  756. //
  757. if (isset($rowmin) && isset($colmin)) {
  758. // Row and column titles have been defined.
  759. // Row title has been defined.
  760. $this->_storeNameLong(
  761. $this->_worksheets[$i]->index,
  762. 0x07, // NAME type
  763. $rowmin,
  764. $rowmax,
  765. $colmin,
  766. $colmax
  767. );
  768. } elseif (isset($rowmin)) {
  769. // Row title has been defined.
  770. $this->_storeNameShort(
  771. $this->_worksheets[$i]->index,
  772. 0x07, // NAME type
  773. $rowmin,
  774. $rowmax,
  775. 0x00,
  776. 0xff
  777. );
  778. } elseif (isset($colmin)) {
  779. // Column title has been defined.
  780. $this->_storeNameShort(
  781. $this->_worksheets[$i]->index,
  782. 0x07, // NAME type
  783. 0x0000,
  784. 0x3fff,
  785. $colmin,
  786. $colmax
  787. );
  788. } else {
  789. // Print title hasn't been defined.
  790. }
  791. }
  792. }
  793. /**
  794. * Writes all the DEFINEDNAME records (BIFF8).
  795. * So far this is only used for repeating rows/columns (print titles) and print areas
  796. */
  797. public function writeAllDefinedNamesBiff8()
  798. {
  799. $chunk = '';
  800. // write the print titles (repeating rows, columns), if any
  801. $total_worksheets = count($this->_worksheets);
  802. for ($i = 0; $i < $total_worksheets; ++$i) {
  803. // repeatColumns / repeatRows
  804. if ($this->_phpExcel->getSheet($i)->getPageSetup()->isColumnsToRepeatAtLeftSet() || $this->_phpExcel->getSheet($i)->getPageSetup()->isRowsToRepeatAtTopSet()) {
  805. // Row and column titles have been defined
  806. // Columns to repeat
  807. if ($this->_phpExcel->getSheet($i)->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
  808. $repeat = $this->_phpExcel->getSheet($i)->getPageSetup()->getColumnsToRepeatAtLeft();
  809. $colmin = PHPExcel_Cell::columnIndexFromString($repeat[0]) - 1;
  810. $colmax = PHPExcel_Cell::columnIndexFromString($repeat[1]) - 1;
  811. } else {
  812. $colmin = 0;
  813. $colmax = 255;
  814. }
  815. // Rows to repeat
  816. if ($this->_phpExcel->getSheet($i)->getPageSetup()->isRowsToRepeatAtTopSet()) {
  817. $repeat = $this->_phpExcel->getSheet($i)->getPageSetup()->getRowsToRepeatAtTop();
  818. $rowmin = $repeat[0] - 1;
  819. $rowmax = $repeat[1] - 1;
  820. } else {
  821. $rowmin = 0;
  822. $rowmax = 65535;
  823. }
  824. // construct formula data manually because parser does not recognize absolute 3d cell references
  825. $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
  826. // store the DEFINEDNAME record
  827. $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
  828. }
  829. }
  830. // write the print areas, if any
  831. for ($i = 0; $i < $total_worksheets; ++$i) {
  832. if ($this->_phpExcel->getSheet($i)->getPageSetup()->isPrintAreaSet()) {
  833. // Print area
  834. $printArea = PHPExcel_Cell::splitRange($this->_phpExcel->getSheet($i)->getPageSetup()->getPrintArea());
  835. $printArea[0] = PHPExcel_Cell::coordinateFromString($printArea[0]);
  836. $printArea[1] = PHPExcel_Cell::coordinateFromString($printArea[1]);
  837. $print_rowmin = $printArea[0][1] - 1;
  838. $print_rowmax = $printArea[1][1] - 1;
  839. $print_colmin = PHPExcel_Cell::columnIndexFromString($printArea[0][0]) - 1;
  840. $print_colmax = PHPExcel_Cell::columnIndexFromString($printArea[1][0]) - 1;
  841. // construct formula data manually because parser does not recognize absolute 3d cell references
  842. $formulaData = pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
  843. // store the DEFINEDNAME record
  844. $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
  845. }
  846. }
  847. return $chunk;
  848. }
  849. /**
  850. * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data
  851. *
  852. * @param string $name The name in UTF-8
  853. * @param string $formulaData The binary formula data
  854. * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
  855. * @param boolean $isBuiltIn Built-in name?
  856. * @return string Complete binary record data
  857. */
  858. public function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
  859. {
  860. $record = 0x0018;
  861. // option flags
  862. $options = $isBuiltIn ? 0x20 : 0x00;
  863. // length of the name, character count
  864. $nlen = function_exists('mb_strlen') ?
  865. mb_strlen($name, 'UTF8') : strlen($name);
  866. // name with stripped length field
  867. $name = substr(PHPExcel_Shared_String::UTF8toBIFF8UnicodeLong($name), 2);
  868. // size of the formula (in bytes)
  869. $sz = strlen($formulaData);
  870. // combine the parts
  871. $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
  872. . $name . $formulaData;
  873. $length = strlen($data);
  874. $header = pack('vv', $record, $length);
  875. return $header . $data;
  876. }
  877. /******************************************************************************
  878. *
  879. * BIFF RECORDS
  880. *
  881. */
  882. /**
  883. * Stores the CODEPAGE biff record.
  884. *
  885. * @access private
  886. */
  887. function _storeCodepage()
  888. {
  889. $record = 0x0042; // Record identifier
  890. $length = 0x0002; // Number of bytes to follow
  891. $cv = $this->_codepage; // The code page
  892. $header = pack('vv', $record, $length);
  893. $data = pack('v', $cv);
  894. $this->_append($header . $data);
  895. }
  896. /**
  897. * Write Excel BIFF WINDOW1 record.
  898. *
  899. * @access private
  900. */
  901. function _storeWindow1()
  902. {
  903. $record = 0x003D; // Record identifier
  904. $length = 0x0012; // Number of bytes to follow
  905. $xWn = 0x0000; // Horizontal position of window
  906. $yWn = 0x0000; // Vertical position of window
  907. $dxWn = 0x25BC; // Width of window
  908. $dyWn = 0x1572; // Height of window
  909. $grbit = 0x0038; // Option flags
  910. $ctabsel = $this->_selected; // Number of workbook tabs selected
  911. $wTabRatio = 0x0258; // Tab to scrollbar ratio
  912. $itabFirst = $this->_firstsheet; // 1st displayed worksheet
  913. $itabCur = $this->_activesheet; // Active worksheet
  914. $header = pack("vv", $record, $length);
  915. $data = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn,
  916. $grbit,
  917. $itabCur, $itabFirst,
  918. $ctabsel, $wTabRatio);
  919. $this->_append($header . $data);
  920. }
  921. /**
  922. * Writes Excel BIFF BOUNDSHEET record.
  923. * FIXME: inconsistent with BIFF documentation
  924. *
  925. * @param string $sheetname Worksheet name
  926. * @param integer $offset Location of worksheet BOF
  927. * @access private
  928. */
  929. function _storeBoundsheet($sheetname,$offset)
  930. {
  931. $record = 0x0085; // Record identifier
  932. if ($this->_BIFF_version == 0x0600) {
  933. //$recordData = $this->_writeUnicodeDataShort($sheetname);
  934. $recordData = PHPExcel_Shared_String::UTF8toBIFF8UnicodeShort($sheetname);
  935. $length = 0x06 + strlen($recordData); // Number of bytes to follow
  936. } else {
  937. $length = 0x07 + strlen($sheetname); // Number of bytes to follow
  938. }
  939. $grbit = 0x0000; // Visibility and sheet type
  940. $header = pack("vv", $record, $length);
  941. if ($this->_BIFF_version == 0x0600) {
  942. $data = pack("Vv", $offset, $grbit);
  943. $this->_append($header.$data.$recordData);
  944. } else {
  945. $cch = strlen($sheetname); // Length of sheet name
  946. $data = pack("VvC", $offset, $grbit, $cch);
  947. $this->_append($header.$data.$sheetname);
  948. }
  949. }
  950. /**
  951. * Write Internal SUPBOOK record
  952. *
  953. * @access private
  954. */
  955. public function writeSupbookInternal()
  956. {
  957. $record = 0x01AE; // Record identifier
  958. $length = 0x0004; // Bytes to follow
  959. $header = pack("vv", $record, $length);
  960. //$data = pack("vv", count($this->_worksheets), 0x0104);
  961. $data = pack("vv", count($this->_worksheets), 0x0401);
  962. //$this->_append($header . $data);
  963. return $this->writeData($header . $data);
  964. }
  965. /**
  966. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  967. * formulas.
  968. *
  969. * @param string $sheetname Worksheet name
  970. * @access private
  971. */
  972. public function writeExternsheetBiff8()
  973. {
  974. $total_references = count($this->_parser->_references);
  975. $record = 0x0017; // Record identifier
  976. $length = 2 + 6 * $total_references; // Number of bytes to follow
  977. $supbook_index = 0; // FIXME: only using internal SUPBOOK record
  978. $header = pack("vv", $record, $length);
  979. $data = pack('v', $total_references);
  980. for ($i = 0; $i < $total_references; ++$i) {
  981. $data .= $this->_parser->_references[$i];
  982. }
  983. //$this->_append($header . $data);
  984. return $this->writeData($header . $data);
  985. }
  986. /**
  987. * Write Excel BIFF STYLE records.
  988. *
  989. * @access private
  990. */
  991. function _storeStyle()
  992. {
  993. $record = 0x0293; // Record identifier
  994. $length = 0x0004; // Bytes to follow
  995. $ixfe = 0x8000; // Index to style XF
  996. $BuiltIn = 0x00; // Built-in style
  997. $iLevel = 0xff; // Outline style level
  998. $header = pack("vv", $record, $length);
  999. $data = pack("vCC", $ixfe, $BuiltIn, $iLevel);
  1000. $this->_append($header . $data);
  1001. }
  1002. /**
  1003. * Writes Excel FORMAT record for non "built-in" numerical formats.
  1004. *
  1005. * @param string $format Custom format string
  1006. * @param integer $ifmt Format index code
  1007. * @access private
  1008. */
  1009. function _storeNumFormat($format, $ifmt)
  1010. {
  1011. $record = 0x041E; // Record identifier
  1012. if ($this->_BIFF_version == 0x0600) {
  1013. //$numberFormatString = $this->_writeUnicodeDataLong($format);
  1014. $numberFormatString = PHPExcel_Shared_String::UTF8toBIFF8UnicodeLong($format);
  1015. $length = 2 + strlen($numberFormatString); // Number of bytes to follow
  1016. } elseif ($this->_BIFF_version == 0x0500) {
  1017. $length = 3 + strlen($format); // Number of bytes to follow
  1018. }
  1019. $header = pack("vv", $record, $length);
  1020. if ($this->_BIFF_version == 0x0600) {
  1021. $data = pack("v", $ifmt) . $numberFormatString;
  1022. $this->_append($header . $data);
  1023. } elseif ($this->_BIFF_version == 0x0500) {
  1024. $cch = strlen($format); // Length of format string
  1025. $data = pack("vC", $ifmt, $cch);
  1026. $this->_append($header . $data . $format);
  1027. }
  1028. }
  1029. /**
  1030. * Write DATEMODE record to indicate the date system in use (1904 or 1900).
  1031. *
  1032. * @access private
  1033. */
  1034. function _storeDatemode()
  1035. {
  1036. $record = 0x0022; // Record identifier
  1037. $length = 0x0002; // Bytes to follow
  1038. $f1904 = (PHPExcel_Shared_Date::getExcelCalendar() == PHPExcel_Shared_Date::CALENDAR_MAC_1904) ?
  1039. 1 : 0; // Flag for 1904 date system
  1040. $header = pack("vv", $record, $length);
  1041. $data = pack("v", $f1904);
  1042. $this->_append($header . $data);
  1043. }
  1044. /**
  1045. * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  1046. * references in the workbook.
  1047. *
  1048. * Excel only stores references to external sheets that are used in NAME.
  1049. * The workbook NAME record is required to define the print area and the repeat
  1050. * rows and columns.
  1051. *
  1052. * A similar method is used in Worksheet.php for a slightly different purpose.
  1053. *
  1054. * @param integer $cxals Number of external references
  1055. * @access private
  1056. */
  1057. function _storeExterncount($cxals)
  1058. {
  1059. $record = 0x0016; // Record identifier
  1060. $length = 0x0002; // Number of bytes to follow
  1061. $header = pack("vv", $record, $length);
  1062. $data = pack("v", $cxals);
  1063. $this->_append($header . $data);
  1064. }
  1065. /**
  1066. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  1067. * formulas. NAME record is required to define the print area and the repeat
  1068. * rows and columns.
  1069. *
  1070. * A similar method is used in Worksheet.php for a slightly different purpose.
  1071. *
  1072. * @param string $sheetname Worksheet name
  1073. * @access private
  1074. */
  1075. function _storeExternsheet($sheetname)
  1076. {
  1077. $record = 0x0017; // Record identifier
  1078. $length = 0x02 + strlen($sheetname); // Number of bytes to follow
  1079. $cch = strlen($sheetname); // Length of sheet name
  1080. $rgch = 0x03; // Filename encoding
  1081. $header = pack("vv", $record, $length);
  1082. $data = pack("CC", $cch, $rgch);
  1083. $this->_append($header . $data . $sheetname);
  1084. }
  1085. /**
  1086. * Store the NAME record in the short format that is used for storing the print
  1087. * area, repeat rows only and repeat columns only.
  1088. *
  1089. * @param integer $index Sheet index
  1090. * @param integer $type Built-in name type
  1091. * @param integer $rowmin Start row
  1092. * @param integer $rowmax End row
  1093. * @param integer $colmin Start colum
  1094. * @param integer $colmax End column
  1095. * @access private
  1096. */
  1097. function _storeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  1098. {
  1099. $record = 0x0018; // Record identifier
  1100. $length = 0x0024; // Number of bytes to follow
  1101. $grbit = 0x0020; // Option flags
  1102. $chKey = 0x00; // Keyboard shortcut
  1103. $cch = 0x01; // Length of text name
  1104. $cce = 0x0015; // Length of text definition
  1105. $ixals = $index + 1; // Sheet index
  1106. $itab = $ixals; // Equal to ixals
  1107. $cchCustMenu = 0x00; // Length of cust menu text
  1108. $cchDescription = 0x00; // Length of description text
  1109. $cchHelptopic = 0x00; // Length of help topic text
  1110. $cchStatustext = 0x00; // Length of status bar text
  1111. $rgch = $type; // Built-in name type
  1112. $unknown03 = 0x3b;
  1113. $unknown04 = 0xffff-$index;
  1114. $unknown05 = 0x0000;
  1115. $unknown06 = 0x0000;
  1116. $unknown07 = 0x1087;
  1117. $unknown08 = 0x8005;
  1118. $header = pack("vv", $record, $length);
  1119. $data = pack("v", $grbit);
  1120. $data .= pack("C", $chKey);
  1121. $data .= pack("C", $cch);
  1122. $data .= pack("v", $cce);
  1123. $data .= pack("v", $ixals);
  1124. $data .= pack("v", $itab);
  1125. $data .= pack("C", $cchCustMenu);
  1126. $data .= pack("C", $cchDescription);
  1127. $data .= pack("C", $cchHelptopic);
  1128. $data .= pack("C", $cchStatustext);
  1129. $data .= pack("C", $rgch);
  1130. $data .= pack("C", $unknown03);
  1131. $data .= pack("v", $unknown04);
  1132. $data .= pack("v", $unknown05);
  1133. $data .= pack("v", $unknown06);
  1134. $data .= pack("v", $unknown07);
  1135. $data .= pack("v", $unknown08);
  1136. $data .= pack("v", $index);
  1137. $data .= pack("v", $index);
  1138. $data .= pack("v", $rowmin);
  1139. $data .= pack("v", $rowmax);
  1140. $data .= pack("C", $colmin);
  1141. $data .= pack("C", $colmax);
  1142. $this->_append($header . $data);
  1143. }
  1144. /**
  1145. * Store the NAME record in the long format that is used for storing the repeat
  1146. * rows and columns when both are specified. This shares a lot of code with
  1147. * _storeNameShort() but we use a separate method to keep the code clean.
  1148. * Code abstraction for reuse can be carried too far, and I should know. ;-)
  1149. *
  1150. * @param integer $index Sheet index
  1151. * @param integer $type Built-in name type
  1152. * @param integer $rowmin Start row
  1153. * @param integer $rowmax End row
  1154. * @param integer $colmin Start colum
  1155. * @param integer $colmax End column
  1156. * @access private
  1157. */
  1158. function _storeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  1159. {
  1160. $record = 0x0018; // Record identifier
  1161. $length = 0x003d; // Number of bytes to follow
  1162. $grbit = 0x0020; // Option flags
  1163. $chKey = 0x00; // Keyboard shortcut
  1164. $cch = 0x01; // Length of text name
  1165. $cce = 0x002e; // Length of text definition
  1166. $ixals = $index + 1; // Sheet index
  1167. $itab = $ixals; // Equal to ixals
  1168. $cchCustMenu = 0x00; // Length of cust menu text
  1169. $cchDescription = 0x00; // Length of description text
  1170. $cchHelptopic = 0x00; // Length of help topic text
  1171. $cchStatustext = 0x00; // Length of status bar text
  1172. $rgch = $type; // Built-in name type
  1173. $unknown01 = 0x29;
  1174. $unknown02 = 0x002b;
  1175. $unknown03 = 0x3b;
  1176. $unknown04 = 0xffff-$index;
  1177. $unknown05 = 0x0000;
  1178. $unknown06 = 0x0000;
  1179. $unknown07 = 0x1087;
  1180. $unknown08 = 0x8008;
  1181. $header = pack("vv", $record, $length);
  1182. $data = pack("v", $grbit);
  1183. $data .= pack("C", $chKey);
  1184. $data .= pack("C", $cch);
  1185. $data .= pack("v", $cce);
  1186. $data .= pack("v", $ixals);
  1187. $data .= pack("v", $itab);
  1188. $data .= pack("C", $cchCustMenu);
  1189. $data .= pack("C", $cchDescription);
  1190. $data .= pack("C", $cchHelptopic);
  1191. $data .= pack("C", $cchStatustext);
  1192. $data .= pack("C", $rgch);
  1193. $data .= pack("C", $unknown01);
  1194. $data .= pack("v", $unknown02);
  1195. // Column definition
  1196. $data .= pack("C", $unknown03);
  1197. $data .= pack("v", $unknown04);
  1198. $data .= pack("v", $unknown05);
  1199. $data .= pack("v", $unknown06);
  1200. $data .= pack("v", $unknown07);
  1201. $data .= pack("v", $unknown08);
  1202. $data .= pack("v", $index);
  1203. $data .= pack("v", $index);
  1204. $data .= pack("v", 0x0000);
  1205. $data .= pack("v", 0x3fff);
  1206. $data .= pack("C", $colmin);
  1207. $data .= pack("C", $colmax);
  1208. // Row definition
  1209. $data .= pack("C", $unknown03);
  1210. $data .= pack("v", $unknown04);
  1211. $data .= pack("v", $unknown05);
  1212. $data .= pack("v", $unknown06);
  1213. $data .= pack("v", $unknown07);
  1214. $data .= pack("v", $unknown08);
  1215. $data .= pack("v", $index);
  1216. $data .= pack("v", $index);
  1217. $data .= pack("v", $rowmin);
  1218. $data .= pack("v", $rowmax);
  1219. $data .= pack("C", 0x00);
  1220. $data .= pack("C", 0xff);
  1221. // End of data
  1222. $data .= pack("C", 0x10);
  1223. $this->_append($header . $data);
  1224. }
  1225. /**
  1226. * Stores the COUNTRY record for localization
  1227. *
  1228. * @return string
  1229. */
  1230. public function writeCountry()
  1231. {
  1232. $record = 0x008C; // Record identifier
  1233. $length = 4; // Number of bytes to follow
  1234. $header = pack('vv', $record, $length);
  1235. /* using the same country code always for simplicity */
  1236. $data = pack('vv', $this->_country_code, $this->_country_code);
  1237. //$this->_append($header . $data);
  1238. return $this->writeData($header . $data);
  1239. }
  1240. /**
  1241. * Stores the PALETTE biff record.
  1242. *
  1243. * @access private
  1244. */
  1245. function _storePalette()
  1246. {
  1247. $aref = $this->_palette;
  1248. $record = 0x0092; // Record identifier
  1249. $length = 2 + 4 * count($aref); // Number of bytes to follow
  1250. $ccv = count($aref); // Number of RGB values to follow
  1251. $data = ''; // The RGB data
  1252. // Pack the RGB data
  1253. foreach ($aref as $color) {
  1254. foreach ($color as $byte) {
  1255. $data .= pack("C",$byte);
  1256. }
  1257. }
  1258. $header = pack("vvv", $record, $length, $ccv);
  1259. $this->_append($header . $data);
  1260. }
  1261. /**
  1262. * Calculate
  1263. * Handling of the SST continue blocks is complicated by the need to include an
  1264. * additional continuation byte depending on whether the string is split between
  1265. * blocks or whether it starts at the beginning of the block. (There are also
  1266. * additional complications that will arise later when/if Rich Strings are
  1267. * supported).
  1268. *
  1269. * @access private
  1270. */
  1271. function _calculateSharedStringsSizes()
  1272. {
  1273. /* Iterate through the strings to calculate the CONTINUE block sizes.
  1274. For simplicity we use the same size for the SST and CONTINUE records:
  1275. 8228 : Maximum Excel97 block size
  1276. -4 : Length of block header
  1277. -8 : Length of additional SST header information
  1278. = 8216
  1279. */
  1280. $continue_limit = 8208;
  1281. $block_length = 0;
  1282. $written = 0;
  1283. $this->_block_sizes = array();
  1284. $continue = 0;
  1285. foreach (array_keys($this->_str_table) as $string) {
  1286. $string_length = strlen($string);
  1287. $headerinfo = unpack("vlength/Cencoding", $string);
  1288. $encoding = $headerinfo["encoding"];
  1289. $split_string = 0;
  1290. // Block length is the total length of the strings that will be
  1291. // written out in a single SST or CONTINUE block.
  1292. $block_length += $string_length;
  1293. // We can write the string if it doesn't cross a CONTINUE boundary
  1294. if ($block_length < $continue_limit) {
  1295. $written += $string_length;
  1296. continue;
  1297. }
  1298. // Deal with the cases where the next string to be written will exceed
  1299. // the CONTINUE boundary. If the string is very long it may need to be
  1300. // written in more than one CONTINUE record.
  1301. while ($block_length >= $continue_limit) {
  1302. // We need to avoid the case where a string is continued in the first
  1303. // n bytes that contain the string header information.
  1304. $header_length = 3; // Min string + header size -1
  1305. $space_remaining = $continue_limit - $written - $continue;
  1306. /* TODO: Unicode data should only be split on char (2 byte)
  1307. boundaries. Therefore, in some cases we need to reduce the
  1308. amount of available
  1309. */
  1310. $align = 0;
  1311. // Only applies to Unicode strings
  1312. if ($encoding == 1) {
  1313. // Min string + header size -1
  1314. $header_length = 4;
  1315. if ($space_remaining > $header_length) {
  1316. // String contains 3 byte header => split on odd boundary
  1317. if (!$split_string && $space_remaining % 2 != 1) {
  1318. --$space_remaining;
  1319. $align = 1;
  1320. }
  1321. // Split section without header => split on even boundary
  1322. else if ($split_string && $space_remaining % 2 == 1) {
  1323. --$space_remaining;
  1324. $align = 1;
  1325. }
  1326. $split_string = 1;
  1327. }
  1328. }
  1329. if ($space_remaining > $header_length) {
  1330. // Write as much as possible of the string in the current block
  1331. $written += $space_remaining;
  1332. // Reduce the current block length by the amount written
  1333. $block_length -= $continue_limit - $continue - $align;
  1334. // Store the max size for this block
  1335. $this->_block_sizes[] = $continue_limit - $align;
  1336. // If the current string was split then the next CONTINUE block
  1337. // should have the string continue flag (grbit) set unless the
  1338. // split string fits exactly into the remaining space.
  1339. if ($block_length > 0) {
  1340. $continue = 1;
  1341. } else {
  1342. $continue = 0;
  1343. }
  1344. } else {
  1345. // Store the max size for this block
  1346. $this->_block_sizes[] = $written + $continue;
  1347. // Not enough space to start the string in the current block
  1348. $block_length -= $continue_limit - $space_remaining - $continue;
  1349. $continue = 0;
  1350. }
  1351. // If the string (or substr) is small enough we can write it in the
  1352. // new CONTINUE block. Else, go through the loop again to write it in
  1353. // one or more CONTINUE blocks
  1354. if ($block_length < $continue_limit) {
  1355. $written = $block_length;
  1356. } else {
  1357. $written = 0;
  1358. }
  1359. }
  1360. }
  1361. // Store the max size for the last block unless it is empty
  1362. if ($written + $continue) {
  1363. $this->_block_sizes[] = $written + $continue;
  1364. }
  1365. /* Calculate the total length of the SST and associated CONTINUEs (if any).
  1366. The SST record will have a length even if it contains no strings.
  1367. This length is required to set the offsets in the BOUNDSHEET records since
  1368. they must be written before the SST records
  1369. */
  1370. $tmp_block_sizes = array();
  1371. $tmp_block_sizes = $this->_block_sizes;
  1372. $length = 12;
  1373. if (!empty($tmp_block_sizes)) {
  1374. $length += array_shift($tmp_block_sizes); // SST information
  1375. }
  1376. while (!empty($tmp_block_sizes)) {
  1377. $length += 4 + array_shift($tmp_block_sizes); // add CONTINUE headers
  1378. }
  1379. return $length;
  1380. }
  1381. /**
  1382. * Write all of the workbooks strings into an indexed array.
  1383. * See the comments in _calculate_shared_string_sizes() for more information.
  1384. *
  1385. * The Excel documentation says that the SST record should be followed by an
  1386. * EXTSST record. The EXTSST record is a hash table that is used to optimise
  1387. * access to SST. However, despite the documentation it doesn't seem to be
  1388. * required so we will ignore it.
  1389. *
  1390. * @access private
  1391. */
  1392. public function writeSharedStringsTable()
  1393. {
  1394. $chunk = '';
  1395. $record = 0x00fc; // Record identifier
  1396. $length = 0x0008; // Number of bytes to follow
  1397. $total = 0x0000;
  1398. // Iterate through the strings to calculate the CONTINUE block sizes
  1399. $continue_limit = 8208;
  1400. $block_length = 0;
  1401. $written = 0;
  1402. $continue = 0;
  1403. // sizes are upside down
  1404. $tmp_block_sizes = $this->_block_sizes;
  1405. // $tmp_block_sizes = array_reverse($this->_block_sizes);
  1406. // The SST record is required even if it contains no strings. Thus we will
  1407. // always have a length
  1408. //
  1409. if (!empty($tmp_block_sizes)) {
  1410. $length = 8 + array_shift($tmp_block_sizes);
  1411. } else {
  1412. // No strings
  1413. $length = 8;
  1414. }
  1415. // Write the SST block header information
  1416. $header = pack("vv", $record, $length);
  1417. $data = pack("VV", $this->_str_total, $this->_str_unique);
  1418. //$this->_append($header . $data);
  1419. $chunk .= $this->writeData($header . $data);
  1420. /* TODO: not good for performance */
  1421. foreach (array_keys($this->_str_table) as $string) {
  1422. $string_length = strlen($string);
  1423. $headerinfo = unpack("vlength/Cencoding", $string);
  1424. $encoding = $headerinfo["encoding"];
  1425. $split_string = 0;
  1426. // Block length is the total length of the strings that will be
  1427. // written out in a single SST or CONTINUE block.
  1428. //
  1429. $block_length += $string_length;
  1430. // We can write the string if it doesn't cross a CONTINUE boundary
  1431. if ($block_length < $continue_limit) {
  1432. //$this->_append($string);
  1433. $chunk .= $this->writeData($string);
  1434. $written += $string_length;
  1435. continue;
  1436. }
  1437. // Deal with the cases where the next string to be written will exceed
  1438. // the CONTINUE boundary. If the string is very long it may need to be
  1439. // written in more than one CONTINUE record.
  1440. //
  1441. while ($block_length >= $continue_limit) {
  1442. // We need to avoid the case where a string is continued in the first
  1443. // n bytes that contain the string header information.
  1444. //
  1445. $header_length = 3; // Min string + header size -1
  1446. $space_remaining = $continue_limit - $written - $continue;
  1447. // Unicode data should only be split on char (2 byte) boundaries.
  1448. // Therefore, in some cases we need to reduce the amount of available
  1449. // space by 1 byte to ensure the correct alignment.
  1450. $align = 0;
  1451. // Only applies to Unicode strings
  1452. if ($encoding == 1) {
  1453. // Min string + header size -1
  1454. $header_length = 4;
  1455. if ($space_remaining > $header_length) {
  1456. // String contains 3 byte header => split on odd boundary
  1457. if (!$split_string && $space_remaining % 2 != 1) {
  1458. --$space_remaining;
  1459. $align = 1;
  1460. }
  1461. // Split section without header => split on even boundary
  1462. else if ($split_string && $space_remaining % 2 == 1) {
  1463. --$space_remaining;
  1464. $align = 1;
  1465. }
  1466. $split_string = 1;
  1467. }
  1468. }
  1469. if ($space_remaining > $header_length) {
  1470. // Write as much as possible of the string in the current block
  1471. $tmp = substr($string, 0, $space_remaining);
  1472. //$this->_append($tmp);
  1473. $chunk .= $this->writeData($tmp);
  1474. // The remainder will be written in the next block(s)
  1475. $string = substr($string, $space_remaining);
  1476. // Reduce the current block length by the amount written
  1477. $block_length -= $continue_limit - $continue - $align;
  1478. // If the current string was split then the next CONTINUE block
  1479. // should have the string continue flag (grbit) set unless the
  1480. // split string fits exactly into the remaining space.
  1481. //
  1482. if ($block_length > 0) {
  1483. $continue = 1;
  1484. } else {
  1485. $continue = 0;
  1486. }
  1487. } else {
  1488. // Not enough space to start the string in the current block
  1489. $block_length -= $continue_limit - $space_remaining - $continue;
  1490. $continue = 0;
  1491. }
  1492. // Write the CONTINUE block header
  1493. if (!empty($this->_block_sizes)) {
  1494. $record = 0x003C;
  1495. $length = array_shift($tmp_block_sizes);
  1496. $header = pack('vv', $record, $length);
  1497. if ($continue) {
  1498. $header .= pack('C', $encoding);
  1499. }
  1500. //$this->_append($header);
  1501. $chunk .= $this->writeData($header);
  1502. }
  1503. // If the string (or substr) is small enough we can write it in the
  1504. // new CONTINUE block. Else, go through the loop again to write it in
  1505. // one or more CONTINUE blocks
  1506. //
  1507. if ($block_length < $continue_limit) {
  1508. //$this->_append($string);
  1509. $chunk .= $this->writeData($string);
  1510. $written = $block_length;
  1511. } else {
  1512. $written = 0;
  1513. }
  1514. }
  1515. }
  1516. return $chunk;
  1517. }
  1518. }