Excel5.php 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. <?php
  2. /**
  3. * 重庆赤晓店信息科技有限公司
  4. * https://www.chixiaodian.com
  5. * Copyright (c) 2023 赤店商城 All rights reserved.
  6. */
  7. class PHPExcel_Writer_Excel5 extends PHPExcel_Writer_Abstract implements PHPExcel_Writer_IWriter
  8. {
  9. /**
  10. * PHPExcel object
  11. *
  12. * @var PHPExcel
  13. */
  14. private $phpExcel;
  15. /**
  16. * Total number of shared strings in workbook
  17. *
  18. * @var int
  19. */
  20. private $strTotal = 0;
  21. /**
  22. * Number of unique shared strings in workbook
  23. *
  24. * @var int
  25. */
  26. private $strUnique = 0;
  27. /**
  28. * Array of unique shared strings in workbook
  29. *
  30. * @var array
  31. */
  32. private $strTable = array();
  33. /**
  34. * Color cache. Mapping between RGB value and color index.
  35. *
  36. * @var array
  37. */
  38. private $colors;
  39. /**
  40. * Formula parser
  41. *
  42. * @var PHPExcel_Writer_Excel5_Parser
  43. */
  44. private $parser;
  45. /**
  46. * Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
  47. *
  48. * @var array
  49. */
  50. private $IDCLs;
  51. /**
  52. * Basic OLE object summary information
  53. *
  54. * @var array
  55. */
  56. private $summaryInformation;
  57. /**
  58. * Extended OLE object document summary information
  59. *
  60. * @var array
  61. */
  62. private $documentSummaryInformation;
  63. /**
  64. * Create a new PHPExcel_Writer_Excel5
  65. *
  66. * @param PHPExcel $phpExcel PHPExcel object
  67. */
  68. public function __construct(PHPExcel $phpExcel)
  69. {
  70. $this->phpExcel = $phpExcel;
  71. $this->parser = new PHPExcel_Writer_Excel5_Parser();
  72. }
  73. /**
  74. * Save PHPExcel to file
  75. *
  76. * @param string $pFilename
  77. * @throws PHPExcel_Writer_Exception
  78. */
  79. public function save($pFilename = null)
  80. {
  81. // garbage collect
  82. $this->phpExcel->garbageCollect();
  83. $saveDebugLog = PHPExcel_Calculation::getInstance($this->phpExcel)->getDebugLog()->getWriteDebugLog();
  84. PHPExcel_Calculation::getInstance($this->phpExcel)->getDebugLog()->setWriteDebugLog(false);
  85. $saveDateReturnType = PHPExcel_Calculation_Functions::getReturnDateType();
  86. PHPExcel_Calculation_Functions::setReturnDateType(PHPExcel_Calculation_Functions::RETURNDATE_EXCEL);
  87. // initialize colors array
  88. $this->colors = array();
  89. // Initialise workbook writer
  90. $this->writerWorkbook = new PHPExcel_Writer_Excel5_Workbook($this->phpExcel, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser);
  91. // Initialise worksheet writers
  92. $countSheets = $this->phpExcel->getSheetCount();
  93. for ($i = 0; $i < $countSheets; ++$i) {
  94. $this->writerWorksheets[$i] = new PHPExcel_Writer_Excel5_Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->phpExcel->getSheet($i));
  95. }
  96. // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
  97. $this->buildWorksheetEschers();
  98. $this->buildWorkbookEscher();
  99. // add 15 identical cell style Xfs
  100. // for now, we use the first cellXf instead of cellStyleXf
  101. $cellXfCollection = $this->phpExcel->getCellXfCollection();
  102. for ($i = 0; $i < 15; ++$i) {
  103. $this->writerWorkbook->addXfWriter($cellXfCollection[0], true);
  104. }
  105. // add all the cell Xfs
  106. foreach ($this->phpExcel->getCellXfCollection() as $style) {
  107. $this->writerWorkbook->addXfWriter($style, false);
  108. }
  109. // add fonts from rich text eleemnts
  110. for ($i = 0; $i < $countSheets; ++$i) {
  111. foreach ($this->writerWorksheets[$i]->phpSheet->getCellCollection() as $cellID) {
  112. $cell = $this->writerWorksheets[$i]->phpSheet->getCell($cellID);
  113. $cVal = $cell->getValue();
  114. if ($cVal instanceof PHPExcel_RichText) {
  115. $elements = $cVal->getRichTextElements();
  116. foreach ($elements as $element) {
  117. if ($element instanceof PHPExcel_RichText_Run) {
  118. $font = $element->getFont();
  119. $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font);
  120. }
  121. }
  122. }
  123. }
  124. }
  125. // initialize OLE file
  126. $workbookStreamName = 'Workbook';
  127. $OLE = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs($workbookStreamName));
  128. // Write the worksheet streams before the global workbook stream,
  129. // because the byte sizes of these are needed in the global workbook stream
  130. $worksheetSizes = array();
  131. for ($i = 0; $i < $countSheets; ++$i) {
  132. $this->writerWorksheets[$i]->close();
  133. $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize;
  134. }
  135. // add binary data for global workbook stream
  136. $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes));
  137. // add binary data for sheet streams
  138. for ($i = 0; $i < $countSheets; ++$i) {
  139. $OLE->append($this->writerWorksheets[$i]->getData());
  140. }
  141. $this->documentSummaryInformation = $this->writeDocumentSummaryInformation();
  142. // initialize OLE Document Summary Information
  143. if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) {
  144. $OLE_DocumentSummaryInformation = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs(chr(5) . 'DocumentSummaryInformation'));
  145. $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation);
  146. }
  147. $this->summaryInformation = $this->writeSummaryInformation();
  148. // initialize OLE Summary Information
  149. if (isset($this->summaryInformation) && !empty($this->summaryInformation)) {
  150. $OLE_SummaryInformation = new PHPExcel_Shared_OLE_PPS_File(PHPExcel_Shared_OLE::Asc2Ucs(chr(5) . 'SummaryInformation'));
  151. $OLE_SummaryInformation->append($this->summaryInformation);
  152. }
  153. // define OLE Parts
  154. $arrRootData = array($OLE);
  155. // initialize OLE Properties file
  156. if (isset($OLE_SummaryInformation)) {
  157. $arrRootData[] = $OLE_SummaryInformation;
  158. }
  159. // initialize OLE Extended Properties file
  160. if (isset($OLE_DocumentSummaryInformation)) {
  161. $arrRootData[] = $OLE_DocumentSummaryInformation;
  162. }
  163. $root = new PHPExcel_Shared_OLE_PPS_Root(time(), time(), $arrRootData);
  164. // save the OLE file
  165. $res = $root->save($pFilename);
  166. PHPExcel_Calculation_Functions::setReturnDateType($saveDateReturnType);
  167. PHPExcel_Calculation::getInstance($this->phpExcel)->getDebugLog()->setWriteDebugLog($saveDebugLog);
  168. }
  169. /**
  170. * Set temporary storage directory
  171. *
  172. * @deprecated
  173. * @param string $pValue Temporary storage directory
  174. * @throws PHPExcel_Writer_Exception when directory does not exist
  175. * @return PHPExcel_Writer_Excel5
  176. */
  177. public function setTempDir($pValue = '')
  178. {
  179. return $this;
  180. }
  181. /**
  182. * Build the Worksheet Escher objects
  183. *
  184. */
  185. private function buildWorksheetEschers()
  186. {
  187. // 1-based index to BstoreContainer
  188. $blipIndex = 0;
  189. $lastReducedSpId = 0;
  190. $lastSpId = 0;
  191. foreach ($this->phpExcel->getAllsheets() as $sheet) {
  192. // sheet index
  193. $sheetIndex = $sheet->getParent()->getIndex($sheet);
  194. $escher = null;
  195. // check if there are any shapes for this sheet
  196. $filterRange = $sheet->getAutoFilter()->getRange();
  197. if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
  198. continue;
  199. }
  200. // create intermediate Escher object
  201. $escher = new PHPExcel_Shared_Escher();
  202. // dgContainer
  203. $dgContainer = new PHPExcel_Shared_Escher_DgContainer();
  204. // set the drawing index (we use sheet index + 1)
  205. $dgId = $sheet->getParent()->getIndex($sheet) + 1;
  206. $dgContainer->setDgId($dgId);
  207. $escher->setDgContainer($dgContainer);
  208. // spgrContainer
  209. $spgrContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer();
  210. $dgContainer->setSpgrContainer($spgrContainer);
  211. // add one shape which is the group shape
  212. $spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
  213. $spContainer->setSpgr(true);
  214. $spContainer->setSpType(0);
  215. $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
  216. $spgrContainer->addChild($spContainer);
  217. // add the shapes
  218. $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
  219. foreach ($sheet->getDrawingCollection() as $drawing) {
  220. ++$blipIndex;
  221. ++$countShapes[$sheetIndex];
  222. // add the shape
  223. $spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
  224. // set the shape type
  225. $spContainer->setSpType(0x004B);
  226. // set the shape flag
  227. $spContainer->setSpFlag(0x02);
  228. // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
  229. $reducedSpId = $countShapes[$sheetIndex];
  230. $spId = $reducedSpId
  231. | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
  232. $spContainer->setSpId($spId);
  233. // keep track of last reducedSpId
  234. $lastReducedSpId = $reducedSpId;
  235. // keep track of last spId
  236. $lastSpId = $spId;
  237. // set the BLIP index
  238. $spContainer->setOPT(0x4104, $blipIndex);
  239. // set coordinates and offsets, client anchor
  240. $coordinates = $drawing->getCoordinates();
  241. $offsetX = $drawing->getOffsetX();
  242. $offsetY = $drawing->getOffsetY();
  243. $width = $drawing->getWidth();
  244. $height = $drawing->getHeight();
  245. $twoAnchor = PHPExcel_Shared_Excel5::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
  246. $spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
  247. $spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
  248. $spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
  249. $spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
  250. $spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
  251. $spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
  252. $spgrContainer->addChild($spContainer);
  253. }
  254. // AutoFilters
  255. if (!empty($filterRange)) {
  256. $rangeBounds = PHPExcel_Cell::rangeBoundaries($filterRange);
  257. $iNumColStart = $rangeBounds[0][0];
  258. $iNumColEnd = $rangeBounds[1][0];
  259. $iInc = $iNumColStart;
  260. while ($iInc <= $iNumColEnd) {
  261. ++$countShapes[$sheetIndex];
  262. // create an Drawing Object for the dropdown
  263. $oDrawing = new PHPExcel_Worksheet_BaseDrawing();
  264. // get the coordinates of drawing
  265. $cDrawing = PHPExcel_Cell::stringFromColumnIndex($iInc - 1) . $rangeBounds[0][1];
  266. $oDrawing->setCoordinates($cDrawing);
  267. $oDrawing->setWorksheet($sheet);
  268. // add the shape
  269. $spContainer = new PHPExcel_Shared_Escher_DgContainer_SpgrContainer_SpContainer();
  270. // set the shape type
  271. $spContainer->setSpType(0x00C9);
  272. // set the shape flag
  273. $spContainer->setSpFlag(0x01);
  274. // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
  275. $reducedSpId = $countShapes[$sheetIndex];
  276. $spId = $reducedSpId
  277. | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
  278. $spContainer->setSpId($spId);
  279. // keep track of last reducedSpId
  280. $lastReducedSpId = $reducedSpId;
  281. // keep track of last spId
  282. $lastSpId = $spId;
  283. $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
  284. $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
  285. $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
  286. $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
  287. $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
  288. // set coordinates and offsets, client anchor
  289. $endCoordinates = PHPExcel_Cell::stringFromColumnIndex(PHPExcel_Cell::stringFromColumnIndex($iInc - 1));
  290. $endCoordinates .= $rangeBounds[0][1] + 1;
  291. $spContainer->setStartCoordinates($cDrawing);
  292. $spContainer->setStartOffsetX(0);
  293. $spContainer->setStartOffsetY(0);
  294. $spContainer->setEndCoordinates($endCoordinates);
  295. $spContainer->setEndOffsetX(0);
  296. $spContainer->setEndOffsetY(0);
  297. $spgrContainer->addChild($spContainer);
  298. $iInc++;
  299. }
  300. }
  301. // identifier clusters, used for workbook Escher object
  302. $this->IDCLs[$dgId] = $lastReducedSpId;
  303. // set last shape index
  304. $dgContainer->setLastSpId($lastSpId);
  305. // set the Escher object
  306. $this->writerWorksheets[$sheetIndex]->setEscher($escher);
  307. }
  308. }
  309. /**
  310. * Build the Escher object corresponding to the MSODRAWINGGROUP record
  311. */
  312. private function buildWorkbookEscher()
  313. {
  314. $escher = null;
  315. // any drawings in this workbook?
  316. $found = false;
  317. foreach ($this->phpExcel->getAllSheets() as $sheet) {
  318. if (count($sheet->getDrawingCollection()) > 0) {
  319. $found = true;
  320. break;
  321. }
  322. }
  323. // nothing to do if there are no drawings
  324. if (!$found) {
  325. return;
  326. }
  327. // if we reach here, then there are drawings in the workbook
  328. $escher = new PHPExcel_Shared_Escher();
  329. // dggContainer
  330. $dggContainer = new PHPExcel_Shared_Escher_DggContainer();
  331. $escher->setDggContainer($dggContainer);
  332. // set IDCLs (identifier clusters)
  333. $dggContainer->setIDCLs($this->IDCLs);
  334. // this loop is for determining maximum shape identifier of all drawing
  335. $spIdMax = 0;
  336. $totalCountShapes = 0;
  337. $countDrawings = 0;
  338. foreach ($this->phpExcel->getAllsheets() as $sheet) {
  339. $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
  340. if (count($sheet->getDrawingCollection()) > 0) {
  341. ++$countDrawings;
  342. foreach ($sheet->getDrawingCollection() as $drawing) {
  343. ++$sheetCountShapes;
  344. ++$totalCountShapes;
  345. $spId = $sheetCountShapes | ($this->phpExcel->getIndex($sheet) + 1) << 10;
  346. $spIdMax = max($spId, $spIdMax);
  347. }
  348. }
  349. }
  350. $dggContainer->setSpIdMax($spIdMax + 1);
  351. $dggContainer->setCDgSaved($countDrawings);
  352. $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
  353. // bstoreContainer
  354. $bstoreContainer = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer();
  355. $dggContainer->setBstoreContainer($bstoreContainer);
  356. // the BSE's (all the images)
  357. foreach ($this->phpExcel->getAllsheets() as $sheet) {
  358. foreach ($sheet->getDrawingCollection() as $drawing) {
  359. if ($drawing instanceof PHPExcel_Worksheet_Drawing) {
  360. $filename = $drawing->getPath();
  361. list($imagesx, $imagesy, $imageFormat) = getimagesize($filename);
  362. switch ($imageFormat) {
  363. case 1: // GIF, not supported by BIFF8, we convert to PNG
  364. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
  365. ob_start();
  366. imagepng(imagecreatefromgif($filename));
  367. $blipData = ob_get_contents();
  368. ob_end_clean();
  369. break;
  370. case 2: // JPEG
  371. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG;
  372. $blipData = file_get_contents($filename);
  373. break;
  374. case 3: // PNG
  375. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
  376. $blipData = file_get_contents($filename);
  377. break;
  378. case 6: // Windows DIB (BMP), we convert to PNG
  379. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
  380. ob_start();
  381. imagepng(PHPExcel_Shared_Drawing::imagecreatefrombmp($filename));
  382. $blipData = ob_get_contents();
  383. ob_end_clean();
  384. break;
  385. default:
  386. continue 2;
  387. }
  388. $blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip();
  389. $blip->setData($blipData);
  390. $BSE = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE();
  391. $BSE->setBlipType($blipType);
  392. $BSE->setBlip($blip);
  393. $bstoreContainer->addBSE($BSE);
  394. } elseif ($drawing instanceof PHPExcel_Worksheet_MemoryDrawing) {
  395. switch ($drawing->getRenderingFunction()) {
  396. case PHPExcel_Worksheet_MemoryDrawing::RENDERING_JPEG:
  397. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_JPEG;
  398. $renderingFunction = 'imagejpeg';
  399. break;
  400. case PHPExcel_Worksheet_MemoryDrawing::RENDERING_GIF:
  401. case PHPExcel_Worksheet_MemoryDrawing::RENDERING_PNG:
  402. case PHPExcel_Worksheet_MemoryDrawing::RENDERING_DEFAULT:
  403. $blipType = PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE::BLIPTYPE_PNG;
  404. $renderingFunction = 'imagepng';
  405. break;
  406. }
  407. ob_start();
  408. call_user_func($renderingFunction, $drawing->getImageResource());
  409. $blipData = ob_get_contents();
  410. ob_end_clean();
  411. $blip = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE_Blip();
  412. $blip->setData($blipData);
  413. $BSE = new PHPExcel_Shared_Escher_DggContainer_BstoreContainer_BSE();
  414. $BSE->setBlipType($blipType);
  415. $BSE->setBlip($blip);
  416. $bstoreContainer->addBSE($BSE);
  417. }
  418. }
  419. }
  420. // Set the Escher object
  421. $this->writerWorkbook->setEscher($escher);
  422. }
  423. /**
  424. * Build the OLE Part for DocumentSummary Information
  425. * @return string
  426. */
  427. private function writeDocumentSummaryInformation()
  428. {
  429. // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
  430. $data = pack('v', 0xFFFE);
  431. // offset: 2; size: 2;
  432. $data .= pack('v', 0x0000);
  433. // offset: 4; size: 2; OS version
  434. $data .= pack('v', 0x0106);
  435. // offset: 6; size: 2; OS indicator
  436. $data .= pack('v', 0x0002);
  437. // offset: 8; size: 16
  438. $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
  439. // offset: 24; size: 4; section count
  440. $data .= pack('V', 0x0001);
  441. // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
  442. $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
  443. // offset: 44; size: 4; offset of the start
  444. $data .= pack('V', 0x30);
  445. // SECTION
  446. $dataSection = array();
  447. $dataSection_NumProps = 0;
  448. $dataSection_Summary = '';
  449. $dataSection_Content = '';
  450. // GKPIDDSI_CODEPAGE: CodePage
  451. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x01),
  452. 'offset' => array('pack' => 'V'),
  453. 'type' => array('pack' => 'V', 'data' => 0x02), // 2 byte signed integer
  454. 'data' => array('data' => 1252));
  455. $dataSection_NumProps++;
  456. // GKPIDDSI_CATEGORY : Category
  457. if ($this->phpExcel->getProperties()->getCategory()) {
  458. $dataProp = $this->phpExcel->getProperties()->getCategory();
  459. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x02),
  460. 'offset' => array('pack' => 'V'),
  461. 'type' => array('pack' => 'V', 'data' => 0x1E),
  462. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  463. $dataSection_NumProps++;
  464. }
  465. // GKPIDDSI_VERSION :Version of the application that wrote the property storage
  466. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x17),
  467. 'offset' => array('pack' => 'V'),
  468. 'type' => array('pack' => 'V', 'data' => 0x03),
  469. 'data' => array('pack' => 'V', 'data' => 0x000C0000));
  470. $dataSection_NumProps++;
  471. // GKPIDDSI_SCALE : FALSE
  472. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0B),
  473. 'offset' => array('pack' => 'V'),
  474. 'type' => array('pack' => 'V', 'data' => 0x0B),
  475. 'data' => array('data' => false));
  476. $dataSection_NumProps++;
  477. // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
  478. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x10),
  479. 'offset' => array('pack' => 'V'),
  480. 'type' => array('pack' => 'V', 'data' => 0x0B),
  481. 'data' => array('data' => false));
  482. $dataSection_NumProps++;
  483. // GKPIDDSI_SHAREDOC : FALSE
  484. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x13),
  485. 'offset' => array('pack' => 'V'),
  486. 'type' => array('pack' => 'V', 'data' => 0x0B),
  487. 'data' => array('data' => false));
  488. $dataSection_NumProps++;
  489. // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
  490. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x16),
  491. 'offset' => array('pack' => 'V'),
  492. 'type' => array('pack' => 'V', 'data' => 0x0B),
  493. 'data' => array('data' => false));
  494. $dataSection_NumProps++;
  495. // GKPIDDSI_DOCSPARTS
  496. // MS-OSHARED p75 (2.3.3.2.2.1)
  497. // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
  498. // cElements
  499. $dataProp = pack('v', 0x0001);
  500. $dataProp .= pack('v', 0x0000);
  501. // array of UnalignedLpstr
  502. // cch
  503. $dataProp .= pack('v', 0x000A);
  504. $dataProp .= pack('v', 0x0000);
  505. // value
  506. $dataProp .= 'Worksheet'.chr(0);
  507. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0D),
  508. 'offset' => array('pack' => 'V'),
  509. 'type' => array('pack' => 'V', 'data' => 0x101E),
  510. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  511. $dataSection_NumProps++;
  512. // GKPIDDSI_HEADINGPAIR
  513. // VtVecHeadingPairValue
  514. // cElements
  515. $dataProp = pack('v', 0x0002);
  516. $dataProp .= pack('v', 0x0000);
  517. // Array of vtHeadingPair
  518. // vtUnalignedString - headingString
  519. // stringType
  520. $dataProp .= pack('v', 0x001E);
  521. // padding
  522. $dataProp .= pack('v', 0x0000);
  523. // UnalignedLpstr
  524. // cch
  525. $dataProp .= pack('v', 0x0013);
  526. $dataProp .= pack('v', 0x0000);
  527. // value
  528. $dataProp .= 'Feuilles de calcul';
  529. // vtUnalignedString - headingParts
  530. // wType : 0x0003 = 32 bit signed integer
  531. $dataProp .= pack('v', 0x0300);
  532. // padding
  533. $dataProp .= pack('v', 0x0000);
  534. // value
  535. $dataProp .= pack('v', 0x0100);
  536. $dataProp .= pack('v', 0x0000);
  537. $dataProp .= pack('v', 0x0000);
  538. $dataProp .= pack('v', 0x0000);
  539. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0C),
  540. 'offset' => array('pack' => 'V'),
  541. 'type' => array('pack' => 'V', 'data' => 0x100C),
  542. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  543. $dataSection_NumProps++;
  544. // 4 Section Length
  545. // 4 Property count
  546. // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
  547. $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
  548. foreach ($dataSection as $dataProp) {
  549. // Summary
  550. $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
  551. // Offset
  552. $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
  553. // DataType
  554. $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
  555. // Data
  556. if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
  557. $dataSection_Content .= pack('V', $dataProp['data']['data']);
  558. $dataSection_Content_Offset += 4 + 4;
  559. } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
  560. $dataSection_Content .= pack('V', $dataProp['data']['data']);
  561. $dataSection_Content_Offset += 4 + 4;
  562. } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean
  563. if ($dataProp['data']['data'] == false) {
  564. $dataSection_Content .= pack('V', 0x0000);
  565. } else {
  566. $dataSection_Content .= pack('V', 0x0001);
  567. }
  568. $dataSection_Content_Offset += 4 + 4;
  569. } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
  570. // Null-terminated string
  571. $dataProp['data']['data'] .= chr(0);
  572. $dataProp['data']['length'] += 1;
  573. // Complete the string with null string for being a %4
  574. $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4)==4 ? 0 : (4 - $dataProp['data']['length'] % 4));
  575. $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
  576. $dataSection_Content .= pack('V', $dataProp['data']['length']);
  577. $dataSection_Content .= $dataProp['data']['data'];
  578. $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
  579. } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  580. $dataSection_Content .= $dataProp['data']['data'];
  581. $dataSection_Content_Offset += 4 + 8;
  582. } else {
  583. // Data Type Not Used at the moment
  584. $dataSection_Content .= $dataProp['data']['data'];
  585. $dataSection_Content_Offset += 4 + $dataProp['data']['length'];
  586. }
  587. }
  588. // Now $dataSection_Content_Offset contains the size of the content
  589. // section header
  590. // offset: $secOffset; size: 4; section length
  591. // + x Size of the content (summary + content)
  592. $data .= pack('V', $dataSection_Content_Offset);
  593. // offset: $secOffset+4; size: 4; property count
  594. $data .= pack('V', $dataSection_NumProps);
  595. // Section Summary
  596. $data .= $dataSection_Summary;
  597. // Section Content
  598. $data .= $dataSection_Content;
  599. return $data;
  600. }
  601. /**
  602. * Build the OLE Part for Summary Information
  603. * @return string
  604. */
  605. private function writeSummaryInformation()
  606. {
  607. // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
  608. $data = pack('v', 0xFFFE);
  609. // offset: 2; size: 2;
  610. $data .= pack('v', 0x0000);
  611. // offset: 4; size: 2; OS version
  612. $data .= pack('v', 0x0106);
  613. // offset: 6; size: 2; OS indicator
  614. $data .= pack('v', 0x0002);
  615. // offset: 8; size: 16
  616. $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
  617. // offset: 24; size: 4; section count
  618. $data .= pack('V', 0x0001);
  619. // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
  620. $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
  621. // offset: 44; size: 4; offset of the start
  622. $data .= pack('V', 0x30);
  623. // SECTION
  624. $dataSection = array();
  625. $dataSection_NumProps = 0;
  626. $dataSection_Summary = '';
  627. $dataSection_Content = '';
  628. // CodePage : CP-1252
  629. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x01),
  630. 'offset' => array('pack' => 'V'),
  631. 'type' => array('pack' => 'V', 'data' => 0x02), // 2 byte signed integer
  632. 'data' => array('data' => 1252));
  633. $dataSection_NumProps++;
  634. // Title
  635. if ($this->phpExcel->getProperties()->getTitle()) {
  636. $dataProp = $this->phpExcel->getProperties()->getTitle();
  637. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x02),
  638. 'offset' => array('pack' => 'V'),
  639. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  640. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  641. $dataSection_NumProps++;
  642. }
  643. // Subject
  644. if ($this->phpExcel->getProperties()->getSubject()) {
  645. $dataProp = $this->phpExcel->getProperties()->getSubject();
  646. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x03),
  647. 'offset' => array('pack' => 'V'),
  648. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  649. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  650. $dataSection_NumProps++;
  651. }
  652. // Author (Creator)
  653. if ($this->phpExcel->getProperties()->getCreator()) {
  654. $dataProp = $this->phpExcel->getProperties()->getCreator();
  655. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x04),
  656. 'offset' => array('pack' => 'V'),
  657. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  658. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  659. $dataSection_NumProps++;
  660. }
  661. // Keywords
  662. if ($this->phpExcel->getProperties()->getKeywords()) {
  663. $dataProp = $this->phpExcel->getProperties()->getKeywords();
  664. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x05),
  665. 'offset' => array('pack' => 'V'),
  666. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  667. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  668. $dataSection_NumProps++;
  669. }
  670. // Comments (Description)
  671. if ($this->phpExcel->getProperties()->getDescription()) {
  672. $dataProp = $this->phpExcel->getProperties()->getDescription();
  673. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x06),
  674. 'offset' => array('pack' => 'V'),
  675. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  676. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  677. $dataSection_NumProps++;
  678. }
  679. // Last Saved By (LastModifiedBy)
  680. if ($this->phpExcel->getProperties()->getLastModifiedBy()) {
  681. $dataProp = $this->phpExcel->getProperties()->getLastModifiedBy();
  682. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x08),
  683. 'offset' => array('pack' => 'V'),
  684. 'type' => array('pack' => 'V', 'data' => 0x1E), // null-terminated string prepended by dword string length
  685. 'data' => array('data' => $dataProp, 'length' => strlen($dataProp)));
  686. $dataSection_NumProps++;
  687. }
  688. // Created Date/Time
  689. if ($this->phpExcel->getProperties()->getCreated()) {
  690. $dataProp = $this->phpExcel->getProperties()->getCreated();
  691. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0C),
  692. 'offset' => array('pack' => 'V'),
  693. 'type' => array('pack' => 'V', 'data' => 0x40), // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  694. 'data' => array('data' => PHPExcel_Shared_OLE::LocalDate2OLE($dataProp)));
  695. $dataSection_NumProps++;
  696. }
  697. // Modified Date/Time
  698. if ($this->phpExcel->getProperties()->getModified()) {
  699. $dataProp = $this->phpExcel->getProperties()->getModified();
  700. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x0D),
  701. 'offset' => array('pack' => 'V'),
  702. 'type' => array('pack' => 'V', 'data' => 0x40), // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  703. 'data' => array('data' => PHPExcel_Shared_OLE::LocalDate2OLE($dataProp)));
  704. $dataSection_NumProps++;
  705. }
  706. // Security
  707. $dataSection[] = array('summary'=> array('pack' => 'V', 'data' => 0x13),
  708. 'offset' => array('pack' => 'V'),
  709. 'type' => array('pack' => 'V', 'data' => 0x03), // 4 byte signed integer
  710. 'data' => array('data' => 0x00));
  711. $dataSection_NumProps++;
  712. // 4 Section Length
  713. // 4 Property count
  714. // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
  715. $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
  716. foreach ($dataSection as $dataProp) {
  717. // Summary
  718. $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
  719. // Offset
  720. $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
  721. // DataType
  722. $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
  723. // Data
  724. if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
  725. $dataSection_Content .= pack('V', $dataProp['data']['data']);
  726. $dataSection_Content_Offset += 4 + 4;
  727. } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
  728. $dataSection_Content .= pack('V', $dataProp['data']['data']);
  729. $dataSection_Content_Offset += 4 + 4;
  730. } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
  731. // Null-terminated string
  732. $dataProp['data']['data'] .= chr(0);
  733. $dataProp['data']['length'] += 1;
  734. // Complete the string with null string for being a %4
  735. $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4)==4 ? 0 : (4 - $dataProp['data']['length'] % 4));
  736. $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
  737. $dataSection_Content .= pack('V', $dataProp['data']['length']);
  738. $dataSection_Content .= $dataProp['data']['data'];
  739. $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
  740. } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
  741. $dataSection_Content .= $dataProp['data']['data'];
  742. $dataSection_Content_Offset += 4 + 8;
  743. } else {
  744. // Data Type Not Used at the moment
  745. }
  746. }
  747. // Now $dataSection_Content_Offset contains the size of the content
  748. // section header
  749. // offset: $secOffset; size: 4; section length
  750. // + x Size of the content (summary + content)
  751. $data .= pack('V', $dataSection_Content_Offset);
  752. // offset: $secOffset+4; size: 4; property count
  753. $data .= pack('V', $dataSection_NumProps);
  754. // Section Summary
  755. $data .= $dataSection_Summary;
  756. // Section Content
  757. $data .= $dataSection_Content;
  758. return $data;
  759. }
  760. }