No Description

Cell.php 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023
  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2014 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 - 2014 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. /**
  28. * PHPExcel_Cell
  29. *
  30. * @category PHPExcel
  31. * @package PHPExcel_Cell
  32. * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
  33. */
  34. class PHPExcel_Cell
  35. {
  36. /**
  37. * Default range variable constant
  38. *
  39. * @var string
  40. */
  41. const DEFAULT_RANGE = 'A1:A1';
  42. /**
  43. * Value binder to use
  44. *
  45. * @var PHPExcel_Cell_IValueBinder
  46. */
  47. private static $_valueBinder = NULL;
  48. /**
  49. * Value of the cell
  50. *
  51. * @var mixed
  52. */
  53. private $_value;
  54. /**
  55. * Calculated value of the cell (used for caching)
  56. * This returns the value last calculated by MS Excel or whichever spreadsheet program was used to
  57. * create the original spreadsheet file.
  58. * Note that this value is not guaranteed to reflect the actual calculated value because it is
  59. * possible that auto-calculation was disabled in the original spreadsheet, and underlying data
  60. * values used by the formula have changed since it was last calculated.
  61. *
  62. * @var mixed
  63. */
  64. private $_calculatedValue = NULL;
  65. /**
  66. * Type of the cell data
  67. *
  68. * @var string
  69. */
  70. private $_dataType;
  71. /**
  72. * Parent worksheet
  73. *
  74. * @var PHPExcel_CachedObjectStorage_CacheBase
  75. */
  76. private $_parent;
  77. /**
  78. * Index to cellXf
  79. *
  80. * @var int
  81. */
  82. private $_xfIndex = 0;
  83. /**
  84. * Attributes of the formula
  85. *
  86. */
  87. private $_formulaAttributes;
  88. /**
  89. * Send notification to the cache controller
  90. *
  91. * @return void
  92. **/
  93. public function notifyCacheController() {
  94. $this->_parent->updateCacheData($this);
  95. return $this;
  96. }
  97. public function detach() {
  98. $this->_parent = NULL;
  99. }
  100. public function attach(PHPExcel_CachedObjectStorage_CacheBase $parent) {
  101. $this->_parent = $parent;
  102. }
  103. /**
  104. * Create a new Cell
  105. *
  106. * @param mixed $pValue
  107. * @param string $pDataType
  108. * @param PHPExcel_Worksheet $pSheet
  109. * @throws PHPExcel_Exception
  110. */
  111. public function __construct($pValue = NULL, $pDataType = NULL, PHPExcel_Worksheet $pSheet = NULL)
  112. {
  113. // Initialise cell value
  114. $this->_value = $pValue;
  115. // Set worksheet cache
  116. $this->_parent = $pSheet->getCellCacheController();
  117. // Set datatype?
  118. if ($pDataType !== NULL) {
  119. if ($pDataType == PHPExcel_Cell_DataType::TYPE_STRING2)
  120. $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
  121. $this->_dataType = $pDataType;
  122. } elseif (!self::getValueBinder()->bindValue($this, $pValue)) {
  123. throw new PHPExcel_Exception("Value could not be bound to cell.");
  124. }
  125. }
  126. /**
  127. * Get cell coordinate column
  128. *
  129. * @return string
  130. */
  131. public function getColumn()
  132. {
  133. return $this->_parent->getCurrentColumn();
  134. }
  135. /**
  136. * Get cell coordinate row
  137. *
  138. * @return int
  139. */
  140. public function getRow()
  141. {
  142. return $this->_parent->getCurrentRow();
  143. }
  144. /**
  145. * Get cell coordinate
  146. *
  147. * @return string
  148. */
  149. public function getCoordinate()
  150. {
  151. return $this->_parent->getCurrentAddress();
  152. }
  153. /**
  154. * Get cell value
  155. *
  156. * @return mixed
  157. */
  158. public function getValue()
  159. {
  160. return $this->_value;
  161. }
  162. /**
  163. * Get cell value with formatting
  164. *
  165. * @return string
  166. */
  167. public function getFormattedValue()
  168. {
  169. return (string) PHPExcel_Style_NumberFormat::toFormattedString(
  170. $this->getCalculatedValue(),
  171. $this->getStyle()
  172. ->getNumberFormat()->getFormatCode()
  173. );
  174. }
  175. /**
  176. * Set cell value
  177. *
  178. * Sets the value for a cell, automatically determining the datatype using the value binder
  179. *
  180. * @param mixed $pValue Value
  181. * @return PHPExcel_Cell
  182. * @throws PHPExcel_Exception
  183. */
  184. public function setValue($pValue = NULL)
  185. {
  186. if (!self::getValueBinder()->bindValue($this, $pValue)) {
  187. throw new PHPExcel_Exception("Value could not be bound to cell.");
  188. }
  189. return $this;
  190. }
  191. /**
  192. * Set the value for a cell, with the explicit data type passed to the method (bypassing any use of the value binder)
  193. *
  194. * @param mixed $pValue Value
  195. * @param string $pDataType Explicit data type
  196. * @return PHPExcel_Cell
  197. * @throws PHPExcel_Exception
  198. */
  199. public function setValueExplicit($pValue = NULL, $pDataType = PHPExcel_Cell_DataType::TYPE_STRING)
  200. {
  201. // set the value according to data type
  202. switch ($pDataType) {
  203. case PHPExcel_Cell_DataType::TYPE_NULL:
  204. $this->_value = $pValue;
  205. break;
  206. case PHPExcel_Cell_DataType::TYPE_STRING2:
  207. $pDataType = PHPExcel_Cell_DataType::TYPE_STRING;
  208. case PHPExcel_Cell_DataType::TYPE_STRING:
  209. case PHPExcel_Cell_DataType::TYPE_INLINE:
  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. $this->_dataType = $pDataType;
  328. return $this->notifyCacheController();
  329. }
  330. /**
  331. * Identify if the cell contains a formula
  332. *
  333. * @return boolean
  334. */
  335. public function isFormula()
  336. {
  337. return $this->_dataType == PHPExcel_Cell_DataType::TYPE_FORMULA;
  338. }
  339. /**
  340. * Does this cell contain Data validation rules?
  341. *
  342. * @return boolean
  343. * @throws PHPExcel_Exception
  344. */
  345. public function hasDataValidation()
  346. {
  347. if (!isset($this->_parent)) {
  348. throw new PHPExcel_Exception('Cannot check for data validation when cell is not bound to a worksheet');
  349. }
  350. return $this->getWorksheet()->dataValidationExists($this->getCoordinate());
  351. }
  352. /**
  353. * Get Data validation rules
  354. *
  355. * @return PHPExcel_Cell_DataValidation
  356. * @throws PHPExcel_Exception
  357. */
  358. public function getDataValidation()
  359. {
  360. if (!isset($this->_parent)) {
  361. throw new PHPExcel_Exception('Cannot get data validation for cell that is not bound to a worksheet');
  362. }
  363. return $this->getWorksheet()->getDataValidation($this->getCoordinate());
  364. }
  365. /**
  366. * Set Data validation rules
  367. *
  368. * @param PHPExcel_Cell_DataValidation $pDataValidation
  369. * @return PHPExcel_Cell
  370. * @throws PHPExcel_Exception
  371. */
  372. public function setDataValidation(PHPExcel_Cell_DataValidation $pDataValidation = NULL)
  373. {
  374. if (!isset($this->_parent)) {
  375. throw new PHPExcel_Exception('Cannot set data validation for cell that is not bound to a worksheet');
  376. }
  377. $this->getWorksheet()->setDataValidation($this->getCoordinate(), $pDataValidation);
  378. return $this->notifyCacheController();
  379. }
  380. /**
  381. * Does this cell contain a Hyperlink?
  382. *
  383. * @return boolean
  384. * @throws PHPExcel_Exception
  385. */
  386. public function hasHyperlink()
  387. {
  388. if (!isset($this->_parent)) {
  389. throw new PHPExcel_Exception('Cannot check for hyperlink when cell is not bound to a worksheet');
  390. }
  391. return $this->getWorksheet()->hyperlinkExists($this->getCoordinate());
  392. }
  393. /**
  394. * Get Hyperlink
  395. *
  396. * @return PHPExcel_Cell_Hyperlink
  397. * @throws PHPExcel_Exception
  398. */
  399. public function getHyperlink()
  400. {
  401. if (!isset($this->_parent)) {
  402. throw new PHPExcel_Exception('Cannot get hyperlink for cell that is not bound to a worksheet');
  403. }
  404. return $this->getWorksheet()->getHyperlink($this->getCoordinate());
  405. }
  406. /**
  407. * Set Hyperlink
  408. *
  409. * @param PHPExcel_Cell_Hyperlink $pHyperlink
  410. * @return PHPExcel_Cell
  411. * @throws PHPExcel_Exception
  412. */
  413. public function setHyperlink(PHPExcel_Cell_Hyperlink $pHyperlink = NULL)
  414. {
  415. if (!isset($this->_parent)) {
  416. throw new PHPExcel_Exception('Cannot set hyperlink for cell that is not bound to a worksheet');
  417. }
  418. $this->getWorksheet()->setHyperlink($this->getCoordinate(), $pHyperlink);
  419. return $this->notifyCacheController();
  420. }
  421. /**
  422. * Get parent worksheet
  423. *
  424. * @return PHPExcel_CachedObjectStorage_CacheBase
  425. */
  426. public function getParent() {
  427. return $this->_parent;
  428. }
  429. /**
  430. * Get parent worksheet
  431. *
  432. * @return PHPExcel_Worksheet
  433. */
  434. public function getWorksheet() {
  435. return $this->_parent->getParent();
  436. }
  437. /**
  438. * Is this cell in a merge range
  439. *
  440. * @return boolean
  441. */
  442. public function isInMergeRange() {
  443. return (boolean) $this->getMergeRange();
  444. }
  445. /**
  446. * Is this cell the master (top left cell) in a merge range (that holds the actual data value)
  447. *
  448. * @return boolean
  449. */
  450. public function isMergeRangeValueCell() {
  451. if ($mergeRange = $this->getMergeRange()) {
  452. $mergeRange = PHPExcel_Cell::splitRange($mergeRange);
  453. list($startCell) = $mergeRange[0];
  454. if ($this->getCoordinate() === $startCell) {
  455. return true;
  456. }
  457. }
  458. return false;
  459. }
  460. /**
  461. * If this cell is in a merge range, then return the range
  462. *
  463. * @return string
  464. */
  465. public function getMergeRange() {
  466. foreach($this->getWorksheet()->getMergeCells() as $mergeRange) {
  467. if ($this->isInRange($mergeRange)) {
  468. return $mergeRange;
  469. }
  470. }
  471. return false;
  472. }
  473. /**
  474. * Get cell style
  475. *
  476. * @return PHPExcel_Style
  477. */
  478. public function getStyle()
  479. {
  480. return $this->getWorksheet()->getStyle($this->getCoordinate());
  481. }
  482. /**
  483. * Re-bind parent
  484. *
  485. * @param PHPExcel_Worksheet $parent
  486. * @return PHPExcel_Cell
  487. */
  488. public function rebindParent(PHPExcel_Worksheet $parent) {
  489. $this->_parent = $parent->getCellCacheController();
  490. return $this->notifyCacheController();
  491. }
  492. /**
  493. * Is cell in a specific range?
  494. *
  495. * @param string $pRange Cell range (e.g. A1:A1)
  496. * @return boolean
  497. */
  498. public function isInRange($pRange = 'A1:A1')
  499. {
  500. list($rangeStart,$rangeEnd) = self::rangeBoundaries($pRange);
  501. // Translate properties
  502. $myColumn = self::columnIndexFromString($this->getColumn());
  503. $myRow = $this->getRow();
  504. // Verify if cell is in range
  505. return (($rangeStart[0] <= $myColumn) && ($rangeEnd[0] >= $myColumn) &&
  506. ($rangeStart[1] <= $myRow) && ($rangeEnd[1] >= $myRow)
  507. );
  508. }
  509. /**
  510. * Coordinate from string
  511. *
  512. * @param string $pCoordinateString
  513. * @return array Array containing column and row (indexes 0 and 1)
  514. * @throws PHPExcel_Exception
  515. */
  516. public static function coordinateFromString($pCoordinateString = 'A1')
  517. {
  518. if (preg_match("/^([$]?[A-Z]{1,3})([$]?\d{1,7})$/", $pCoordinateString, $matches)) {
  519. return array($matches[1],$matches[2]);
  520. } elseif ((strpos($pCoordinateString,':') !== FALSE) || (strpos($pCoordinateString,',') !== FALSE)) {
  521. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  522. } elseif ($pCoordinateString == '') {
  523. throw new PHPExcel_Exception('Cell coordinate can not be zero-length string');
  524. }
  525. throw new PHPExcel_Exception('Invalid cell coordinate '.$pCoordinateString);
  526. }
  527. /**
  528. * Make string row, column or cell coordinate absolute
  529. *
  530. * @param string $pCoordinateString e.g. 'A' or '1' or 'A1'
  531. * Note that this value can be a row or column reference as well as a cell reference
  532. * @return string Absolute coordinate e.g. '$A' or '$1' or '$A$1'
  533. * @throws PHPExcel_Exception
  534. */
  535. public static function absoluteReference($pCoordinateString = 'A1')
  536. {
  537. if (strpos($pCoordinateString,':') === FALSE && strpos($pCoordinateString,',') === FALSE) {
  538. // Split out any worksheet name from the reference
  539. $worksheet = '';
  540. $cellAddress = explode('!',$pCoordinateString);
  541. if (count($cellAddress) > 1) {
  542. list($worksheet,$pCoordinateString) = $cellAddress;
  543. }
  544. if ($worksheet > '') $worksheet .= '!';
  545. // Create absolute coordinate
  546. if (ctype_digit($pCoordinateString)) {
  547. return $worksheet . '$' . $pCoordinateString;
  548. } elseif (ctype_alpha($pCoordinateString)) {
  549. return $worksheet . '$' . strtoupper($pCoordinateString);
  550. }
  551. return $worksheet . self::absoluteCoordinate($pCoordinateString);
  552. }
  553. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  554. }
  555. /**
  556. * Make string coordinate absolute
  557. *
  558. * @param string $pCoordinateString e.g. 'A1'
  559. * @return string Absolute coordinate e.g. '$A$1'
  560. * @throws PHPExcel_Exception
  561. */
  562. public static function absoluteCoordinate($pCoordinateString = 'A1')
  563. {
  564. if (strpos($pCoordinateString,':') === FALSE && strpos($pCoordinateString,',') === FALSE) {
  565. // Split out any worksheet name from the coordinate
  566. $worksheet = '';
  567. $cellAddress = explode('!',$pCoordinateString);
  568. if (count($cellAddress) > 1) {
  569. list($worksheet,$pCoordinateString) = $cellAddress;
  570. }
  571. if ($worksheet > '') $worksheet .= '!';
  572. // Create absolute coordinate
  573. list($column, $row) = self::coordinateFromString($pCoordinateString);
  574. $column = ltrim($column,'$');
  575. $row = ltrim($row,'$');
  576. return $worksheet . '$' . $column . '$' . $row;
  577. }
  578. throw new PHPExcel_Exception('Cell coordinate string can not be a range of cells');
  579. }
  580. /**
  581. * Split range into coordinate strings
  582. *
  583. * @param string $pRange e.g. 'B4:D9' or 'B4:D9,H2:O11' or 'B4'
  584. * @return array Array containg one or more arrays containing one or two coordinate strings
  585. * e.g. array('B4','D9') or array(array('B4','D9'),array('H2','O11'))
  586. * or array('B4')
  587. */
  588. public static function splitRange($pRange = 'A1:A1')
  589. {
  590. // Ensure $pRange is a valid range
  591. if(empty($pRange)) {
  592. $pRange = self::DEFAULT_RANGE;
  593. }
  594. $exploded = explode(',', $pRange);
  595. $counter = count($exploded);
  596. for ($i = 0; $i < $counter; ++$i) {
  597. $exploded[$i] = explode(':', $exploded[$i]);
  598. }
  599. return $exploded;
  600. }
  601. /**
  602. * Build range from coordinate strings
  603. *
  604. * @param array $pRange Array containg one or more arrays containing one or two coordinate strings
  605. * @return string String representation of $pRange
  606. * @throws PHPExcel_Exception
  607. */
  608. public static function buildRange($pRange)
  609. {
  610. // Verify range
  611. if (!is_array($pRange) || empty($pRange) || !is_array($pRange[0])) {
  612. throw new PHPExcel_Exception('Range does not contain any information');
  613. }
  614. // Build range
  615. $imploded = array();
  616. $counter = count($pRange);
  617. for ($i = 0; $i < $counter; ++$i) {
  618. $pRange[$i] = implode(':', $pRange[$i]);
  619. }
  620. $imploded = implode(',', $pRange);
  621. return $imploded;
  622. }
  623. /**
  624. * Calculate range boundaries
  625. *
  626. * @param string $pRange Cell range (e.g. A1:A1)
  627. * @return array Range coordinates array(Start Cell, End Cell)
  628. * where Start Cell and End Cell are arrays (Column Number, Row Number)
  629. */
  630. public static function rangeBoundaries($pRange = 'A1:A1')
  631. {
  632. // Ensure $pRange is a valid range
  633. if(empty($pRange)) {
  634. $pRange = self::DEFAULT_RANGE;
  635. }
  636. // Uppercase coordinate
  637. $pRange = strtoupper($pRange);
  638. // Extract range
  639. if (strpos($pRange, ':') === FALSE) {
  640. $rangeA = $rangeB = $pRange;
  641. } else {
  642. list($rangeA, $rangeB) = explode(':', $pRange);
  643. }
  644. // Calculate range outer borders
  645. $rangeStart = self::coordinateFromString($rangeA);
  646. $rangeEnd = self::coordinateFromString($rangeB);
  647. // Translate column into index
  648. $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
  649. $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);
  650. return array($rangeStart, $rangeEnd);
  651. }
  652. /**
  653. * Calculate range dimension
  654. *
  655. * @param string $pRange Cell range (e.g. A1:A1)
  656. * @return array Range dimension (width, height)
  657. */
  658. public static function rangeDimension($pRange = 'A1:A1')
  659. {
  660. // Calculate range outer borders
  661. list($rangeStart,$rangeEnd) = self::rangeBoundaries($pRange);
  662. return array( ($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1) );
  663. }
  664. /**
  665. * Calculate range boundaries
  666. *
  667. * @param string $pRange Cell range (e.g. A1:A1)
  668. * @return array Range coordinates array(Start Cell, End Cell)
  669. * where Start Cell and End Cell are arrays (Column ID, Row Number)
  670. */
  671. public static function getRangeBoundaries($pRange = 'A1:A1')
  672. {
  673. // Ensure $pRange is a valid range
  674. if(empty($pRange)) {
  675. $pRange = self::DEFAULT_RANGE;
  676. }
  677. // Uppercase coordinate
  678. $pRange = strtoupper($pRange);
  679. // Extract range
  680. if (strpos($pRange, ':') === FALSE) {
  681. $rangeA = $rangeB = $pRange;
  682. } else {
  683. list($rangeA, $rangeB) = explode(':', $pRange);
  684. }
  685. return array( self::coordinateFromString($rangeA), self::coordinateFromString($rangeB));
  686. }
  687. /**
  688. * Column index from string
  689. *
  690. * @param string $pString
  691. * @return int Column index (base 1 !!!)
  692. */
  693. public static function columnIndexFromString($pString = 'A')
  694. {
  695. // Using a lookup cache adds a slight memory overhead, but boosts speed
  696. // caching using a static within the method is faster than a class static,
  697. // though it's additional memory overhead
  698. static $_indexCache = array();
  699. if (isset($_indexCache[$pString]))
  700. return $_indexCache[$pString];
  701. // It's surprising how costly the strtoupper() and ord() calls actually are, so we use a lookup array rather than use ord()
  702. // and make it case insensitive to get rid of the strtoupper() as well. Because it's a static, there's no significant
  703. // memory overhead either
  704. static $_columnLookup = array(
  705. '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,
  706. '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,
  707. '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,
  708. '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
  709. );
  710. // We also use the language construct isset() rather than the more costly strlen() function to match the length of $pString
  711. // for improved performance
  712. if (isset($pString{0})) {
  713. if (!isset($pString{1})) {
  714. $_indexCache[$pString] = $_columnLookup[$pString];
  715. return $_indexCache[$pString];
  716. } elseif(!isset($pString{2})) {
  717. $_indexCache[$pString] = $_columnLookup[$pString{0}] * 26 + $_columnLookup[$pString{1}];
  718. return $_indexCache[$pString];
  719. } elseif(!isset($pString{3})) {
  720. $_indexCache[$pString] = $_columnLookup[$pString{0}] * 676 + $_columnLookup[$pString{1}] * 26 + $_columnLookup[$pString{2}];
  721. return $_indexCache[$pString];
  722. }
  723. }
  724. throw new PHPExcel_Exception("Column string index can not be " . ((isset($pString{0})) ? "longer than 3 characters" : "empty"));
  725. }
  726. /**
  727. * String from columnindex
  728. *
  729. * @param int $pColumnIndex Column index (base 0 !!!)
  730. * @return string
  731. */
  732. public static function stringFromColumnIndex($pColumnIndex = 0)
  733. {
  734. // Using a lookup cache adds a slight memory overhead, but boosts speed
  735. // caching using a static within the method is faster than a class static,
  736. // though it's additional memory overhead
  737. static $_indexCache = array();
  738. if (!isset($_indexCache[$pColumnIndex])) {
  739. // Determine column string
  740. if ($pColumnIndex < 26) {
  741. $_indexCache[$pColumnIndex] = chr(65 + $pColumnIndex);
  742. } elseif ($pColumnIndex < 702) {
  743. $_indexCache[$pColumnIndex] = chr(64 + ($pColumnIndex / 26)) .
  744. chr(65 + $pColumnIndex % 26);
  745. } else {
  746. $_indexCache[$pColumnIndex] = chr(64 + (($pColumnIndex - 26) / 676)) .
  747. chr(65 + ((($pColumnIndex - 26) % 676) / 26)) .
  748. chr(65 + $pColumnIndex % 26);
  749. }
  750. }
  751. return $_indexCache[$pColumnIndex];
  752. }
  753. /**
  754. * Extract all cell references in range
  755. *
  756. * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
  757. * @return array Array containing single cell references
  758. */
  759. public static function extractAllCellReferencesInRange($pRange = 'A1') {
  760. // Returnvalue
  761. $returnValue = array();
  762. // Explode spaces
  763. $cellBlocks = explode(' ', str_replace('$', '', strtoupper($pRange)));
  764. foreach ($cellBlocks as $cellBlock) {
  765. // Single cell?
  766. if (strpos($cellBlock,':') === FALSE && strpos($cellBlock,',') === FALSE) {
  767. $returnValue[] = $cellBlock;
  768. continue;
  769. }
  770. // Range...
  771. $ranges = self::splitRange($cellBlock);
  772. foreach($ranges as $range) {
  773. // Single cell?
  774. if (!isset($range[1])) {
  775. $returnValue[] = $range[0];
  776. continue;
  777. }
  778. // Range...
  779. list($rangeStart, $rangeEnd) = $range;
  780. sscanf($rangeStart,'%[A-Z]%d', $startCol, $startRow);
  781. sscanf($rangeEnd,'%[A-Z]%d', $endCol, $endRow);
  782. $endCol++;
  783. // Current data
  784. $currentCol = $startCol;
  785. $currentRow = $startRow;
  786. // Loop cells
  787. while ($currentCol != $endCol) {
  788. while ($currentRow <= $endRow) {
  789. $returnValue[] = $currentCol.$currentRow;
  790. ++$currentRow;
  791. }
  792. ++$currentCol;
  793. $currentRow = $startRow;
  794. }
  795. }
  796. }
  797. // Sort the result by column and row
  798. $sortKeys = array();
  799. foreach (array_unique($returnValue) as $coord) {
  800. sscanf($coord,'%[A-Z]%d', $column, $row);
  801. $sortKeys[sprintf('%3s%09d',$column,$row)] = $coord;
  802. }
  803. ksort($sortKeys);
  804. // Return value
  805. return array_values($sortKeys);
  806. }
  807. /**
  808. * Compare 2 cells
  809. *
  810. * @param PHPExcel_Cell $a Cell a
  811. * @param PHPExcel_Cell $b Cell b
  812. * @return int Result of comparison (always -1 or 1, never zero!)
  813. */
  814. public static function compareCells(PHPExcel_Cell $a, PHPExcel_Cell $b)
  815. {
  816. if ($a->getRow() < $b->getRow()) {
  817. return -1;
  818. } elseif ($a->getRow() > $b->getRow()) {
  819. return 1;
  820. } elseif (self::columnIndexFromString($a->getColumn()) < self::columnIndexFromString($b->getColumn())) {
  821. return -1;
  822. } else {
  823. return 1;
  824. }
  825. }
  826. /**
  827. * Get value binder to use
  828. *
  829. * @return PHPExcel_Cell_IValueBinder
  830. */
  831. public static function getValueBinder() {
  832. if (self::$_valueBinder === NULL) {
  833. self::$_valueBinder = new PHPExcel_Cell_DefaultValueBinder();
  834. }
  835. return self::$_valueBinder;
  836. }
  837. /**
  838. * Set value binder to use
  839. *
  840. * @param PHPExcel_Cell_IValueBinder $binder
  841. * @throws PHPExcel_Exception
  842. */
  843. public static function setValueBinder(PHPExcel_Cell_IValueBinder $binder = NULL) {
  844. if ($binder === NULL) {
  845. throw new PHPExcel_Exception("A PHPExcel_Cell_IValueBinder is required for PHPExcel to function correctly.");
  846. }
  847. self::$_valueBinder = $binder;
  848. }
  849. /**
  850. * Implement PHP __clone to create a deep clone, not just a shallow copy.
  851. */
  852. public function __clone() {
  853. $vars = get_object_vars($this);
  854. foreach ($vars as $key => $value) {
  855. if ((is_object($value)) && ($key != '_parent')) {
  856. $this->$key = clone $value;
  857. } else {
  858. $this->$key = $value;
  859. }
  860. }
  861. }
  862. /**
  863. * Get index to cellXf
  864. *
  865. * @return int
  866. */
  867. public function getXfIndex()
  868. {
  869. return $this->_xfIndex;
  870. }
  871. /**
  872. * Set index to cellXf
  873. *
  874. * @param int $pValue
  875. * @return PHPExcel_Cell
  876. */
  877. public function setXfIndex($pValue = 0)
  878. {
  879. $this->_xfIndex = $pValue;
  880. return $this->notifyCacheController();
  881. }
  882. /**
  883. * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
  884. */
  885. public function setFormulaAttributes($pAttributes)
  886. {
  887. $this->_formulaAttributes = $pAttributes;
  888. return $this;
  889. }
  890. /**
  891. * @deprecated Since version 1.7.8 for planned changes to cell for array formula handling
  892. */
  893. public function getFormulaAttributes()
  894. {
  895. return $this->_formulaAttributes;
  896. }
  897. /**
  898. * Convert to string
  899. *
  900. * @return string
  901. */
  902. public function __toString()
  903. {
  904. return (string) $this->getValue();
  905. }
  906. }