Spreadsheet.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185
  1. <?php
  2. namespace PhpOffice\PhpSpreadsheet;
  3. use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
  4. use PhpOffice\PhpSpreadsheet\Style\Style;
  5. use PhpOffice\PhpSpreadsheet\Worksheet\Iterator;
  6. use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
  7. class Spreadsheet
  8. {
  9. /**
  10. * Unique ID.
  11. *
  12. * @var string
  13. */
  14. private $uniqueID;
  15. /**
  16. * Document properties.
  17. *
  18. * @var Document\Properties
  19. */
  20. private $properties;
  21. /**
  22. * Document security.
  23. *
  24. * @var Document\Security
  25. */
  26. private $security;
  27. /**
  28. * Collection of Worksheet objects.
  29. *
  30. * @var Worksheet[]
  31. */
  32. private $workSheetCollection = [];
  33. /**
  34. * Calculation Engine.
  35. *
  36. * @var Calculation
  37. */
  38. private $calculationEngine;
  39. /**
  40. * Active sheet index.
  41. *
  42. * @var int
  43. */
  44. private $activeSheetIndex = 0;
  45. /**
  46. * Named ranges.
  47. *
  48. * @var NamedRange[]
  49. */
  50. private $namedRanges = [];
  51. /**
  52. * CellXf supervisor.
  53. *
  54. * @var Style
  55. */
  56. private $cellXfSupervisor;
  57. /**
  58. * CellXf collection.
  59. *
  60. * @var Style[]
  61. */
  62. private $cellXfCollection = [];
  63. /**
  64. * CellStyleXf collection.
  65. *
  66. * @var Style[]
  67. */
  68. private $cellStyleXfCollection = [];
  69. /**
  70. * hasMacros : this workbook have macros ?
  71. *
  72. * @var bool
  73. */
  74. private $hasMacros = false;
  75. /**
  76. * macrosCode : all macros code as binary data (the vbaProject.bin file, this include form, code, etc.), null if no macro.
  77. *
  78. * @var string
  79. */
  80. private $macrosCode;
  81. /**
  82. * macrosCertificate : if macros are signed, contains binary data vbaProjectSignature.bin file, null if not signed.
  83. *
  84. * @var string
  85. */
  86. private $macrosCertificate;
  87. /**
  88. * ribbonXMLData : null if workbook is'nt Excel 2007 or not contain a customized UI.
  89. *
  90. * @var null|string
  91. */
  92. private $ribbonXMLData;
  93. /**
  94. * ribbonBinObjects : null if workbook is'nt Excel 2007 or not contain embedded objects (picture(s)) for Ribbon Elements
  95. * ignored if $ribbonXMLData is null.
  96. *
  97. * @var null|array
  98. */
  99. private $ribbonBinObjects;
  100. /**
  101. * The workbook has macros ?
  102. *
  103. * @return bool
  104. */
  105. public function hasMacros()
  106. {
  107. return $this->hasMacros;
  108. }
  109. /**
  110. * Define if a workbook has macros.
  111. *
  112. * @param bool $hasMacros true|false
  113. */
  114. public function setHasMacros($hasMacros)
  115. {
  116. $this->hasMacros = (bool) $hasMacros;
  117. }
  118. /**
  119. * Set the macros code.
  120. *
  121. * @param string $macroCode string|null
  122. */
  123. public function setMacrosCode($macroCode)
  124. {
  125. $this->macrosCode = $macroCode;
  126. $this->setHasMacros($macroCode !== null);
  127. }
  128. /**
  129. * Return the macros code.
  130. *
  131. * @return null|string
  132. */
  133. public function getMacrosCode()
  134. {
  135. return $this->macrosCode;
  136. }
  137. /**
  138. * Set the macros certificate.
  139. *
  140. * @param null|string $certificate
  141. */
  142. public function setMacrosCertificate($certificate)
  143. {
  144. $this->macrosCertificate = $certificate;
  145. }
  146. /**
  147. * Is the project signed ?
  148. *
  149. * @return bool true|false
  150. */
  151. public function hasMacrosCertificate()
  152. {
  153. return $this->macrosCertificate !== null;
  154. }
  155. /**
  156. * Return the macros certificate.
  157. *
  158. * @return null|string
  159. */
  160. public function getMacrosCertificate()
  161. {
  162. return $this->macrosCertificate;
  163. }
  164. /**
  165. * Remove all macros, certificate from spreadsheet.
  166. */
  167. public function discardMacros()
  168. {
  169. $this->hasMacros = false;
  170. $this->macrosCode = null;
  171. $this->macrosCertificate = null;
  172. }
  173. /**
  174. * set ribbon XML data.
  175. *
  176. * @param null|mixed $target
  177. * @param null|mixed $xmlData
  178. */
  179. public function setRibbonXMLData($target, $xmlData)
  180. {
  181. if ($target !== null && $xmlData !== null) {
  182. $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
  183. } else {
  184. $this->ribbonXMLData = null;
  185. }
  186. }
  187. /**
  188. * retrieve ribbon XML Data.
  189. *
  190. * return string|null|array
  191. *
  192. * @param string $what
  193. *
  194. * @return string
  195. */
  196. public function getRibbonXMLData($what = 'all') //we need some constants here...
  197. {
  198. $returnData = null;
  199. $what = strtolower($what);
  200. switch ($what) {
  201. case 'all':
  202. $returnData = $this->ribbonXMLData;
  203. break;
  204. case 'target':
  205. case 'data':
  206. if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) {
  207. $returnData = $this->ribbonXMLData[$what];
  208. }
  209. break;
  210. }
  211. return $returnData;
  212. }
  213. /**
  214. * store binaries ribbon objects (pictures).
  215. *
  216. * @param null|mixed $BinObjectsNames
  217. * @param null|mixed $BinObjectsData
  218. */
  219. public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData)
  220. {
  221. if ($BinObjectsNames !== null && $BinObjectsData !== null) {
  222. $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
  223. } else {
  224. $this->ribbonBinObjects = null;
  225. }
  226. }
  227. /**
  228. * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
  229. *
  230. * @param mixed $path
  231. *
  232. * @return string
  233. */
  234. private function getExtensionOnly($path)
  235. {
  236. return pathinfo($path, PATHINFO_EXTENSION);
  237. }
  238. /**
  239. * retrieve Binaries Ribbon Objects.
  240. *
  241. * @param string $what
  242. *
  243. * @return null|array
  244. */
  245. public function getRibbonBinObjects($what = 'all')
  246. {
  247. $ReturnData = null;
  248. $what = strtolower($what);
  249. switch ($what) {
  250. case 'all':
  251. return $this->ribbonBinObjects;
  252. break;
  253. case 'names':
  254. case 'data':
  255. if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
  256. $ReturnData = $this->ribbonBinObjects[$what];
  257. }
  258. break;
  259. case 'types':
  260. if (is_array($this->ribbonBinObjects) &&
  261. isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])) {
  262. $tmpTypes = array_keys($this->ribbonBinObjects['data']);
  263. $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
  264. } else {
  265. $ReturnData = []; // the caller want an array... not null if empty
  266. }
  267. break;
  268. }
  269. return $ReturnData;
  270. }
  271. /**
  272. * This workbook have a custom UI ?
  273. *
  274. * @return bool
  275. */
  276. public function hasRibbon()
  277. {
  278. return $this->ribbonXMLData !== null;
  279. }
  280. /**
  281. * This workbook have additionnal object for the ribbon ?
  282. *
  283. * @return bool
  284. */
  285. public function hasRibbonBinObjects()
  286. {
  287. return $this->ribbonBinObjects !== null;
  288. }
  289. /**
  290. * Check if a sheet with a specified code name already exists.
  291. *
  292. * @param string $pSheetCodeName Name of the worksheet to check
  293. *
  294. * @return bool
  295. */
  296. public function sheetCodeNameExists($pSheetCodeName)
  297. {
  298. return $this->getSheetByCodeName($pSheetCodeName) !== null;
  299. }
  300. /**
  301. * Get sheet by code name. Warning : sheet don't have always a code name !
  302. *
  303. * @param string $pName Sheet name
  304. *
  305. * @return Worksheet
  306. */
  307. public function getSheetByCodeName($pName)
  308. {
  309. $worksheetCount = count($this->workSheetCollection);
  310. for ($i = 0; $i < $worksheetCount; ++$i) {
  311. if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
  312. return $this->workSheetCollection[$i];
  313. }
  314. }
  315. return null;
  316. }
  317. /**
  318. * Create a new PhpSpreadsheet with one Worksheet.
  319. */
  320. public function __construct()
  321. {
  322. $this->uniqueID = uniqid('', true);
  323. $this->calculationEngine = new Calculation($this);
  324. // Initialise worksheet collection and add one worksheet
  325. $this->workSheetCollection = [];
  326. $this->workSheetCollection[] = new Worksheet($this);
  327. $this->activeSheetIndex = 0;
  328. // Create document properties
  329. $this->properties = new Document\Properties();
  330. // Create document security
  331. $this->security = new Document\Security();
  332. // Set named ranges
  333. $this->namedRanges = [];
  334. // Create the cellXf supervisor
  335. $this->cellXfSupervisor = new Style(true);
  336. $this->cellXfSupervisor->bindParent($this);
  337. // Create the default style
  338. $this->addCellXf(new Style());
  339. $this->addCellStyleXf(new Style());
  340. }
  341. /**
  342. * Code to execute when this worksheet is unset().
  343. */
  344. public function __destruct()
  345. {
  346. $this->calculationEngine = null;
  347. $this->disconnectWorksheets();
  348. }
  349. /**
  350. * Disconnect all worksheets from this PhpSpreadsheet workbook object,
  351. * typically so that the PhpSpreadsheet object can be unset.
  352. */
  353. public function disconnectWorksheets()
  354. {
  355. $worksheet = null;
  356. foreach ($this->workSheetCollection as $k => &$worksheet) {
  357. $worksheet->disconnectCells();
  358. $this->workSheetCollection[$k] = null;
  359. }
  360. unset($worksheet);
  361. $this->workSheetCollection = [];
  362. }
  363. /**
  364. * Return the calculation engine for this worksheet.
  365. *
  366. * @return Calculation
  367. */
  368. public function getCalculationEngine()
  369. {
  370. return $this->calculationEngine;
  371. }
  372. /**
  373. * Get properties.
  374. *
  375. * @return Document\Properties
  376. */
  377. public function getProperties()
  378. {
  379. return $this->properties;
  380. }
  381. /**
  382. * Set properties.
  383. *
  384. * @param Document\Properties $pValue
  385. */
  386. public function setProperties(Document\Properties $pValue)
  387. {
  388. $this->properties = $pValue;
  389. }
  390. /**
  391. * Get security.
  392. *
  393. * @return Document\Security
  394. */
  395. public function getSecurity()
  396. {
  397. return $this->security;
  398. }
  399. /**
  400. * Set security.
  401. *
  402. * @param Document\Security $pValue
  403. */
  404. public function setSecurity(Document\Security $pValue)
  405. {
  406. $this->security = $pValue;
  407. }
  408. /**
  409. * Get active sheet.
  410. *
  411. * @throws Exception
  412. *
  413. * @return Worksheet
  414. */
  415. public function getActiveSheet()
  416. {
  417. return $this->getSheet($this->activeSheetIndex);
  418. }
  419. /**
  420. * Create sheet and add it to this workbook.
  421. *
  422. * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
  423. *
  424. * @throws Exception
  425. *
  426. * @return Worksheet
  427. */
  428. public function createSheet($sheetIndex = null)
  429. {
  430. $newSheet = new Worksheet($this);
  431. $this->addSheet($newSheet, $sheetIndex);
  432. return $newSheet;
  433. }
  434. /**
  435. * Check if a sheet with a specified name already exists.
  436. *
  437. * @param string $pSheetName Name of the worksheet to check
  438. *
  439. * @return bool
  440. */
  441. public function sheetNameExists($pSheetName)
  442. {
  443. return $this->getSheetByName($pSheetName) !== null;
  444. }
  445. /**
  446. * Add sheet.
  447. *
  448. * @param Worksheet $pSheet
  449. * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
  450. *
  451. * @throws Exception
  452. *
  453. * @return Worksheet
  454. */
  455. public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
  456. {
  457. if ($this->sheetNameExists($pSheet->getTitle())) {
  458. throw new Exception(
  459. "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
  460. );
  461. }
  462. if ($iSheetIndex === null) {
  463. if ($this->activeSheetIndex < 0) {
  464. $this->activeSheetIndex = 0;
  465. }
  466. $this->workSheetCollection[] = $pSheet;
  467. } else {
  468. // Insert the sheet at the requested index
  469. array_splice(
  470. $this->workSheetCollection,
  471. $iSheetIndex,
  472. 0,
  473. [$pSheet]
  474. );
  475. // Adjust active sheet index if necessary
  476. if ($this->activeSheetIndex >= $iSheetIndex) {
  477. ++$this->activeSheetIndex;
  478. }
  479. }
  480. if ($pSheet->getParent() === null) {
  481. $pSheet->rebindParent($this);
  482. }
  483. return $pSheet;
  484. }
  485. /**
  486. * Remove sheet by index.
  487. *
  488. * @param int $pIndex Active sheet index
  489. *
  490. * @throws Exception
  491. */
  492. public function removeSheetByIndex($pIndex)
  493. {
  494. $numSheets = count($this->workSheetCollection);
  495. if ($pIndex > $numSheets - 1) {
  496. throw new Exception(
  497. "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
  498. );
  499. }
  500. array_splice($this->workSheetCollection, $pIndex, 1);
  501. // Adjust active sheet index if necessary
  502. if (($this->activeSheetIndex >= $pIndex) &&
  503. ($pIndex > count($this->workSheetCollection) - 1)) {
  504. --$this->activeSheetIndex;
  505. }
  506. }
  507. /**
  508. * Get sheet by index.
  509. *
  510. * @param int $pIndex Sheet index
  511. *
  512. * @throws Exception
  513. *
  514. * @return Worksheet
  515. */
  516. public function getSheet($pIndex)
  517. {
  518. if (!isset($this->workSheetCollection[$pIndex])) {
  519. $numSheets = $this->getSheetCount();
  520. throw new Exception(
  521. "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
  522. );
  523. }
  524. return $this->workSheetCollection[$pIndex];
  525. }
  526. /**
  527. * Get all sheets.
  528. *
  529. * @return Worksheet[]
  530. */
  531. public function getAllSheets()
  532. {
  533. return $this->workSheetCollection;
  534. }
  535. /**
  536. * Get sheet by name.
  537. *
  538. * @param string $pName Sheet name
  539. *
  540. * @return Worksheet
  541. */
  542. public function getSheetByName($pName)
  543. {
  544. $worksheetCount = count($this->workSheetCollection);
  545. for ($i = 0; $i < $worksheetCount; ++$i) {
  546. if ($this->workSheetCollection[$i]->getTitle() === $pName) {
  547. return $this->workSheetCollection[$i];
  548. }
  549. }
  550. return null;
  551. }
  552. /**
  553. * Get index for sheet.
  554. *
  555. * @param Worksheet $pSheet
  556. *
  557. * @throws Exception
  558. *
  559. * @return int index
  560. */
  561. public function getIndex(Worksheet $pSheet)
  562. {
  563. foreach ($this->workSheetCollection as $key => $value) {
  564. if ($value->getHashCode() == $pSheet->getHashCode()) {
  565. return $key;
  566. }
  567. }
  568. throw new Exception('Sheet does not exist.');
  569. }
  570. /**
  571. * Set index for sheet by sheet name.
  572. *
  573. * @param string $sheetName Sheet name to modify index for
  574. * @param int $newIndex New index for the sheet
  575. *
  576. * @throws Exception
  577. *
  578. * @return int New sheet index
  579. */
  580. public function setIndexByName($sheetName, $newIndex)
  581. {
  582. $oldIndex = $this->getIndex($this->getSheetByName($sheetName));
  583. $pSheet = array_splice(
  584. $this->workSheetCollection,
  585. $oldIndex,
  586. 1
  587. );
  588. array_splice(
  589. $this->workSheetCollection,
  590. $newIndex,
  591. 0,
  592. $pSheet
  593. );
  594. return $newIndex;
  595. }
  596. /**
  597. * Get sheet count.
  598. *
  599. * @return int
  600. */
  601. public function getSheetCount()
  602. {
  603. return count($this->workSheetCollection);
  604. }
  605. /**
  606. * Get active sheet index.
  607. *
  608. * @return int Active sheet index
  609. */
  610. public function getActiveSheetIndex()
  611. {
  612. return $this->activeSheetIndex;
  613. }
  614. /**
  615. * Set active sheet index.
  616. *
  617. * @param int $pIndex Active sheet index
  618. *
  619. * @throws Exception
  620. *
  621. * @return Worksheet
  622. */
  623. public function setActiveSheetIndex($pIndex)
  624. {
  625. $numSheets = count($this->workSheetCollection);
  626. if ($pIndex > $numSheets - 1) {
  627. throw new Exception(
  628. "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
  629. );
  630. }
  631. $this->activeSheetIndex = $pIndex;
  632. return $this->getActiveSheet();
  633. }
  634. /**
  635. * Set active sheet index by name.
  636. *
  637. * @param string $pValue Sheet title
  638. *
  639. * @throws Exception
  640. *
  641. * @return Worksheet
  642. */
  643. public function setActiveSheetIndexByName($pValue)
  644. {
  645. if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
  646. $this->setActiveSheetIndex($this->getIndex($worksheet));
  647. return $worksheet;
  648. }
  649. throw new Exception('Workbook does not contain sheet:' . $pValue);
  650. }
  651. /**
  652. * Get sheet names.
  653. *
  654. * @return string[]
  655. */
  656. public function getSheetNames()
  657. {
  658. $returnValue = [];
  659. $worksheetCount = $this->getSheetCount();
  660. for ($i = 0; $i < $worksheetCount; ++$i) {
  661. $returnValue[] = $this->getSheet($i)->getTitle();
  662. }
  663. return $returnValue;
  664. }
  665. /**
  666. * Add external sheet.
  667. *
  668. * @param Worksheet $pSheet External sheet to add
  669. * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
  670. *
  671. * @throws Exception
  672. *
  673. * @return Worksheet
  674. */
  675. public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
  676. {
  677. if ($this->sheetNameExists($pSheet->getTitle())) {
  678. throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
  679. }
  680. // count how many cellXfs there are in this workbook currently, we will need this below
  681. $countCellXfs = count($this->cellXfCollection);
  682. // copy all the shared cellXfs from the external workbook and append them to the current
  683. foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
  684. $this->addCellXf(clone $cellXf);
  685. }
  686. // move sheet to this workbook
  687. $pSheet->rebindParent($this);
  688. // update the cellXfs
  689. foreach ($pSheet->getCoordinates(false) as $coordinate) {
  690. $cell = $pSheet->getCell($coordinate);
  691. $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
  692. }
  693. return $this->addSheet($pSheet, $iSheetIndex);
  694. }
  695. /**
  696. * Get named ranges.
  697. *
  698. * @return NamedRange[]
  699. */
  700. public function getNamedRanges()
  701. {
  702. return $this->namedRanges;
  703. }
  704. /**
  705. * Add named range.
  706. *
  707. * @param NamedRange $namedRange
  708. *
  709. * @return bool
  710. */
  711. public function addNamedRange(NamedRange $namedRange)
  712. {
  713. if ($namedRange->getScope() == null) {
  714. // global scope
  715. $this->namedRanges[$namedRange->getName()] = $namedRange;
  716. } else {
  717. // local scope
  718. $this->namedRanges[$namedRange->getScope()->getTitle() . '!' . $namedRange->getName()] = $namedRange;
  719. }
  720. return true;
  721. }
  722. /**
  723. * Get named range.
  724. *
  725. * @param string $namedRange
  726. * @param null|Worksheet $pSheet Scope. Use null for global scope
  727. *
  728. * @return null|NamedRange
  729. */
  730. public function getNamedRange($namedRange, Worksheet $pSheet = null)
  731. {
  732. $returnValue = null;
  733. if ($namedRange != '' && ($namedRange !== null)) {
  734. // first look for global defined name
  735. if (isset($this->namedRanges[$namedRange])) {
  736. $returnValue = $this->namedRanges[$namedRange];
  737. }
  738. // then look for local defined name (has priority over global defined name if both names exist)
  739. if (($pSheet !== null) && isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
  740. $returnValue = $this->namedRanges[$pSheet->getTitle() . '!' . $namedRange];
  741. }
  742. }
  743. return $returnValue;
  744. }
  745. /**
  746. * Remove named range.
  747. *
  748. * @param string $namedRange
  749. * @param null|Worksheet $pSheet scope: use null for global scope
  750. *
  751. * @return Spreadsheet
  752. */
  753. public function removeNamedRange($namedRange, Worksheet $pSheet = null)
  754. {
  755. if ($pSheet === null) {
  756. if (isset($this->namedRanges[$namedRange])) {
  757. unset($this->namedRanges[$namedRange]);
  758. }
  759. } else {
  760. if (isset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange])) {
  761. unset($this->namedRanges[$pSheet->getTitle() . '!' . $namedRange]);
  762. }
  763. }
  764. return $this;
  765. }
  766. /**
  767. * Get worksheet iterator.
  768. *
  769. * @return Iterator
  770. */
  771. public function getWorksheetIterator()
  772. {
  773. return new Iterator($this);
  774. }
  775. /**
  776. * Copy workbook (!= clone!).
  777. *
  778. * @return Spreadsheet
  779. */
  780. public function copy()
  781. {
  782. $copied = clone $this;
  783. $worksheetCount = count($this->workSheetCollection);
  784. for ($i = 0; $i < $worksheetCount; ++$i) {
  785. $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
  786. $this->workSheetCollection[$i]->rebindParent($this);
  787. }
  788. return $copied;
  789. }
  790. /**
  791. * Implement PHP __clone to create a deep clone, not just a shallow copy.
  792. */
  793. public function __clone()
  794. {
  795. foreach ($this as $key => $val) {
  796. if (is_object($val) || (is_array($val))) {
  797. $this->{$key} = unserialize(serialize($val));
  798. }
  799. }
  800. }
  801. /**
  802. * Get the workbook collection of cellXfs.
  803. *
  804. * @return Style[]
  805. */
  806. public function getCellXfCollection()
  807. {
  808. return $this->cellXfCollection;
  809. }
  810. /**
  811. * Get cellXf by index.
  812. *
  813. * @param int $pIndex
  814. *
  815. * @return Style
  816. */
  817. public function getCellXfByIndex($pIndex)
  818. {
  819. return $this->cellXfCollection[$pIndex];
  820. }
  821. /**
  822. * Get cellXf by hash code.
  823. *
  824. * @param string $pValue
  825. *
  826. * @return false|Style
  827. */
  828. public function getCellXfByHashCode($pValue)
  829. {
  830. foreach ($this->cellXfCollection as $cellXf) {
  831. if ($cellXf->getHashCode() == $pValue) {
  832. return $cellXf;
  833. }
  834. }
  835. return false;
  836. }
  837. /**
  838. * Check if style exists in style collection.
  839. *
  840. * @param Style $pCellStyle
  841. *
  842. * @return bool
  843. */
  844. public function cellXfExists($pCellStyle)
  845. {
  846. return in_array($pCellStyle, $this->cellXfCollection, true);
  847. }
  848. /**
  849. * Get default style.
  850. *
  851. * @throws Exception
  852. *
  853. * @return Style
  854. */
  855. public function getDefaultStyle()
  856. {
  857. if (isset($this->cellXfCollection[0])) {
  858. return $this->cellXfCollection[0];
  859. }
  860. throw new Exception('No default style found for this workbook');
  861. }
  862. /**
  863. * Add a cellXf to the workbook.
  864. *
  865. * @param Style $style
  866. */
  867. public function addCellXf(Style $style)
  868. {
  869. $this->cellXfCollection[] = $style;
  870. $style->setIndex(count($this->cellXfCollection) - 1);
  871. }
  872. /**
  873. * Remove cellXf by index. It is ensured that all cells get their xf index updated.
  874. *
  875. * @param int $pIndex Index to cellXf
  876. *
  877. * @throws Exception
  878. */
  879. public function removeCellXfByIndex($pIndex)
  880. {
  881. if ($pIndex > count($this->cellXfCollection) - 1) {
  882. throw new Exception('CellXf index is out of bounds.');
  883. }
  884. // first remove the cellXf
  885. array_splice($this->cellXfCollection, $pIndex, 1);
  886. // then update cellXf indexes for cells
  887. foreach ($this->workSheetCollection as $worksheet) {
  888. foreach ($worksheet->getCoordinates(false) as $coordinate) {
  889. $cell = $worksheet->getCell($coordinate);
  890. $xfIndex = $cell->getXfIndex();
  891. if ($xfIndex > $pIndex) {
  892. // decrease xf index by 1
  893. $cell->setXfIndex($xfIndex - 1);
  894. } elseif ($xfIndex == $pIndex) {
  895. // set to default xf index 0
  896. $cell->setXfIndex(0);
  897. }
  898. }
  899. }
  900. }
  901. /**
  902. * Get the cellXf supervisor.
  903. *
  904. * @return Style
  905. */
  906. public function getCellXfSupervisor()
  907. {
  908. return $this->cellXfSupervisor;
  909. }
  910. /**
  911. * Get the workbook collection of cellStyleXfs.
  912. *
  913. * @return Style[]
  914. */
  915. public function getCellStyleXfCollection()
  916. {
  917. return $this->cellStyleXfCollection;
  918. }
  919. /**
  920. * Get cellStyleXf by index.
  921. *
  922. * @param int $pIndex Index to cellXf
  923. *
  924. * @return Style
  925. */
  926. public function getCellStyleXfByIndex($pIndex)
  927. {
  928. return $this->cellStyleXfCollection[$pIndex];
  929. }
  930. /**
  931. * Get cellStyleXf by hash code.
  932. *
  933. * @param string $pValue
  934. *
  935. * @return false|Style
  936. */
  937. public function getCellStyleXfByHashCode($pValue)
  938. {
  939. foreach ($this->cellStyleXfCollection as $cellStyleXf) {
  940. if ($cellStyleXf->getHashCode() == $pValue) {
  941. return $cellStyleXf;
  942. }
  943. }
  944. return false;
  945. }
  946. /**
  947. * Add a cellStyleXf to the workbook.
  948. *
  949. * @param Style $pStyle
  950. */
  951. public function addCellStyleXf(Style $pStyle)
  952. {
  953. $this->cellStyleXfCollection[] = $pStyle;
  954. $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
  955. }
  956. /**
  957. * Remove cellStyleXf by index.
  958. *
  959. * @param int $pIndex Index to cellXf
  960. *
  961. * @throws Exception
  962. */
  963. public function removeCellStyleXfByIndex($pIndex)
  964. {
  965. if ($pIndex > count($this->cellStyleXfCollection) - 1) {
  966. throw new Exception('CellStyleXf index is out of bounds.');
  967. }
  968. array_splice($this->cellStyleXfCollection, $pIndex, 1);
  969. }
  970. /**
  971. * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
  972. * and columns in the workbook.
  973. */
  974. public function garbageCollect()
  975. {
  976. // how many references are there to each cellXf ?
  977. $countReferencesCellXf = [];
  978. foreach ($this->cellXfCollection as $index => $cellXf) {
  979. $countReferencesCellXf[$index] = 0;
  980. }
  981. foreach ($this->getWorksheetIterator() as $sheet) {
  982. // from cells
  983. foreach ($sheet->getCoordinates(false) as $coordinate) {
  984. $cell = $sheet->getCell($coordinate);
  985. ++$countReferencesCellXf[$cell->getXfIndex()];
  986. }
  987. // from row dimensions
  988. foreach ($sheet->getRowDimensions() as $rowDimension) {
  989. if ($rowDimension->getXfIndex() !== null) {
  990. ++$countReferencesCellXf[$rowDimension->getXfIndex()];
  991. }
  992. }
  993. // from column dimensions
  994. foreach ($sheet->getColumnDimensions() as $columnDimension) {
  995. ++$countReferencesCellXf[$columnDimension->getXfIndex()];
  996. }
  997. }
  998. // remove cellXfs without references and create mapping so we can update xfIndex
  999. // for all cells and columns
  1000. $countNeededCellXfs = 0;
  1001. foreach ($this->cellXfCollection as $index => $cellXf) {
  1002. if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
  1003. ++$countNeededCellXfs;
  1004. } else {
  1005. unset($this->cellXfCollection[$index]);
  1006. }
  1007. $map[$index] = $countNeededCellXfs - 1;
  1008. }
  1009. $this->cellXfCollection = array_values($this->cellXfCollection);
  1010. // update the index for all cellXfs
  1011. foreach ($this->cellXfCollection as $i => $cellXf) {
  1012. $cellXf->setIndex($i);
  1013. }
  1014. // make sure there is always at least one cellXf (there should be)
  1015. if (empty($this->cellXfCollection)) {
  1016. $this->cellXfCollection[] = new Style();
  1017. }
  1018. // update the xfIndex for all cells, row dimensions, column dimensions
  1019. foreach ($this->getWorksheetIterator() as $sheet) {
  1020. // for all cells
  1021. foreach ($sheet->getCoordinates(false) as $coordinate) {
  1022. $cell = $sheet->getCell($coordinate);
  1023. $cell->setXfIndex($map[$cell->getXfIndex()]);
  1024. }
  1025. // for all row dimensions
  1026. foreach ($sheet->getRowDimensions() as $rowDimension) {
  1027. if ($rowDimension->getXfIndex() !== null) {
  1028. $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
  1029. }
  1030. }
  1031. // for all column dimensions
  1032. foreach ($sheet->getColumnDimensions() as $columnDimension) {
  1033. $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
  1034. }
  1035. // also do garbage collection for all the sheets
  1036. $sheet->garbageCollect();
  1037. }
  1038. }
  1039. /**
  1040. * Return the unique ID value assigned to this spreadsheet workbook.
  1041. *
  1042. * @return string
  1043. */
  1044. public function getID()
  1045. {
  1046. return $this->uniqueID;
  1047. }
  1048. }