Workbook.php 46 KB

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