Workbook.php 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  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 <xnoguer@rezebra.com>
  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. // * <jmcnamara@cpan.org>
  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 xnoguer@rezebra.com
  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. }