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

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

#
PHP | 1583 lines | 857 code | 187 blank | 539 comment | 120 complexity | b21e1907b65df76041c16353f0f845d5 MD5 | raw file
Possible License(s): AGPL-1.0, LGPL-2.0, LGPL-2.1, GPL-3.0, LGPL-3.0

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

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