For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1444 lines
52 KiB

  1. <?php
  2. /**
  3. * PHPExcel_Writer_Excel5_Workbook
  4. *
  5. * Copyright (c) 2006 - 2015 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Writer_Excel5
  23. * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. // Original file header of PEAR::Spreadsheet_Excel_Writer_Workbook (used as the base for this class):
  28. // -----------------------------------------------------------------------------------------
  29. // /*
  30. // * Module written/ported by Xavier Noguer <[email protected]>
  31. // *
  32. // * The majority of this is _NOT_ my code. I simply ported it from the
  33. // * PERL Spreadsheet::WriteExcel module.
  34. // *
  35. // * The author of the Spreadsheet::WriteExcel module is John McNamara
  36. // * <[email protected]>
  37. // *
  38. // * I _DO_ maintain this code, and John McNamara has nothing to do with the
  39. // * porting of this code to PHP. Any questions directly related to this
  40. // * class library should be directed to me.
  41. // *
  42. // * License Information:
  43. // *
  44. // * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
  45. // * Copyright (c) 2002-2003 Xavier Noguer [email protected]
  46. // *
  47. // * This library is free software; you can redistribute it and/or
  48. // * modify it under the terms of the GNU Lesser General Public
  49. // * License as published by the Free Software Foundation; either
  50. // * version 2.1 of the License, or (at your option) any later version.
  51. // *
  52. // * This library is distributed in the hope that it will be useful,
  53. // * but WITHOUT ANY WARRANTY; without even the implied warranty of
  54. // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  55. // * Lesser General Public License for more details.
  56. // *
  57. // * You should have received a copy of the GNU Lesser General Public
  58. // * License along with this library; if not, write to the Free Software
  59. // * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  60. // */
  61. class PHPExcel_Writer_Excel5_Workbook extends PHPExcel_Writer_Excel5_BIFFwriter
  62. {
  63. /**
  64. * Formula parser
  65. *
  66. * @var PHPExcel_Writer_Excel5_Parser
  67. */
  68. private $parser;
  69. /**
  70. * The BIFF file size for the workbook.
  71. * @var integer
  72. * @see calcSheetOffsets()
  73. */
  74. private $biffSize;
  75. /**
  76. * XF Writers
  77. * @var PHPExcel_Writer_Excel5_Xf[]
  78. */
  79. private $xfWriters = array();
  80. /**
  81. * Array containing the colour palette
  82. * @var array
  83. */
  84. private $palette;
  85. /**
  86. * The codepage indicates the text encoding used for strings
  87. * @var integer
  88. */
  89. private $codepage;
  90. /**
  91. * The country code used for localization
  92. * @var integer
  93. */
  94. private $countryCode;
  95. /**
  96. * Workbook
  97. * @var PHPExcel
  98. */
  99. private $phpExcel;
  100. /**
  101. * Fonts writers
  102. *
  103. * @var PHPExcel_Writer_Excel5_Font[]
  104. */
  105. private $fontWriters = array();
  106. /**
  107. * Added fonts. Maps from font's hash => index in workbook
  108. *
  109. * @var array
  110. */
  111. private $addedFonts = array();
  112. /**
  113. * Shared number formats
  114. *
  115. * @var array
  116. */
  117. private $numberFormats = array();
  118. /**
  119. * Added number formats. Maps from numberFormat's hash => index in workbook
  120. *
  121. * @var array
  122. */
  123. private $addedNumberFormats = array();
  124. /**
  125. * Sizes of the binary worksheet streams
  126. *
  127. * @var array
  128. */
  129. private $worksheetSizes = array();
  130. /**
  131. * Offsets of the binary worksheet streams relative to the start of the global workbook stream
  132. *
  133. * @var array
  134. */
  135. private $worksheetOffsets = array();
  136. /**
  137. * Total number of shared strings in workbook
  138. *
  139. * @var int
  140. */
  141. private $stringTotal;
  142. /**
  143. * Number of unique shared strings in workbook
  144. *
  145. * @var int
  146. */
  147. private $stringUnique;
  148. /**
  149. * Array of unique shared strings in workbook
  150. *
  151. * @var array
  152. */
  153. private $stringTable;
  154. /**
  155. * Color cache
  156. */
  157. private $colors;
  158. /**
  159. * Escher object corresponding to MSODRAWINGGROUP
  160. *
  161. * @var PHPExcel_Shared_Escher
  162. */
  163. private $escher;
  164. /**
  165. * Class constructor
  166. *
  167. * @param PHPExcel $phpExcel The Workbook
  168. * @param int &$str_total Total number of strings
  169. * @param int &$str_unique Total number of unique strings
  170. * @param array &$str_table String Table
  171. * @param array &$colors Colour Table
  172. * @param mixed $parser The formula parser created for the Workbook
  173. */
  174. public function __construct(PHPExcel $phpExcel = null, &$str_total, &$str_unique, &$str_table, &$colors, $parser)
  175. {
  176. // It needs to call its parent's constructor explicitly
  177. parent::__construct();
  178. $this->parser = $parser;
  179. $this->biffSize = 0;
  180. $this->palette = array();
  181. $this->countryCode = -1;
  182. $this->stringTotal = &$str_total;
  183. $this->stringUnique = &$str_unique;
  184. $this->stringTable = &$str_table;
  185. $this->colors = &$colors;
  186. $this->setPaletteXl97();
  187. $this->phpExcel = $phpExcel;
  188. // set BIFFwriter limit for CONTINUE records
  189. // $this->_limit = 8224;
  190. $this->codepage = 0x04B0;
  191. // Add empty sheets and Build color cache
  192. $countSheets = $phpExcel->getSheetCount();
  193. for ($i = 0; $i < $countSheets; ++$i) {
  194. $phpSheet = $phpExcel->getSheet($i);
  195. $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
  196. $supbook_index = 0x00;
  197. $ref = pack('vvv', $supbook_index, $i, $i);
  198. $this->parser->references[] = $ref; // Register reference with parser
  199. // Sheet tab colors?
  200. if ($phpSheet->isTabColorSet()) {
  201. $this->addColor($phpSheet->getTabColor()->getRGB());
  202. }
  203. }
  204. }
  205. /**
  206. * Add a new XF writer
  207. *
  208. * @param PHPExcel_Style
  209. * @param boolean Is it a style XF?
  210. * @return int Index to XF record
  211. */
  212. public function addXfWriter($style, $isStyleXf = false)
  213. {
  214. $xfWriter = new PHPExcel_Writer_Excel5_Xf($style);
  215. $xfWriter->setIsStyleXf($isStyleXf);
  216. // Add the font if not already added
  217. $fontIndex = $this->addFont($style->getFont());
  218. // Assign the font index to the xf record
  219. $xfWriter->setFontIndex($fontIndex);
  220. // Background colors, best to treat these after the font so black will come after white in custom palette
  221. $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
  222. $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
  223. $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
  224. $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
  225. $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
  226. $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
  227. $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
  228. // Add the number format if it is not a built-in one and not already added
  229. if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
  230. $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
  231. if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
  232. $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
  233. } else {
  234. $numberFormatIndex = 164 + count($this->numberFormats);
  235. $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
  236. $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
  237. }
  238. } else {
  239. $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
  240. }
  241. // Assign the number format index to xf record
  242. $xfWriter->setNumberFormatIndex($numberFormatIndex);
  243. $this->xfWriters[] = $xfWriter;
  244. $xfIndex = count($this->xfWriters) - 1;
  245. return $xfIndex;
  246. }
  247. /**
  248. * Add a font to added fonts
  249. *
  250. * @param PHPExcel_Style_Font $font
  251. * @return int Index to FONT record
  252. */
  253. public function addFont(PHPExcel_Style_Font $font)
  254. {
  255. $fontHashCode = $font->getHashCode();
  256. if (isset($this->addedFonts[$fontHashCode])) {
  257. $fontIndex = $this->addedFonts[$fontHashCode];
  258. } else {
  259. $countFonts = count($this->fontWriters);
  260. $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
  261. $fontWriter = new PHPExcel_Writer_Excel5_Font($font);
  262. $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
  263. $this->fontWriters[] = $fontWriter;
  264. $this->addedFonts[$fontHashCode] = $fontIndex;
  265. }
  266. return $fontIndex;
  267. }
  268. /**
  269. * Alter color palette adding a custom color
  270. *
  271. * @param string $rgb E.g. 'FF00AA'
  272. * @return int Color index
  273. */
  274. private function addColor($rgb)
  275. {
  276. if (!isset($this->colors[$rgb])) {
  277. if (count($this->colors) < 57) {
  278. // then we add a custom color altering the palette
  279. $colorIndex = 8 + count($this->colors);
  280. $this->palette[$colorIndex] =
  281. array(
  282. hexdec(substr($rgb, 0, 2)),
  283. hexdec(substr($rgb, 2, 2)),
  284. hexdec(substr($rgb, 4)),
  285. 0
  286. );
  287. $this->colors[$rgb] = $colorIndex;
  288. } else {
  289. // no room for more custom colors, just map to black
  290. $colorIndex = 0;
  291. }
  292. } else {
  293. // fetch already added custom color
  294. $colorIndex = $this->colors[$rgb];
  295. }
  296. return $colorIndex;
  297. }
  298. /**
  299. * Sets the colour palette to the Excel 97+ default.
  300. *
  301. * @access private
  302. */
  303. private function setPaletteXl97()
  304. {
  305. $this->palette = array(
  306. 0x08 => array(0x00, 0x00, 0x00, 0x00),
  307. 0x09 => array(0xff, 0xff, 0xff, 0x00),
  308. 0x0A => array(0xff, 0x00, 0x00, 0x00),
  309. 0x0B => array(0x00, 0xff, 0x00, 0x00),
  310. 0x0C => array(0x00, 0x00, 0xff, 0x00),
  311. 0x0D => array(0xff, 0xff, 0x00, 0x00),
  312. 0x0E => array(0xff, 0x00, 0xff, 0x00),
  313. 0x0F => array(0x00, 0xff, 0xff, 0x00),
  314. 0x10 => array(0x80, 0x00, 0x00, 0x00),
  315. 0x11 => array(0x00, 0x80, 0x00, 0x00),
  316. 0x12 => array(0x00, 0x00, 0x80, 0x00),
  317. 0x13 => array(0x80, 0x80, 0x00, 0x00),
  318. 0x14 => array(0x80, 0x00, 0x80, 0x00),
  319. 0x15 => array(0x00, 0x80, 0x80, 0x00),
  320. 0x16 => array(0xc0, 0xc0, 0xc0, 0x00),
  321. 0x17 => array(0x80, 0x80, 0x80, 0x00),
  322. 0x18 => array(0x99, 0x99, 0xff, 0x00),
  323. 0x19 => array(0x99, 0x33, 0x66, 0x00),
  324. 0x1A => array(0xff, 0xff, 0xcc, 0x00),
  325. 0x1B => array(0xcc, 0xff, 0xff, 0x00),
  326. 0x1C => array(0x66, 0x00, 0x66, 0x00),
  327. 0x1D => array(0xff, 0x80, 0x80, 0x00),
  328. 0x1E => array(0x00, 0x66, 0xcc, 0x00),
  329. 0x1F => array(0xcc, 0xcc, 0xff, 0x00),
  330. 0x20 => array(0x00, 0x00, 0x80, 0x00),
  331. 0x21 => array(0xff, 0x00, 0xff, 0x00),
  332. 0x22 => array(0xff, 0xff, 0x00, 0x00),
  333. 0x23 => array(0x00, 0xff, 0xff, 0x00),
  334. 0x24 => array(0x80, 0x00, 0x80, 0x00),
  335. 0x25 => array(0x80, 0x00, 0x00, 0x00),
  336. 0x26 => array(0x00, 0x80, 0x80, 0x00),
  337. 0x27 => array(0x00, 0x00, 0xff, 0x00),
  338. 0x28 => array(0x00, 0xcc, 0xff, 0x00),
  339. 0x29 => array(0xcc, 0xff, 0xff, 0x00),
  340. 0x2A => array(0xcc, 0xff, 0xcc, 0x00),
  341. 0x2B => array(0xff, 0xff, 0x99, 0x00),
  342. 0x2C => array(0x99, 0xcc, 0xff, 0x00),
  343. 0x2D => array(0xff, 0x99, 0xcc, 0x00),
  344. 0x2E => array(0xcc, 0x99, 0xff, 0x00),
  345. 0x2F => array(0xff, 0xcc, 0x99, 0x00),
  346. 0x30 => array(0x33, 0x66, 0xff, 0x00),
  347. 0x31 => array(0x33, 0xcc, 0xcc, 0x00),
  348. 0x32 => array(0x99, 0xcc, 0x00, 0x00),
  349. 0x33 => array(0xff, 0xcc, 0x00, 0x00),
  350. 0x34 => array(0xff, 0x99, 0x00, 0x00),
  351. 0x35 => array(0xff, 0x66, 0x00, 0x00),
  352. 0x36 => array(0x66, 0x66, 0x99, 0x00),
  353. 0x37 => array(0x96, 0x96, 0x96, 0x00),
  354. 0x38 => array(0x00, 0x33, 0x66, 0x00),
  355. 0x39 => array(0x33, 0x99, 0x66, 0x00),
  356. 0x3A => array(0x00, 0x33, 0x00, 0x00),
  357. 0x3B => array(0x33, 0x33, 0x00, 0x00),
  358. 0x3C => array(0x99, 0x33, 0x00, 0x00),
  359. 0x3D => array(0x99, 0x33, 0x66, 0x00),
  360. 0x3E => array(0x33, 0x33, 0x99, 0x00),
  361. 0x3F => array(0x33, 0x33, 0x33, 0x00),
  362. );
  363. }
  364. /**
  365. * Assemble worksheets into a workbook and send the BIFF data to an OLE
  366. * storage.
  367. *
  368. * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
  369. * @return string Binary data for workbook stream
  370. */
  371. public function writeWorkbook($pWorksheetSizes = null)
  372. {
  373. $this->worksheetSizes = $pWorksheetSizes;
  374. // Calculate the number of selected worksheet tabs and call the finalization
  375. // methods for each worksheet
  376. $total_worksheets = $this->phpExcel->getSheetCount();
  377. // Add part 1 of the Workbook globals, what goes before the SHEET records
  378. $this->storeBof(0x0005);
  379. $this->writeCodepage();
  380. $this->writeWindow1();
  381. $this->writeDateMode();
  382. $this->writeAllFonts();
  383. $this->writeAllNumberFormats();
  384. $this->writeAllXfs();
  385. $this->writeAllStyles();
  386. $this->writePalette();
  387. // Prepare part 3 of the workbook global stream, what goes after the SHEET records
  388. $part3 = '';
  389. if ($this->countryCode != -1) {
  390. $part3 .= $this->writeCountry();
  391. }
  392. $part3 .= $this->writeRecalcId();
  393. $part3 .= $this->writeSupbookInternal();
  394. /* TODO: store external SUPBOOK records and XCT and CRN records
  395. in case of external references for BIFF8 */
  396. $part3 .= $this->writeExternalsheetBiff8();
  397. $part3 .= $this->writeAllDefinedNamesBiff8();
  398. $part3 .= $this->writeMsoDrawingGroup();
  399. $part3 .= $this->writeSharedStringsTable();
  400. $part3 .= $this->writeEof();
  401. // Add part 2 of the Workbook globals, the SHEET records
  402. $this->calcSheetOffsets();
  403. for ($i = 0; $i < $total_worksheets; ++$i) {
  404. $this->writeBoundSheet($this->phpExcel->getSheet($i), $this->worksheetOffsets[$i]);
  405. }
  406. // Add part 3 of the Workbook globals
  407. $this->_data .= $part3;
  408. return $this->_data;
  409. }
  410. /**
  411. * Calculate offsets for Worksheet BOF records.
  412. *
  413. * @access private
  414. */
  415. private function calcSheetOffsets()
  416. {
  417. $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
  418. // size of Workbook globals part 1 + 3
  419. $offset = $this->_datasize;
  420. // add size of Workbook globals part 2, the length of the SHEET records
  421. $total_worksheets = count($this->phpExcel->getAllSheets());
  422. foreach ($this->phpExcel->getWorksheetIterator() as $sheet) {
  423. $offset += $boundsheet_length + strlen(PHPExcel_Shared_String::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
  424. }
  425. // add the sizes of each of the Sheet substreams, respectively
  426. for ($i = 0; $i < $total_worksheets; ++$i) {
  427. $this->worksheetOffsets[$i] = $offset;
  428. $offset += $this->worksheetSizes[$i];
  429. }
  430. $this->biffSize = $offset;
  431. }
  432. /**
  433. * Store the Excel FONT records.
  434. */
  435. private function writeAllFonts()
  436. {
  437. foreach ($this->fontWriters as $fontWriter) {
  438. $this->append($fontWriter->writeFont());
  439. }
  440. }
  441. /**
  442. * Store user defined numerical formats i.e. FORMAT records
  443. */
  444. private function writeAllNumberFormats()
  445. {
  446. foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
  447. $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
  448. }
  449. }
  450. /**
  451. * Write all XF records.
  452. */
  453. private function writeAllXfs()
  454. {
  455. foreach ($this->xfWriters as $xfWriter) {
  456. $this->append($xfWriter->writeXf());
  457. }
  458. }
  459. /**
  460. * Write all STYLE records.
  461. */
  462. private function writeAllStyles()
  463. {
  464. $this->writeStyle();
  465. }
  466. /**
  467. * Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
  468. * the NAME records.
  469. */
  470. private function writeExternals()
  471. {
  472. $countSheets = $this->phpExcel->getSheetCount();
  473. // Create EXTERNCOUNT with number of worksheets
  474. $this->writeExternalCount($countSheets);
  475. // Create EXTERNSHEET for each worksheet
  476. for ($i = 0; $i < $countSheets; ++$i) {
  477. $this->writeExternalSheet($this->phpExcel->getSheet($i)->getTitle());
  478. }
  479. }
  480. /**
  481. * Write the NAME record to define the print area and the repeat rows and cols.
  482. */
  483. private function writeNames()
  484. {
  485. // total number of sheets
  486. $total_worksheets = $this->phpExcel->getSheetCount();
  487. // Create the print area NAME records
  488. for ($i = 0; $i < $total_worksheets; ++$i) {
  489. $sheetSetup = $this->phpExcel->getSheet($i)->getPageSetup();
  490. // Write a Name record if the print area has been defined
  491. if ($sheetSetup->isPrintAreaSet()) {
  492. // Print area
  493. $printArea = PHPExcel_Cell::splitRange($sheetSetup->getPrintArea());
  494. $printArea = $printArea[0];
  495. $printArea[0] = PHPExcel_Cell::coordinateFromString($printArea[0]);
  496. $printArea[1] = PHPExcel_Cell::coordinateFromString($printArea[1]);
  497. $print_rowmin = $printArea[0][1] - 1;
  498. $print_rowmax = $printArea[1][1] - 1;
  499. $print_colmin = PHPExcel_Cell::columnIndexFromString($printArea[0][0]) - 1;
  500. $print_colmax = PHPExcel_Cell::columnIndexFromString($printArea[1][0]) - 1;
  501. $this->writeNameShort(
  502. $i, // sheet index
  503. 0x06, // NAME type
  504. $print_rowmin,
  505. $print_rowmax,
  506. $print_colmin,
  507. $print_colmax
  508. );
  509. }
  510. }
  511. // Create the print title NAME records
  512. for ($i = 0; $i < $total_worksheets; ++$i) {
  513. $sheetSetup = $this->phpExcel->getSheet($i)->getPageSetup();
  514. // simultaneous repeatColumns repeatRows
  515. if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
  516. $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
  517. $colmin = PHPExcel_Cell::columnIndexFromString($repeat[0]) - 1;
  518. $colmax = PHPExcel_Cell::columnIndexFromString($repeat[1]) - 1;
  519. $repeat = $sheetSetup->getRowsToRepeatAtTop();
  520. $rowmin = $repeat[0] - 1;
  521. $rowmax = $repeat[1] - 1;
  522. $this->writeNameLong(
  523. $i, // sheet index
  524. 0x07, // NAME type
  525. $rowmin,
  526. $rowmax,
  527. $colmin,
  528. $colmax
  529. );
  530. // (exclusive) either repeatColumns or repeatRows
  531. } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
  532. // Columns to repeat
  533. if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
  534. $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
  535. $colmin = PHPExcel_Cell::columnIndexFromString($repeat[0]) - 1;
  536. $colmax = PHPExcel_Cell::columnIndexFromString($repeat[1]) - 1;
  537. } else {
  538. $colmin = 0;
  539. $colmax = 255;
  540. }
  541. // Rows to repeat
  542. if ($sheetSetup->isRowsToRepeatAtTopSet()) {
  543. $repeat = $sheetSetup->getRowsToRepeatAtTop();
  544. $rowmin = $repeat[0] - 1;
  545. $rowmax = $repeat[1] - 1;
  546. } else {
  547. $rowmin = 0;
  548. $rowmax = 65535;
  549. }
  550. $this->writeNameShort(
  551. $i, // sheet index
  552. 0x07, // NAME type
  553. $rowmin,
  554. $rowmax,
  555. $colmin,
  556. $colmax
  557. );
  558. }
  559. }
  560. }
  561. /**
  562. * Writes all the DEFINEDNAME records (BIFF8).
  563. * So far this is only used for repeating rows/columns (print titles) and print areas
  564. */
  565. private function writeAllDefinedNamesBiff8()
  566. {
  567. $chunk = '';
  568. // Named ranges
  569. if (count($this->phpExcel->getNamedRanges()) > 0) {
  570. // Loop named ranges
  571. $namedRanges = $this->phpExcel->getNamedRanges();
  572. foreach ($namedRanges as $namedRange) {
  573. // Create absolute coordinate
  574. $range = PHPExcel_Cell::splitRange($namedRange->getRange());
  575. for ($i = 0; $i < count($range); $i++) {
  576. $range[$i][0] = '\'' . str_replace("'", "''", $namedRange->getWorksheet()->getTitle()) . '\'!' . PHPExcel_Cell::absoluteCoordinate($range[$i][0]);
  577. if (isset($range[$i][1])) {
  578. $range[$i][1] = PHPExcel_Cell::absoluteCoordinate($range[$i][1]);
  579. }
  580. }
  581. $range = PHPExcel_Cell::buildRange($range); // e.g. Sheet1!$A$1:$B$2
  582. // parse formula
  583. try {
  584. $error = $this->parser->parse($range);
  585. $formulaData = $this->parser->toReversePolish();
  586. // make sure tRef3d is of type tRef3dR (0x3A)
  587. if (isset($formulaData[0]) and ($formulaData[0] == "\x7A" or $formulaData[0] == "\x5A")) {
  588. $formulaData = "\x3A" . substr($formulaData, 1);
  589. }
  590. if ($namedRange->getLocalOnly()) {
  591. // local scope
  592. $scope = $this->phpExcel->getIndex($namedRange->getScope()) + 1;
  593. } else {
  594. // global scope
  595. $scope = 0;
  596. }
  597. $chunk .= $this->writeData($this->writeDefinedNameBiff8($namedRange->getName(), $formulaData, $scope, false));
  598. } catch (PHPExcel_Exception $e) {
  599. // do nothing
  600. }
  601. }
  602. }
  603. // total number of sheets
  604. $total_worksheets = $this->phpExcel->getSheetCount();
  605. // write the print titles (repeating rows, columns), if any
  606. for ($i = 0; $i < $total_worksheets; ++$i) {
  607. $sheetSetup = $this->phpExcel->getSheet($i)->getPageSetup();
  608. // simultaneous repeatColumns repeatRows
  609. if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
  610. $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
  611. $colmin = PHPExcel_Cell::columnIndexFromString($repeat[0]) - 1;
  612. $colmax = PHPExcel_Cell::columnIndexFromString($repeat[1]) - 1;
  613. $repeat = $sheetSetup->getRowsToRepeatAtTop();
  614. $rowmin = $repeat[0] - 1;
  615. $rowmax = $repeat[1] - 1;
  616. // construct formula data manually
  617. $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
  618. $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
  619. $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
  620. $formulaData .= pack('C', 0x10); // tList
  621. // store the DEFINEDNAME record
  622. $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
  623. // (exclusive) either repeatColumns or repeatRows
  624. } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
  625. // Columns to repeat
  626. if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
  627. $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
  628. $colmin = PHPExcel_Cell::columnIndexFromString($repeat[0]) - 1;
  629. $colmax = PHPExcel_Cell::columnIndexFromString($repeat[1]) - 1;
  630. } else {
  631. $colmin = 0;
  632. $colmax = 255;
  633. }
  634. // Rows to repeat
  635. if ($sheetSetup->isRowsToRepeatAtTopSet()) {
  636. $repeat = $sheetSetup->getRowsToRepeatAtTop();
  637. $rowmin = $repeat[0] - 1;
  638. $rowmax = $repeat[1] - 1;
  639. } else {
  640. $rowmin = 0;
  641. $rowmax = 65535;
  642. }
  643. // construct formula data manually because parser does not recognize absolute 3d cell references
  644. $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
  645. // store the DEFINEDNAME record
  646. $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
  647. }
  648. }
  649. // write the print areas, if any
  650. for ($i = 0; $i < $total_worksheets; ++$i) {
  651. $sheetSetup = $this->phpExcel->getSheet($i)->getPageSetup();
  652. if ($sheetSetup->isPrintAreaSet()) {
  653. // Print area, e.g. A3:J6,H1:X20
  654. $printArea = PHPExcel_Cell::splitRange($sheetSetup->getPrintArea());
  655. $countPrintArea = count($printArea);
  656. $formulaData = '';
  657. for ($j = 0; $j < $countPrintArea; ++$j) {
  658. $printAreaRect = $printArea[$j]; // e.g. A3:J6
  659. $printAreaRect[0] = PHPExcel_Cell::coordinateFromString($printAreaRect[0]);
  660. $printAreaRect[1] = PHPExcel_Cell::coordinateFromString($printAreaRect[1]);
  661. $print_rowmin = $printAreaRect[0][1] - 1;
  662. $print_rowmax = $printAreaRect[1][1] - 1;
  663. $print_colmin = PHPExcel_Cell::columnIndexFromString($printAreaRect[0][0]) - 1;
  664. $print_colmax = PHPExcel_Cell::columnIndexFromString($printAreaRect[1][0]) - 1;
  665. // construct formula data manually because parser does not recognize absolute 3d cell references
  666. $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
  667. if ($j > 0) {
  668. $formulaData .= pack('C', 0x10); // list operator token ','
  669. }
  670. }
  671. // store the DEFINEDNAME record
  672. $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
  673. }
  674. }
  675. // write autofilters, if any
  676. for ($i = 0; $i < $total_worksheets; ++$i) {
  677. $sheetAutoFilter = $this->phpExcel->getSheet($i)->getAutoFilter();
  678. $autoFilterRange = $sheetAutoFilter->getRange();
  679. if (!empty($autoFilterRange)) {
  680. $rangeBounds = PHPExcel_Cell::rangeBoundaries($autoFilterRange);
  681. //Autofilter built in name
  682. $name = pack('C', 0x0D);
  683. $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
  684. }
  685. }
  686. return $chunk;
  687. }
  688. /**
  689. * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data
  690. *
  691. * @param string $name The name in UTF-8
  692. * @param string $formulaData The binary formula data
  693. * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
  694. * @param boolean $isBuiltIn Built-in name?
  695. * @return string Complete binary record data
  696. */
  697. private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
  698. {
  699. $record = 0x0018;
  700. // option flags
  701. $options = $isBuiltIn ? 0x20 : 0x00;
  702. // length of the name, character count
  703. $nlen = PHPExcel_Shared_String::CountCharacters($name);
  704. // name with stripped length field
  705. $name = substr(PHPExcel_Shared_String::UTF8toBIFF8UnicodeLong($name), 2);
  706. // size of the formula (in bytes)
  707. $sz = strlen($formulaData);
  708. // combine the parts
  709. $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
  710. . $name . $formulaData;
  711. $length = strlen($data);
  712. $header = pack('vv', $record, $length);
  713. return $header . $data;
  714. }
  715. /**
  716. * Write a short NAME record
  717. *
  718. * @param string $name
  719. * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
  720. * @param integer[][] $rangeBounds range boundaries
  721. * @param boolean $isHidden
  722. * @return string Complete binary record data
  723. * */
  724. private function writeShortNameBiff8($name, $sheetIndex = 0, $rangeBounds, $isHidden = false)
  725. {
  726. $record = 0x0018;
  727. // option flags
  728. $options = ($isHidden ? 0x21 : 0x00);
  729. $extra = pack(
  730. 'Cvvvvv',
  731. 0x3B,
  732. $sheetIndex - 1,
  733. $rangeBounds[0][1] - 1,
  734. $rangeBounds[1][1] - 1,
  735. $rangeBounds[0][0] - 1,
  736. $rangeBounds[1][0] - 1
  737. );
  738. // size of the formula (in bytes)
  739. $sz = strlen($extra);
  740. // combine the parts
  741. $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
  742. . $name . $extra;
  743. $length = strlen($data);
  744. $header = pack('vv', $record, $length);
  745. return $header . $data;
  746. }
  747. /**
  748. * Stores the CODEPAGE biff record.
  749. */
  750. private function writeCodepage()
  751. {
  752. $record = 0x0042; // Record identifier
  753. $length = 0x0002; // Number of bytes to follow
  754. $cv = $this->codepage; // The code page
  755. $header = pack('vv', $record, $length);
  756. $data = pack('v', $cv);
  757. $this->append($header . $data);
  758. }
  759. /**
  760. * Write Excel BIFF WINDOW1 record.
  761. */
  762. private function writeWindow1()
  763. {
  764. $record = 0x003D; // Record identifier
  765. $length = 0x0012; // Number of bytes to follow
  766. $xWn = 0x0000; // Horizontal position of window
  767. $yWn = 0x0000; // Vertical position of window
  768. $dxWn = 0x25BC; // Width of window
  769. $dyWn = 0x1572; // Height of window
  770. $grbit = 0x0038; // Option flags
  771. // not supported by PHPExcel, so there is only one selected sheet, the active
  772. $ctabsel = 1; // Number of workbook tabs selected
  773. $wTabRatio = 0x0258; // Tab to scrollbar ratio
  774. // not supported by PHPExcel, set to 0
  775. $itabFirst = 0; // 1st displayed worksheet
  776. $itabCur = $this->phpExcel->getActiveSheetIndex(); // Active worksheet
  777. $header = pack("vv", $record, $length);
  778. $data = pack("vvvvvvvvv", $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
  779. $this->append($header . $data);
  780. }
  781. /**
  782. * Writes Excel BIFF BOUNDSHEET record.
  783. *
  784. * @param PHPExcel_Worksheet $sheet Worksheet name
  785. * @param integer $offset Location of worksheet BOF
  786. */
  787. private function writeBoundSheet($sheet, $offset)
  788. {
  789. $sheetname = $sheet->getTitle();
  790. $record = 0x0085; // Record identifier
  791. // sheet state
  792. switch ($sheet->getSheetState()) {
  793. case PHPExcel_Worksheet::SHEETSTATE_VISIBLE:
  794. $ss = 0x00;
  795. break;
  796. case PHPExcel_Worksheet::SHEETSTATE_HIDDEN:
  797. $ss = 0x01;
  798. break;
  799. case PHPExcel_Worksheet::SHEETSTATE_VERYHIDDEN:
  800. $ss = 0x02;
  801. break;
  802. default:
  803. $ss = 0x00;
  804. break;
  805. }
  806. // sheet type
  807. $st = 0x00;
  808. $grbit = 0x0000; // Visibility and sheet type
  809. $data = pack("VCC", $offset, $ss, $st);
  810. $data .= PHPExcel_Shared_String::UTF8toBIFF8UnicodeShort($sheetname);
  811. $length = strlen($data);
  812. $header = pack("vv", $record, $length);
  813. $this->append($header . $data);
  814. }
  815. /**
  816. * Write Internal SUPBOOK record
  817. */
  818. private function writeSupbookInternal()
  819. {
  820. $record = 0x01AE; // Record identifier
  821. $length = 0x0004; // Bytes to follow
  822. $header = pack("vv", $record, $length);
  823. $data = pack("vv", $this->phpExcel->getSheetCount(), 0x0401);
  824. return $this->writeData($header . $data);
  825. }
  826. /**
  827. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  828. * formulas.
  829. *
  830. */
  831. private function writeExternalsheetBiff8()
  832. {
  833. $totalReferences = count($this->parser->references);
  834. $record = 0x0017; // Record identifier
  835. $length = 2 + 6 * $totalReferences; // Number of bytes to follow
  836. $supbook_index = 0; // FIXME: only using internal SUPBOOK record
  837. $header = pack("vv", $record, $length);
  838. $data = pack('v', $totalReferences);
  839. for ($i = 0; $i < $totalReferences; ++$i) {
  840. $data .= $this->parser->references[$i];
  841. }
  842. return $this->writeData($header . $data);
  843. }
  844. /**
  845. * Write Excel BIFF STYLE records.
  846. */
  847. private function writeStyle()
  848. {
  849. $record = 0x0293; // Record identifier
  850. $length = 0x0004; // Bytes to follow
  851. $ixfe = 0x8000; // Index to cell style XF
  852. $BuiltIn = 0x00; // Built-in style
  853. $iLevel = 0xff; // Outline style level
  854. $header = pack("vv", $record, $length);
  855. $data = pack("vCC", $ixfe, $BuiltIn, $iLevel);
  856. $this->append($header . $data);
  857. }
  858. /**
  859. * Writes Excel FORMAT record for non "built-in" numerical formats.
  860. *
  861. * @param string $format Custom format string
  862. * @param integer $ifmt Format index code
  863. */
  864. private function writeNumberFormat($format, $ifmt)
  865. {
  866. $record = 0x041E; // Record identifier
  867. $numberFormatString = PHPExcel_Shared_String::UTF8toBIFF8UnicodeLong($format);
  868. $length = 2 + strlen($numberFormatString); // Number of bytes to follow
  869. $header = pack("vv", $record, $length);
  870. $data = pack("v", $ifmt) . $numberFormatString;
  871. $this->append($header . $data);
  872. }
  873. /**
  874. * Write DATEMODE record to indicate the date system in use (1904 or 1900).
  875. */
  876. private function writeDateMode()
  877. {
  878. $record = 0x0022; // Record identifier
  879. $length = 0x0002; // Bytes to follow
  880. $f1904 = (PHPExcel_Shared_Date::getExcelCalendar() == PHPExcel_Shared_Date::CALENDAR_MAC_1904)
  881. ? 1
  882. : 0; // Flag for 1904 date system
  883. $header = pack("vv", $record, $length);
  884. $data = pack("v", $f1904);
  885. $this->append($header . $data);
  886. }
  887. /**
  888. * Write BIFF record EXTERNCOUNT to indicate the number of external sheet
  889. * references in the workbook.
  890. *
  891. * Excel only stores references to external sheets that are used in NAME.
  892. * The workbook NAME record is required to define the print area and the repeat
  893. * rows and columns.
  894. *
  895. * A similar method is used in Worksheet.php for a slightly different purpose.
  896. *
  897. * @param integer $cxals Number of external references
  898. */
  899. private function writeExternalCount($cxals)
  900. {
  901. $record = 0x0016; // Record identifier
  902. $length = 0x0002; // Number of bytes to follow
  903. $header = pack("vv", $record, $length);
  904. $data = pack("v", $cxals);
  905. $this->append($header . $data);
  906. }
  907. /**
  908. * Writes the Excel BIFF EXTERNSHEET record. These references are used by
  909. * formulas. NAME record is required to define the print area and the repeat
  910. * rows and columns.
  911. *
  912. * A similar method is used in Worksheet.php for a slightly different purpose.
  913. *
  914. * @param string $sheetname Worksheet name
  915. */
  916. private function writeExternalSheet($sheetname)
  917. {
  918. $record = 0x0017; // Record identifier
  919. $length = 0x02 + strlen($sheetname); // Number of bytes to follow
  920. $cch = strlen($sheetname); // Length of sheet name
  921. $rgch = 0x03; // Filename encoding
  922. $header = pack("vv", $record, $length);
  923. $data = pack("CC", $cch, $rgch);
  924. $this->append($header . $data . $sheetname);
  925. }
  926. /**
  927. * Store the NAME record in the short format that is used for storing the print
  928. * area, repeat rows only and repeat columns only.
  929. *
  930. * @param integer $index Sheet index
  931. * @param integer $type Built-in name type
  932. * @param integer $rowmin Start row
  933. * @param integer $rowmax End row
  934. * @param integer $colmin Start colum
  935. * @param integer $colmax End column
  936. */
  937. private function writeNameShort($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  938. {
  939. $record = 0x0018; // Record identifier
  940. $length = 0x0024; // Number of bytes to follow
  941. $grbit = 0x0020; // Option flags
  942. $chKey = 0x00; // Keyboard shortcut
  943. $cch = 0x01; // Length of text name
  944. $cce = 0x0015; // Length of text definition
  945. $ixals = $index + 1; // Sheet index
  946. $itab = $ixals; // Equal to ixals
  947. $cchCustMenu = 0x00; // Length of cust menu text
  948. $cchDescription = 0x00; // Length of description text
  949. $cchHelptopic = 0x00; // Length of help topic text
  950. $cchStatustext = 0x00; // Length of status bar text
  951. $rgch = $type; // Built-in name type
  952. $unknown03 = 0x3b;
  953. $unknown04 = 0xffff - $index;
  954. $unknown05 = 0x0000;
  955. $unknown06 = 0x0000;
  956. $unknown07 = 0x1087;
  957. $unknown08 = 0x8005;
  958. $header = pack("vv", $record, $length);
  959. $data = pack("v", $grbit);
  960. $data .= pack("C", $chKey);
  961. $data .= pack("C", $cch);
  962. $data .= pack("v", $cce);
  963. $data .= pack("v", $ixals);
  964. $data .= pack("v", $itab);
  965. $data .= pack("C", $cchCustMenu);
  966. $data .= pack("C", $cchDescription);
  967. $data .= pack("C", $cchHelptopic);
  968. $data .= pack("C", $cchStatustext);
  969. $data .= pack("C", $rgch);
  970. $data .= pack("C", $unknown03);
  971. $data .= pack("v", $unknown04);
  972. $data .= pack("v", $unknown05);
  973. $data .= pack("v", $unknown06);
  974. $data .= pack("v", $unknown07);
  975. $data .= pack("v", $unknown08);
  976. $data .= pack("v", $index);
  977. $data .= pack("v", $index);
  978. $data .= pack("v", $rowmin);
  979. $data .= pack("v", $rowmax);
  980. $data .= pack("C", $colmin);
  981. $data .= pack("C", $colmax);
  982. $this->append($header . $data);
  983. }
  984. /**
  985. * Store the NAME record in the long format that is used for storing the repeat
  986. * rows and columns when both are specified. This shares a lot of code with
  987. * writeNameShort() but we use a separate method to keep the code clean.
  988. * Code abstraction for reuse can be carried too far, and I should know. ;-)
  989. *
  990. * @param integer $index Sheet index
  991. * @param integer $type Built-in name type
  992. * @param integer $rowmin Start row
  993. * @param integer $rowmax End row
  994. * @param integer $colmin Start colum
  995. * @param integer $colmax End column
  996. */
  997. private function writeNameLong($index, $type, $rowmin, $rowmax, $colmin, $colmax)
  998. {
  999. $record = 0x0018; // Record identifier
  1000. $length = 0x003d; // Number of bytes to follow
  1001. $grbit = 0x0020; // Option flags
  1002. $chKey = 0x00; // Keyboard shortcut
  1003. $cch = 0x01; // Length of text name
  1004. $cce = 0x002e; // Length of text definition
  1005. $ixals = $index + 1; // Sheet index
  1006. $itab = $ixals; // Equal to ixals
  1007. $cchCustMenu = 0x00; // Length of cust menu text
  1008. $cchDescription = 0x00; // Length of description text
  1009. $cchHelptopic = 0x00; // Length of help topic text
  1010. $cchStatustext = 0x00; // Length of status bar text
  1011. $rgch = $type; // Built-in name type
  1012. $unknown01 = 0x29;
  1013. $unknown02 = 0x002b;
  1014. $unknown03 = 0x3b;
  1015. $unknown04 = 0xffff-$index;
  1016. $unknown05 = 0x0000;
  1017. $unknown06 = 0x0000;
  1018. $unknown07 = 0x1087;
  1019. $unknown08 = 0x8008;
  1020. $header = pack("vv", $record, $length);
  1021. $data = pack("v", $grbit);
  1022. $data .= pack("C", $chKey);
  1023. $data .= pack("C", $cch);
  1024. $data .= pack("v", $cce);
  1025. $data .= pack("v", $ixals);
  1026. $data .= pack("v", $itab);
  1027. $data .= pack("C", $cchCustMenu);
  1028. $data .= pack("C", $cchDescription);
  1029. $data .= pack("C", $cchHelptopic);
  1030. $data .= pack("C", $cchStatustext);
  1031. $data .= pack("C", $rgch);
  1032. $data .= pack("C", $unknown01);
  1033. $data .= pack("v", $unknown02);
  1034. // Column definition
  1035. $data .= pack("C", $unknown03);
  1036. $data .= pack("v", $unknown04);
  1037. $data .= pack("v", $unknown05);
  1038. $data .= pack("v", $unknown06);
  1039. $data .= pack("v", $unknown07);
  1040. $data .= pack("v", $unknown08);
  1041. $data .= pack("v", $index);
  1042. $data .= pack("v", $index);
  1043. $data .= pack("v", 0x0000);
  1044. $data .= pack("v", 0x3fff);
  1045. $data .= pack("C", $colmin);
  1046. $data .= pack("C", $colmax);
  1047. // Row definition
  1048. $data .= pack("C", $unknown03);
  1049. $data .= pack("v", $unknown04);
  1050. $data .= pack("v", $unknown05);
  1051. $data .= pack("v", $unknown06);
  1052. $data .= pack("v", $unknown07);
  1053. $data .= pack("v", $unknown08);
  1054. $data .= pack("v", $index);
  1055. $data .= pack("v", $index);
  1056. $data .= pack("v", $rowmin);
  1057. $data .= pack("v", $rowmax);
  1058. $data .= pack("C", 0x00);
  1059. $data .= pack("C", 0xff);
  1060. // End of data
  1061. $data .= pack("C", 0x10);
  1062. $this->append($header . $data);
  1063. }
  1064. /**
  1065. * Stores the COUNTRY record for localization
  1066. *
  1067. * @return string
  1068. */
  1069. private function writeCountry()
  1070. {
  1071. $record = 0x008C; // Record identifier
  1072. $length = 4; // Number of bytes to follow
  1073. $header = pack('vv', $record, $length);
  1074. /* using the same country code always for simplicity */
  1075. $data = pack('vv', $this->countryCode, $this->countryCode);
  1076. //$this->append($header . $data);
  1077. return $this->writeData($header . $data);
  1078. }
  1079. /**
  1080. * Write the RECALCID record
  1081. *
  1082. * @return string
  1083. */
  1084. private function writeRecalcId()
  1085. {
  1086. $record = 0x01C1; // Record identifier
  1087. $length = 8; // Number of bytes to follow
  1088. $header = pack('vv', $record, $length);
  1089. // by inspection of real Excel files, MS Office Excel 2007 writes this
  1090. $data = pack('VV', 0x000001C1, 0x00001E667);
  1091. return $this->writeData($header . $data);
  1092. }
  1093. /**
  1094. * Stores the PALETTE biff record.
  1095. */
  1096. private function writePalette()
  1097. {
  1098. $aref = $this->palette;
  1099. $record = 0x0092; // Record identifier
  1100. $length = 2 + 4 * count($aref); // Number of bytes to follow
  1101. $ccv = count($aref); // Number of RGB values to follow
  1102. $data = ''; // The RGB data
  1103. // Pack the RGB data
  1104. foreach ($aref as $color) {
  1105. foreach ($color as $byte) {
  1106. $data .= pack("C", $byte);
  1107. }
  1108. }
  1109. $header = pack("vvv", $record, $length, $ccv);
  1110. $this->append($header . $data);
  1111. }
  1112. /**
  1113. * Handling of the SST continue blocks is complicated by the need to include an
  1114. * additional continuation byte depending on whether the string is split between
  1115. * blocks or whether it starts at the beginning of the block. (There are also
  1116. * additional complications that will arise later when/if Rich Strings are
  1117. * supported).
  1118. *
  1119. * The Excel documentation says that the SST record should be followed by an
  1120. * EXTSST record. The EXTSST record is a hash table that is used to optimise
  1121. * access to SST. However, despite the documentation it doesn't seem to be
  1122. * required so we will ignore it.
  1123. *
  1124. * @return string Binary data
  1125. */
  1126. private function writeSharedStringsTable()
  1127. {
  1128. // maximum size of record data (excluding record header)
  1129. $continue_limit = 8224;
  1130. // initialize array of record data blocks
  1131. $recordDatas = array();
  1132. // start SST record data block with total number of strings, total number of unique strings
  1133. $recordData = pack("VV", $this->stringTotal, $this->stringUnique);
  1134. // loop through all (unique) strings in shared strings table
  1135. foreach (array_keys($this->stringTable) as $string) {
  1136. // here $string is a BIFF8 encoded string
  1137. // length = character count
  1138. $headerinfo = unpack("vlength/Cencoding", $string);
  1139. // currently, this is always 1 = uncompressed
  1140. $encoding = $headerinfo["encoding"];
  1141. // initialize finished writing current $string
  1142. $finished = false;
  1143. while ($finished === false) {
  1144. // normally, there will be only one cycle, but if string cannot immediately be written as is
  1145. // there will be need for more than one cylcle, if string longer than one record data block, there
  1146. // may be need for even more cycles
  1147. if (strlen($recordData) + strlen($string) <= $continue_limit) {
  1148. // then we can write the string (or remainder of string) without any problems
  1149. $recordData .= $string;
  1150. if (strlen($recordData) + strlen($string) == $continue_limit) {
  1151. // we close the record data block, and initialize a new one
  1152. $recordDatas[] = $recordData;
  1153. $recordData = '';
  1154. }
  1155. // we are finished writing this string
  1156. $finished = true;
  1157. } else {
  1158. // special treatment writing the string (or remainder of the string)
  1159. // If the string is very long it may need to be written in more than one CONTINUE record.
  1160. // check how many bytes more there is room for in the current record
  1161. $space_remaining = $continue_limit - strlen($recordData);
  1162. // minimum space needed
  1163. // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
  1164. // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character
  1165. $min_space_needed = ($encoding == 1) ? 5 : 4;
  1166. // We have two cases
  1167. // 1. space remaining is less than minimum space needed
  1168. // here we must waste the space remaining and move to next record data block
  1169. // 2. space remaining is greater than or equal to minimum space needed
  1170. // here we write as much as we can in the current block, then move to next record data block
  1171. // 1. space remaining is less than minimum space needed
  1172. if ($space_remaining < $min_space_needed) {
  1173. // we close the block, store the block data
  1174. $recordDatas[] = $recordData;
  1175. // and start new record data block where we start writing the string
  1176. $recordData = '';
  1177. // 2. space remaining is greater than or equal to minimum space needed
  1178. } else {
  1179. // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
  1180. $effective_space_remaining = $space_remaining;
  1181. // for uncompressed strings, sometimes effective space remaining is reduced by 1
  1182. if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
  1183. --$effective_space_remaining;
  1184. }
  1185. // one block fininshed, store the block data
  1186. $recordData .= substr($string, 0, $effective_space_remaining);
  1187. $string = substr($string, $effective_space_remaining); // for next cycle in while loop
  1188. $recordDatas[] = $recordData;
  1189. // start new record data block with the repeated option flags
  1190. $recordData = pack('C', $encoding);
  1191. }
  1192. }
  1193. }
  1194. }
  1195. // Store the last record data block unless it is empty
  1196. // if there was no need for any continue records, this will be the for SST record data block itself
  1197. if (strlen($recordData) > 0) {
  1198. $recordDatas[] = $recordData;
  1199. }
  1200. // combine into one chunk with all the blocks SST, CONTINUE,...
  1201. $chunk = '';
  1202. foreach ($recordDatas as $i => $recordData) {
  1203. // first block should have the SST record header, remaing should have CONTINUE header
  1204. $record = ($i == 0) ? 0x00FC : 0x003C;
  1205. $header = pack("vv", $record, strlen($recordData));
  1206. $data = $header . $recordData;
  1207. $chunk .= $this->writeData($data);
  1208. }
  1209. return $chunk;
  1210. }
  1211. /**
  1212. * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
  1213. */
  1214. private function writeMsoDrawingGroup()
  1215. {
  1216. // write the Escher stream if necessary
  1217. if (isset($this->escher)) {
  1218. $writer = new PHPExcel_Writer_Excel5_Escher($this->escher);
  1219. $data = $writer->close();
  1220. $record = 0x00EB;
  1221. $length = strlen($data);
  1222. $header = pack("vv", $record, $length);
  1223. return $this->writeData($header . $data);
  1224. } else {
  1225. return '';
  1226. }
  1227. }
  1228. /**
  1229. * Get Escher object
  1230. *
  1231. * @return PHPExcel_Shared_Escher
  1232. */
  1233. public function getEscher()
  1234. {
  1235. return $this->escher;
  1236. }
  1237. /**
  1238. * Set Escher object
  1239. *
  1240. * @param PHPExcel_Shared_Escher $pValue
  1241. */
  1242. public function setEscher(PHPExcel_Shared_Escher $pValue = null)
  1243. {
  1244. $this->escher = $pValue;
  1245. }
  1246. }