Cell.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  1. <?php
  2. /**
  3. * PHPExcel_Cell
  4. *
  5. * Copyright (c) 2006 - 2015 PHPExcel
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20. *
  21. * @category PHPExcel
  22. * @package PHPExcel_Cell
  23. * @copyright Copyright (c) 2006 - 2015 PHPExcel (http://www.codeplex.com/PHPExcel)
  24. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
  25. * @version ##VERSION##, ##DATE##
  26. */
  27. class PHPExcel_Cell
  28. {
  29. /**
  30. * Default range variable constant
  31. *
  32. * @var string
  33. */
  34. const DEFAULT_RANGE = 'A1:A1';
  35. /**
  36. * Value binder to use
  37. *
  38. * @var PHPExcel_Cell_IValueBinder
  39. */
  40. private static $valueBinder;
  41. /**
  42. * Value of the cell
  43. *
  44. * @var mixed
  45. */
  46. private $value;
  47. /**
  48. * Calculated value of the cell (used for caching)
  49. * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
  50. * create the original spreadsheet file.
  51. * Note that this value is not guaranteed to reflect the actual calculated value because it is
  52. * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
  53. * values used by the formula have changed since it was last calculated.
  54. *
  55. * @var mixed
  56. */
  57. private $calculatedValue;
  58. /**
  59. * Type of the cell data
  60. *
  61. * @var string
  62. */
  63. private $dataType;
  64. /**
  65. * Parent worksheet
  66. *
  67. * @var PHPExcel_CachedObjectStorage_CacheBase
  68. */
  69. private $parent;
  70. /**
  71. * Index to cellXf
  72. *
  73. * @var int
  74. */
  75. private $xfIndex = 0;
  76. /**
  77. * Attributes of the formula
  78. *
  79. */
  80. private $formulaAttributes;
  81. /**
  82. * Send notification to the cache controller
  83. *
  84. * @return void
  85. **/
  86. public function notifyCacheController()
  87. {
  88. $this->parent->updateCacheData($this);
  89. return $this;
  90. }
  91. public function detach()
  92. {
  93. $this->parent = null;
  94. }
  95. public function attach(PHPExcel_CachedObjectStorage_CacheBase $parent)
  96. {
  97. $this->parent = $parent;
  98. }
  99. /**
  100. * Create a new Cell
  101. *
  102. * @param mixed $pValue
  103. * @param string $pDataType
  104. * @param PHPExcel_Worksheet $pSheet
  105. * @throws PHPExcel_Exception
  106. */
  107. public function __construct($pValue = null, $pDataType = null, PHPExcel_Worksheet $pSheet = null)
  108. {
  109. // Initialise cell value
  110. $this->value = $pValue;
  111. // Set worksheet cache
  112. $this->parent = $pSheet->getCellCacheController();
  113. // Set datatype?
  114. if ($pDataType !== null) {
  115. if ($pDataType == PHPExcel_Cell_DataType::TYPE_STRING2) {
  116. $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
  117. }
  118. $this->dataType = $pDataType;
  119. } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
  120. throw new PHPExcel_Exception("Value could not be bound to cell.");
  121. }
  122. }
  123. /**
  124. * Get cell coordinate column
  125. *
  126. * @return string
  127. */
  128. public function getColumn()
  129. {
  130. return $this->parent->getCurrentColumn();
  131. }
  132. /**
  133. * Get cell coordinate row
  134. *
  135. * @return int
  136. */
  137. public function getRow()
  138. {
  139. return $this->parent->getCurrentRow();
  140. }
  141. /**
  142. * Get cell coordinate
  143. *
  144. * @return string
  145. */
  146. public function getCoordinate()
  147. {
  148. return $this->parent->getCurrentAddress();
  149. }
  150. /**
  151. * Get cell value
  152. *
  153. * @return mixed
  154. */
  155. public function getValue()
  156. {
  157. return $this->value;
  158. }
  159. /**
  160. * Get cell value with formatting
  161. *
  162. * @return string
  163. */
  164. public function getFormattedValue()
  165. {
  166. return (string) PHPExcel_Style_NumberFormat::toFormattedString(
  167. $this->getCalculatedValue(),
  168. $this->getStyle()
  169. ->getNumberFormat()->getFormatCode()
  170. );
  171. }
  172. /**
  173. * Set cell value
  174. *
  175. * Sets the value for a cell, automatically determining the datatype using the value binder
  176. *
  177. * @param mixed $pValue Value
  178. * @return PHPExcel_Cell
  179. * @throws PHPExcel_Exception
  180. */
  181. public function setValue($pValue = null)
  182. {
  183. if (!self::getValueBinder()->bindValue($this, $pValue)) {
  184. throw new PHPExcel_Exception("Value could not be bound to cell.");
  185. }
  186. return $this;
  187. }
  188. /**
  189. * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder)
  190. *
  191. * @param mixed $pValue Value
  192. * @param string $pDataType Explicit data type
  193. * @return PHPExcel_Cell
  194. * @throws PHPExcel_Exception
  195. */
  196. public function setValueExplicit($pValue = null, $pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
  197. {
  198. // set the value according to data type
  199. switch ($pDataType) {
  200. case PHPExcel_Cell_DataType::TYPE_NULL:
  201. $this->value = $pValue;
  202. break;
  203. case PHPExcel_Cell_DataType::TYPE_STRING2:
  204. $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
  205. // no break
  206. case PHPExcel_Cell_DataType::TYPE_STRING:
  207. // Synonym for string
  208. case PHPExcel_Cell_DataType::TYPE_INLINE:
  209. // Rich text
  210. $this->value = PHPExcel_Cell_DataType::checkString($pValue);
  211. break;
  212. case PHPExcel_Cell_DataType::TYPE_NUMERIC:
  213. $this->value = (float) $pValue;
  214. break;
  215. case PHPExcel_Cell_DataType::TYPE_FORMULA:
  216. $this->value = (string) $pValue;
  217. break;
  218. case PHPExcel_Cell_DataType::TYPE_BOOL:
  219. $this->value = (bool) $pValue;
  220. break;
  221. case PHPExcel_Cell_DataType::TYPE_ERROR:
  222. $this->value = PHPExcel_Cell_DataType::checkErrorCode($pValue);
  223. break;
  224. default:
  225. throw new PHPExcel_Exception('Invalid datatype: ' . $pDataType);
  226. break;
  227. }
  228. // set the datatype
  229. $this->dataType = $pDataType;
  230. return $this->notifyCacheController();
  231. }
  232. /**
  233. * Get calculated cell value
  234. *
  235. * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
  236. *
  237. * @param boolean $resetLog Whether the calculation engine logger should be reset or not
  238. * @return mixed
  239. * @throws PHPExcel_Exception
  240. */
  241. public function getCalculatedValue($resetLog = true)
  242. {
  243. //echo 'Cell '.$this->getCoordinate().' value is a '.$this->dataType.' with a value of '.$this->getValue().PHP_EOL;
  244. if ($this->dataType == PHPExcel_Cell_DataType::TYPE_FORMULA) {
  245. try {
  246. //echo 'Cell value for '.$this->getCoordinate().' is a formula: Calculating value'.PHP_EOL;
  247. $result = PHPExcel_Calculation::getInstance(
  248. $this->getWorksheet()->getParent()
  249. )->calculateCellValue($this, $resetLog);
  250. //echo $this->getCoordinate().' calculation result is '.$result.PHP_EOL;
  251. // We don't yet handle array returns
  252. if (is_array($result)) {
  253. while (is_array($result)) {
  254. $result = array_pop($result);
  255. }
  256. }
  257. } catch (PHPExcel_Exception $ex) {
  258. if (($ex->getMessage() === 'Unable to access External Workbook') && ($this->calculatedValue !== null)) {
  259. //echo 'Returning fallback value of '.$this->calculatedValue.' for cell '.$this->getCoordinate().PHP_EOL;
  260. return $this->calculatedValue; // Fallback for calculations referencing external files.
  261. }
  262. //echo 'Calculation Exception: '.$ex->getMessage().PHP_EOL;
  263. $result = '#N/A';
  264. throw new PHPExcel_Calculation_Exception(
  265. $this->getWorksheet()->getTitle().'!'.$this->getCoordinate().' -> '.$ex->getMessage()
  266. );
  267. }
  268. if ($result === '#Not Yet Implemented') {
  269. //echo 'Returning fallback value of '.$this->calculatedValue.' for cell '.$this->getCoordinate().PHP_EOL;
  270. return $this->calculatedValue; // Fallback if calculation engine does not support the formula.
  271. }
  272. //echo 'Returning calculated value of '.$result.' for cell '.$this->getCoordinate().PHP_EOL;
  273. return $result;
  274. } elseif ($this->value instanceof PHPExcel_RichText) {
  275. // echo 'Cell value for '.$this->getCoordinate().' is rich text: Returning data value of '.$this->value.'<br />';
  276. return $this->value->getPlainText();
  277. }
  278. // echo 'Cell value for '.$this->getCoordinate().' is not a formula: Returning data value of '.$this->value.'<br />';
  279. return $this->value;
  280. }
  281. /**
  282. * Set old calculated value (cached)
  283. *
  284. * @param mixed $pValue Value
  285. * @return PHPExcel_Cell
  286. */
  287. public function setCalculatedValue($pValue = null)
  288. {
  289. if ($pValue !== null) {
  290. $this->calculatedValue = (is_numeric($pValue)) ? (float) $pValue : $pValue;
  291. }
  292. return $this->notifyCacheController();
  293. }
  294. /**
  295. * Get old calculated value (cached)
  296. * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
  297. * create the original spreadsheet file.
  298. * Note that this value is not guaranteed to refelect the actual calculated value because it is
  299. * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
  300. * values used by the formula have changed since it was last calculated.
  301. *
  302. * @return mixed
  303. */
  304. public function getOldCalculatedValue()
  305. {
  306. return $this->calculatedValue;
  307. }
  308. /**
  309. * Get cell data type
  310. *
  311. * @return string
  312. */
  313. public function getDataType()
  314. {
  315. return $this->dataType;
  316. }
  317. /**
  318. * Set cell data type
  319. *
  320. * @param string $pDataType
  321. * @return PHPExcel_Cell
  322. */
  323. public function setDataType($pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
  324. {
  325. if ($pDataType == PHPExcel_Cell_DataType::TYPE_STRING2) {
  326. $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
  327. }
  328. $this->dataType = $pDataType;
  329. return $this->notifyCacheController();
  330. }
  331. /**
  332. * Identify if the cell contains a formula
  333. *
  334. * @return boolean
  335. */
  336. public function isFormula()
  337. {
  338. return $this->dataType == PHPExcel_Cell_DataType::TYPE_FORMULA;
  339. }
  340. /**
  341. * Does this cell contain Data validation rules?
  342. *
  343. * @return boolean
  344. * @throws PHPExcel_Exception
  345. */
  346. public function hasDataValidation()
  347. {
  348. if (!isset($this->parent)) {
  349. throw new PHPExcel_Exception('Cannot check for data validation when cell is not bound to a worksheet');
  350. }
  351. return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
  352. }
  353. /**
  354. * Get Data validation rules
  355. *
  356. * @return PHPExcel_Cell_DataValidation
  357. * @throws PHPExcel_Exception
  358. */
  359. public function getDataValidation()
  360. {
  361. if (!isset($this->parent)) {
  362. throw new PHPExcel_Exception('Cannot get data validation for cell that is not bound to a worksheet');
  363. }
  364. return $this->getWorksheet()->getDataValidation($this->getCoordinate());
  365. }
  366. /**
  367. * Set Data validation rules
  368. *
  369. * @param PHPExcel_Cell_DataValidation $pDataValidation
  370. * @return PHPExcel_Cell
  371. * @throws PHPExcel_Exception
  372. */
  373. public function setDataValidation(PHPExcel_Cell_DataValidation $pDataValidation = null)
  374. {
  375. if (!isset($this->parent)) {
  376. throw new PHPExcel_Exception('Cannot set data validation for cell that is not bound to a worksheet');
  377. }
  378. $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
  379. return $this->notifyCacheController();
  380. }
  381. /**
  382. * Does this cell contain a Hyperlink?
  383. *
  384. * @return boolean
  385. * @throws PHPExcel_Exception
  386. */
  387. public function hasHyperlink()
  388. {
  389. if (!isset($this->parent)) {
  390. throw new PHPExcel_Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
  391. }
  392. return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
  393. }
  394. /**
  395. * Get Hyperlink
  396. *
  397. * @return PHPExcel_Cell_Hyperlink
  398. * @throws PHPExcel_Exception
  399. */
  400. public function getHyperlink()
  401. {
  402. if (!isset($this->parent)) {
  403. throw new PHPExcel_Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
  404. }
  405. return $this->getWorksheet()->getHyperlink($this->getCoordinate());
  406. }
  407. /**
  408. * Set Hyperlink
  409. *
  410. * @param PHPExcel_Cell_Hyperlink $pHyperlink
  411. * @return PHPExcel_Cell
  412. * @throws PHPExcel_Exception
  413. */
  414. public function setHyperlink(PHPExcel_Cell_Hyperlink $pHyperlink = null)
  415. {
  416. if (!isset($this->parent)) {
  417. throw new PHPExcel_Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
  418. }
  419. $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
  420. return $this->notifyCacheController();
  421. }
  422. /**
  423. * Get parent worksheet
  424. *
  425. * @return PHPExcel_CachedObjectStorage_CacheBase
  426. */
  427. public function getParent()
  428. {
  429. return $this->parent;
  430. }
  431. /**
  432. * Get parent worksheet
  433. *
  434. * @return PHPExcel_Worksheet
  435. */
  436. public function getWorksheet()
  437. {
  438. return $this->parent->getParent();
  439. }
  440. /**
  441. * Is this cell in a merge range
  442. *
  443. * @return boolean
  444. */
  445. public function isInMergeRange()
  446. {
  447. return (boolean) $this->getMergeRange();
  448. }
  449. /**
  450. * Is this cell the master (top left cell) in a merge range (that holds the actual data value)
  451. *
  452. * @return boolean
  453. */
  454. public function isMergeRangeValueCell()
  455. {
  456. if ($mergeRange = $this->getMergeRange()) {
  457. $mergeRange = PHPExcel_Cell::splitRange($mergeRange);
  458. list($startCell) = $mergeRange[0];
  459. if ($this->getCoordinate() === $startCell) {
  460. return true;
  461. }
  462. }
  463. return false;
  464. }
  465. /**
  466. * If this cell is in a merge range, then return the range
  467. *
  468. * @return string
  469. */
  470. public function getMergeRange()
  471. {
  472. foreach ($this->getWorksheet()->getMergeCells() as $mergeRange) {
  473. if ($this->isInRange($mergeRange)) {
  474. return $mergeRange;
  475. }
  476. }
  477. return false;
  478. }
  479. /**
  480. * Get cell style
  481. *
  482. * @return PHPExcel_Style
  483. */
  484. public function getStyle()
  485. {
  486. return $this->getWorksheet()->getStyle($this->getCoordinate());
  487. }
  488. /**
  489. * Re-bind parent
  490. *
  491. * @param PHPExcel_Worksheet $parent
  492. * @return PHPExcel_Cell
  493. */
  494. public function rebindParent(PHPExcel_Worksheet $parent)
  495. {
  496. $this->parent = $parent->getCellCacheController();
  497. return $this->notifyCacheController();
  498. }
  499. /**
  500. * Is cell in a specific range?
  501. *
  502. * @param string $pRange Cell range (e.g. A1:A1)
  503. * @return boolean
  504. */
  505. public function isInRange($pRange = 'A1:A1')
  506. {
  507. list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
  508. // Translate properties
  509. $myColumn = self::columnIndexFromString($this->getColumn());
  510. $myRow = $this->getRow();
  511. // Verify if cell is in range
  512. return (($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
  513. ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow)
  514. );
  515. }
  516. /**
  517. * Coordinate from string
  518. *
  519. * @param string $pCoordinateString
  520. * @return array Array containing column and row (indexes 0 and 1)
  521. * @throws PHPExcel_Exception
  522. */
  523. public static function coordinateFromString($pCoordinateString = 'A1')
  524. {
  525. if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) {
  526. return array($matches[1],$matches[2]);
  527. } elseif ((strpos($pCoordinateString, ':') !== false) || (strpos($pCoordinateString, ',') !== false)) {
  528. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  529. } elseif ($pCoordinateString == '') {
  530. throw new PHPExcel_Exception('Cell coordinate can not be zero-length string');
  531. }
  532. throw new PHPExcel_Exception('Invalid cell coordinate '.$pCoordinateString);
  533. }
  534. /**
  535. * Make string row, column or cell coordinate absolute
  536. *
  537. * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
  538. * Note that this value can be a row or column reference as well as a cell reference
  539. * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
  540. * @throws PHPExcel_Exception
  541. */
  542. public static function absoluteReference($pCoordinateString = 'A1')
  543. {
  544. if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
  545. // Split out any worksheet name from the reference
  546. $worksheet = '';
  547. $cellAddress = explode('!', $pCoordinateString);
  548. if (count($cellAddress) > 1) {
  549. list($worksheet, $pCoordinateString) = $cellAddress;
  550. }
  551. if ($worksheet > '') {
  552. $worksheet .= '!';
  553. }
  554. // Create absolute coordinate
  555. if (ctype_digit($pCoordinateString)) {
  556. return $worksheet . '$' . $pCoordinateString;
  557. } elseif (ctype_alpha($pCoordinateString)) {
  558. return $worksheet . '$' . strtoupper($pCoordinateString);
  559. }
  560. return $worksheet . self::absoluteCoordinate($pCoordinateString);
  561. }
  562. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  563. }
  564. /**
  565. * Make string coordinate absolute
  566. *
  567. * @param string $pCoordinateString e.g. 'A1'
  568. * @return string Absolute coordinate e.g. '$A$1'
  569. * @throws PHPExcel_Exception
  570. */
  571. public static function absoluteCoordinate($pCoordinateString = 'A1')
  572. {
  573. if (strpos($pCoordinateString, ':') === false && strpos($pCoordinateString, ',') === false) {
  574. // Split out any worksheet name from the coordinate
  575. $worksheet = '';
  576. $cellAddress = explode('!', $pCoordinateString);
  577. if (count($cellAddress) > 1) {
  578. list($worksheet, $pCoordinateString) = $cellAddress;
  579. }
  580. if ($worksheet > '') {
  581. $worksheet .= '!';
  582. }
  583. // Create absolute coordinate
  584. list($column, $row) = self::coordinateFromString($pCoordinateString);
  585. $column = ltrim($column, '$');
  586. $row = ltrim($row, '$');
  587. return $worksheet . '$' . $column . '$' . $row;
  588. }
  589. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  590. }
  591. /**
  592. * Split range into coordinate strings
  593. *
  594. * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
  595. * @return array Array containg one or more arrays containing one or two coordinate strings
  596. * e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11'))
  597. * or array('B4')
  598. */
  599. public static function splitRange($pRange = 'A1:A1')
  600. {
  601. // Ensure $pRange is a valid range
  602. if (empty($pRange)) {
  603. $pRange = self::DEFAULT_RANGE;
  604. }
  605. $exploded = explode(',', $pRange);
  606. $counter = count($exploded);
  607. for ($i = 0; $i < $counter; ++$i) {
  608. $exploded[$i] = explode(':', $exploded[$i]);
  609. }
  610. return $exploded;
  611. }
  612. /**
  613. * Build range from coordinate strings
  614. *
  615. * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
  616. * @return string String representation of $pRange
  617. * @throws PHPExcel_Exception
  618. */
  619. public static function buildRange($pRange)
  620. {
  621. // Verify range
  622. if (!is_array($pRange) || empty($pRange) || !is_array($pRange[0])) {
  623. throw new PHPExcel_Exception('Range does not contain any information');
  624. }
  625. // Build range
  626. $imploded = array();
  627. $counter = count($pRange);
  628. for ($i = 0; $i < $counter; ++$i) {
  629. $pRange[$i] = implode(':', $pRange[$i]);
  630. }
  631. $imploded = implode(',', $pRange);
  632. return $imploded;
  633. }
  634. /**
  635. * Calculate range boundaries
  636. *
  637. * @param string $pRange Cell range (e.g. A1:A1)
  638. * @return array Range coordinates array(Start Cell, End Cell)
  639. * where Start Cell and End Cell are arrays (Column Number, Row Number)
  640. */
  641. public static function rangeBoundaries($pRange = 'A1:A1')
  642. {
  643. // Ensure $pRange is a valid range
  644. if (empty($pRange)) {
  645. $pRange = self::DEFAULT_RANGE;
  646. }
  647. // Uppercase coordinate
  648. $pRange = strtoupper($pRange);
  649. // Extract range
  650. if (strpos($pRange, ':') === false) {
  651. $rangeA = $rangeB = $pRange;
  652. } else {
  653. list($rangeA, $rangeB) = explode(':', $pRange);
  654. }
  655. // Calculate range outer borders
  656. $rangeStart = self::coordinateFromString($rangeA);
  657. $rangeEnd = self::coordinateFromString($rangeB);
  658. // Translate column into index
  659. $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
  660. $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
  661. return array($rangeStart, $rangeEnd);
  662. }
  663. /**
  664. * Calculate range dimension
  665. *
  666. * @param string $pRange Cell range (e.g. A1:A1)
  667. * @return array Range dimension (width, height)
  668. */
  669. public static function rangeDimension($pRange = 'A1:A1')
  670. {
  671. // Calculate range outer borders
  672. list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);
  673. return array( ($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1) );
  674. }
  675. /**
  676. * Calculate range boundaries
  677. *
  678. * @param string $pRange Cell range (e.g. A1:A1)
  679. * @return array Range coordinates array(Start Cell, End Cell)
  680. * where Start Cell and End Cell are arrays (Column ID, Row Number)
  681. */
  682. public static function getRangeBoundaries($pRange = 'A1:A1')
  683. {
  684. // Ensure $pRange is a valid range
  685. if (empty($pRange)) {
  686. $pRange = self::DEFAULT_RANGE;
  687. }
  688. // Uppercase coordinate
  689. $pRange = strtoupper($pRange);
  690. // Extract range
  691. if (strpos($pRange, ':') === false) {
  692. $rangeA = $rangeB = $pRange;
  693. } else {
  694. list($rangeA, $rangeB) = explode(':', $pRange);
  695. }
  696. return array( self::coordinateFromString($rangeA), self::coordinateFromString($rangeB));
  697. }
  698. /**
  699. * Column index from string
  700. *
  701. * @param string $pString
  702. * @return int Column index (base 1 !!!)
  703. */
  704. public static function columnIndexFromString($pString = 'A')
  705. {
  706. // Using a lookup cache adds a slight memory overhead, but boosts speed
  707. // caching using a static within the method is faster than a class static,
  708. // though it's additional memory overhead
  709. static $_indexCache = array();
  710. if (isset($_indexCache[$pString])) {
  711. return $_indexCache[$pString];
  712. }
  713. // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
  714. // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
  715. // memory overhead either
  716. static $_columnLookup = array(
  717. 'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
  718. 'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
  719. 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
  720. 'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26
  721. );
  722. // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
  723. // for improved performance
  724. if (isset($pString{0})) {
  725. if (!isset($pString{1})) {
  726. $_indexCache[$pString] = $_columnLookup[$pString];
  727. return $_indexCache[$pString];
  728. } elseif (!isset($pString{2})) {
  729. $_indexCache[$pString] = $_columnLookup[$pString{0}] * 26 + $_columnLookup[$pString{1}];
  730. return $_indexCache[$pString];
  731. } elseif (!isset($pString{3})) {
  732. $_indexCache[$pString] = $_columnLookup[$pString{0}] * 676 + $_columnLookup[$pString{1}] * 26 + $_columnLookup[$pString{2}];
  733. return $_indexCache[$pString];
  734. }
  735. }
  736. throw new PHPExcel_Exception("Column string index can not be " . ((isset($pString{0})) ? "longer than 3 characters" : "empty"));
  737. }
  738. /**
  739. * String from columnindex
  740. *
  741. * @param int $pColumnIndex Column index (base 0 !!!)
  742. * @return string
  743. */
  744. public static function stringFromColumnIndex($pColumnIndex = 0)
  745. {
  746. // Using a lookup cache adds a slight memory overhead, but boosts speed
  747. // caching using a static within the method is faster than a class static,
  748. // though it's additional memory overhead
  749. static $_indexCache = array();
  750. if (!isset($_indexCache[$pColumnIndex])) {
  751. // Determine column string
  752. if ($pColumnIndex < 26) {
  753. $_indexCache[$pColumnIndex] = chr(65 + $pColumnIndex);
  754. } elseif ($pColumnIndex < 702) {
  755. $_indexCache[$pColumnIndex] = chr(64 + ($pColumnIndex / 26)) .
  756. chr(65 + $pColumnIndex % 26);
  757. } else {
  758. $_indexCache[$pColumnIndex] = chr(64 + (($pColumnIndex - 26) / 676)) .
  759. chr(65 + ((($pColumnIndex - 26) % 676) / 26)) .
  760. chr(65 + $pColumnIndex % 26);
  761. }
  762. }
  763. return $_indexCache[$pColumnIndex];
  764. }
  765. /**
  766. * Extract all cell references in range
  767. *
  768. * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
  769. * @return array Array containing single cell references
  770. */
  771. public static function extractAllCellReferencesInRange($pRange = 'A1')
  772. {
  773. // Returnvalue
  774. $returnValue = array();
  775. // Explode spaces
  776. $cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
  777. foreach ($cellBlocks as $cellBlock) {
  778. // Single cell?
  779. if (strpos($cellBlock, ':') === false && strpos($cellBlock, ',') === false) {
  780. $returnValue[] = $cellBlock;
  781. continue;
  782. }
  783. // Range...
  784. $ranges = self::splitRange($cellBlock);
  785. foreach ($ranges as $range) {
  786. // Single cell?
  787. if (!isset($range[1])) {
  788. $returnValue[] = $range[0];
  789. continue;
  790. }
  791. // Range...
  792. list($rangeStart, $rangeEnd) = $range;
  793. sscanf($rangeStart, '%[A-Z]%d', $startCol, $startRow);
  794. sscanf($rangeEnd, '%[A-Z]%d', $endCol, $endRow);
  795. ++$endCol;
  796. // Current data
  797. $currentCol = $startCol;
  798. $currentRow = $startRow;
  799. // Loop cells
  800. while ($currentCol != $endCol) {
  801. while ($currentRow <= $endRow) {
  802. $returnValue[] = $currentCol.$currentRow;
  803. ++$currentRow;
  804. }
  805. ++$currentCol;
  806. $currentRow = $startRow;
  807. }
  808. }
  809. }
  810. // Sort the result by column and row
  811. $sortKeys = array();
  812. foreach (array_unique($returnValue) as $coord) {
  813. sscanf($coord, '%[A-Z]%d', $column, $row);
  814. $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
  815. }
  816. ksort($sortKeys);
  817. // Return value
  818. return array_values($sortKeys);
  819. }
  820. /**
  821. * Compare 2 cells
  822. *
  823. * @param PHPExcel_Cell $a Cell a
  824. * @param PHPExcel_Cell $b Cell b
  825. * @return int Result of comparison (always -1 or 1, never zero!)
  826. */
  827. public static function compareCells(PHPExcel_Cell $a, PHPExcel_Cell $b)
  828. {
  829. if ($a->getRow() < $b->getRow()) {
  830. return -1;
  831. } elseif ($a->getRow() > $b->getRow()) {
  832. return 1;
  833. } elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) {
  834. return -1;
  835. } else {
  836. return 1;
  837. }
  838. }
  839. /**
  840. * Get value binder to use
  841. *
  842. * @return PHPExcel_Cell_IValueBinder
  843. */
  844. public static function getValueBinder()
  845. {
  846. if (self::$valueBinder === null) {
  847. self::$valueBinder = new PHPExcel_Cell_DefaultValueBinder();
  848. }
  849. return self::$valueBinder;
  850. }
  851. /**
  852. * Set value binder to use
  853. *
  854. * @param PHPExcel_Cell_IValueBinder $binder
  855. * @throws PHPExcel_Exception
  856. */
  857. public static function setValueBinder(PHPExcel_Cell_IValueBinder $binder = null)
  858. {
  859. if ($binder === null) {
  860. throw new PHPExcel_Exception("A PHPExcel_Cell_IValueBinder is required for PHPExcel to function correctly.");
  861. }
  862. self::$valueBinder = $binder;
  863. }
  864. /**
  865. * Implement PHP __clone to create a deep clone, not just a shallow copy.
  866. */
  867. public function __clone()
  868. {
  869. $vars = get_object_vars($this);
  870. foreach ($vars as $key => $value) {
  871. if ((is_object($value)) && ($key != 'parent')) {
  872. $this->$key = clone $value;
  873. } else {
  874. $this->$key = $value;
  875. }
  876. }
  877. }
  878. /**
  879. * Get index to cellXf
  880. *
  881. * @return int
  882. */
  883. public function getXfIndex()
  884. {
  885. return $this->xfIndex;
  886. }
  887. /**
  888. * Set index to cellXf
  889. *
  890. * @param int $pValue
  891. * @return PHPExcel_Cell
  892. */
  893. public function setXfIndex($pValue = 0)
  894. {
  895. $this->xfIndex = $pValue;
  896. return $this->notifyCacheController();
  897. }
  898. /**
  899. * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
  900. */
  901. public function setFormulaAttributes($pAttributes)
  902. {
  903. $this->formulaAttributes = $pAttributes;
  904. return $this;
  905. }
  906. /**
  907. * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
  908. */
  909. public function getFormulaAttributes()
  910. {
  911. return $this->formulaAttributes;
  912. }
  913. /**
  914. * Convert to string
  915. *
  916. * @return string
  917. */
  918. public function __toString()
  919. {
  920. return (string) $this->getValue();
  921. }
  922. }