For God so loved the world, that He gave His only begotten Son, that all who believe in Him should not perish but have everlasting life
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1032 lines
33 KiB

  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. }