PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/Classes/PHPExcel/ReferenceHelper.php

https://github.com/iGroup/PHPExcel
PHP | 695 lines | 524 code | 62 blank | 109 comment | 86 complexity | b817bf91ffadf7e487f15e6c406d779e MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * PHPExcel
  4. *
  5. * Copyright (c) 2006 - 2012 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
  23. * @copyright Copyright (c) 2006 - 2012 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_ReferenceHelper (Singleton)
  29. *
  30. * @category PHPExcel
  31. * @package PHPExcel
  32. * @copyright Copyright (c) 2006 - 2012 PHPExcel (http://www.codeplex.com/PHPExcel)
  33. */
  34. class PHPExcel_ReferenceHelper
  35. {
  36. /** Constants */
  37. /** Regular Expressions */
  38. const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
  39. const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
  40. const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
  41. const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
  42. /**
  43. * Instance of this class
  44. *
  45. * @var PHPExcel_ReferenceHelper
  46. */
  47. private static $_instance;
  48. /**
  49. * Get an instance of this class
  50. *
  51. * @return PHPExcel_ReferenceHelper
  52. */
  53. public static function getInstance() {
  54. if (!isset(self::$_instance) || (self::$_instance === NULL)) {
  55. self::$_instance = new PHPExcel_ReferenceHelper();
  56. }
  57. return self::$_instance;
  58. }
  59. /**
  60. * Create a new PHPExcel_ReferenceHelper
  61. */
  62. protected function __construct() {
  63. }
  64. /**
  65. * Insert a new column, updating all possible related data
  66. *
  67. * @param int $pBefore Insert before this one
  68. * @param int $pNumCols Number of columns to insert
  69. * @param int $pNumRows Number of rows to insert
  70. * @throws Exception
  71. */
  72. public function insertNewBefore($pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, PHPExcel_Worksheet $pSheet = null) {
  73. $remove = ($pNumCols < 0 || $pNumRows < 0);
  74. $aCellCollection = $pSheet->getCellCollection();
  75. // Get coordinates of $pBefore
  76. $beforeColumn = 'A';
  77. $beforeRow = 1;
  78. list($beforeColumn, $beforeRow) = PHPExcel_Cell::coordinateFromString( $pBefore );
  79. $beforeColumnIndex = PHPExcel_Cell::columnIndexFromString($beforeColumn);
  80. // Clear cells if we are removing columns or rows
  81. $highestColumn = $pSheet->getHighestColumn();
  82. $highestRow = $pSheet->getHighestRow();
  83. // 1. Clear column strips if we are removing columns
  84. if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) {
  85. for ($i = 1; $i <= $highestRow - 1; ++$i) {
  86. for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) {
  87. $coordinate = PHPExcel_Cell::stringFromColumnIndex($j) . $i;
  88. $pSheet->removeConditionalStyles($coordinate);
  89. if ($pSheet->cellExists($coordinate)) {
  90. $pSheet->getCell($coordinate)->setValueExplicit('', PHPExcel_Cell_DataType::TYPE_NULL);
  91. $pSheet->getCell($coordinate)->setXfIndex(0);
  92. }
  93. }
  94. }
  95. }
  96. // 2. Clear row strips if we are removing rows
  97. if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) {
  98. for ($i = $beforeColumnIndex - 1; $i <= PHPExcel_Cell::columnIndexFromString($highestColumn) - 1; ++$i) {
  99. for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) {
  100. $coordinate = PHPExcel_Cell::stringFromColumnIndex($i) . $j;
  101. $pSheet->removeConditionalStyles($coordinate);
  102. if ($pSheet->cellExists($coordinate)) {
  103. $pSheet->getCell($coordinate)->setValueExplicit('', PHPExcel_Cell_DataType::TYPE_NULL);
  104. $pSheet->getCell($coordinate)->setXfIndex(0);
  105. }
  106. }
  107. }
  108. }
  109. // Loop through cells, bottom-up, and change cell coordinates
  110. while (($cellID = $remove ? array_shift($aCellCollection) : array_pop($aCellCollection))) {
  111. $cell = $pSheet->getCell($cellID);
  112. $cellIndex = PHPExcel_Cell::columnIndexFromString($cell->getColumn());
  113. if ($cellIndex-1 + $pNumCols < 0) {
  114. continue;
  115. }
  116. // New coordinates
  117. $newCoordinates = PHPExcel_Cell::stringFromColumnIndex($cellIndex-1 + $pNumCols) . ($cell->getRow() + $pNumRows);
  118. // Should the cell be updated? Move value and cellXf index from one cell to another.
  119. if (($cellIndex >= $beforeColumnIndex) &&
  120. ($cell->getRow() >= $beforeRow)) {
  121. // Update cell styles
  122. $pSheet->getCell($newCoordinates)->setXfIndex($cell->getXfIndex());
  123. // Insert this cell at its new location
  124. if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
  125. // Formula should be adjusted
  126. $pSheet->getCell($newCoordinates)
  127. ->setValue($this->updateFormulaReferences($cell->getValue(),
  128. $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
  129. } else {
  130. // Formula should not be adjusted
  131. $pSheet->getCell($newCoordinates)->setValue($cell->getValue());
  132. }
  133. // Clear the original cell
  134. $pSheet->getCellCacheController()->deleteCacheData($cellID);
  135. } else {
  136. /* We don't need to update styles for rows/columns before our insertion position,
  137. but we do still need to adjust any formulae in those cells */
  138. if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
  139. // Formula should be adjusted
  140. $cell->setValue($this->updateFormulaReferences($cell->getValue(),
  141. $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
  142. }
  143. }
  144. }
  145. // Duplicate styles for the newly inserted cells
  146. $highestColumn = $pSheet->getHighestColumn();
  147. $highestRow = $pSheet->getHighestRow();
  148. if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) {
  149. for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
  150. // Style
  151. $coordinate = PHPExcel_Cell::stringFromColumnIndex( $beforeColumnIndex - 2 ) . $i;
  152. if ($pSheet->cellExists($coordinate)) {
  153. $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
  154. $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
  155. $pSheet->getConditionalStyles($coordinate) : false;
  156. for ($j = $beforeColumnIndex - 1; $j <= $beforeColumnIndex - 2 + $pNumCols; ++$j) {
  157. $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
  158. if ($conditionalStyles) {
  159. $cloned = array();
  160. foreach ($conditionalStyles as $conditionalStyle) {
  161. $cloned[] = clone $conditionalStyle;
  162. }
  163. $pSheet->setConditionalStyles(PHPExcel_Cell::stringFromColumnIndex($j) . $i, $cloned);
  164. }
  165. }
  166. }
  167. }
  168. }
  169. if ($pNumRows > 0 && $beforeRow - 1 > 0) {
  170. for ($i = $beforeColumnIndex - 1; $i <= PHPExcel_Cell::columnIndexFromString($highestColumn) - 1; ++$i) {
  171. // Style
  172. $coordinate = PHPExcel_Cell::stringFromColumnIndex($i) . ($beforeRow - 1);
  173. if ($pSheet->cellExists($coordinate)) {
  174. $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
  175. $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
  176. $pSheet->getConditionalStyles($coordinate) : false;
  177. for ($j = $beforeRow; $j <= $beforeRow - 1 + $pNumRows; ++$j) {
  178. $pSheet->getCell(PHPExcel_Cell::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
  179. if ($conditionalStyles) {
  180. $cloned = array();
  181. foreach ($conditionalStyles as $conditionalStyle) {
  182. $cloned[] = clone $conditionalStyle;
  183. }
  184. $pSheet->setConditionalStyles(PHPExcel_Cell::stringFromColumnIndex($i) . $j, $cloned);
  185. }
  186. }
  187. }
  188. }
  189. }
  190. // Update worksheet: column dimensions
  191. $aColumnDimensions = array_reverse($pSheet->getColumnDimensions(), true);
  192. if (!empty($aColumnDimensions)) {
  193. foreach ($aColumnDimensions as $objColumnDimension) {
  194. $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows);
  195. list($newReference) = PHPExcel_Cell::coordinateFromString($newReference);
  196. if ($objColumnDimension->getColumnIndex() != $newReference) {
  197. $objColumnDimension->setColumnIndex($newReference);
  198. }
  199. }
  200. $pSheet->refreshColumnDimensions();
  201. }
  202. // Update worksheet: row dimensions
  203. $aRowDimensions = array_reverse($pSheet->getRowDimensions(), true);
  204. if (!empty($aRowDimensions)) {
  205. foreach ($aRowDimensions as $objRowDimension) {
  206. $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows);
  207. list(, $newReference) = PHPExcel_Cell::coordinateFromString($newReference);
  208. if ($objRowDimension->getRowIndex() != $newReference) {
  209. $objRowDimension->setRowIndex($newReference);
  210. }
  211. }
  212. $pSheet->refreshRowDimensions();
  213. $copyDimension = $pSheet->getRowDimension($beforeRow - 1);
  214. for ($i = $beforeRow; $i <= $beforeRow - 1 + $pNumRows; ++$i) {
  215. $newDimension = $pSheet->getRowDimension($i);
  216. $newDimension->setRowHeight($copyDimension->getRowHeight());
  217. $newDimension->setVisible($copyDimension->getVisible());
  218. $newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
  219. $newDimension->setCollapsed($copyDimension->getCollapsed());
  220. }
  221. }
  222. // Update worksheet: breaks
  223. $aBreaks = array_reverse($pSheet->getBreaks(), true);
  224. foreach ($aBreaks as $key => $value) {
  225. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  226. if ($key != $newReference) {
  227. $pSheet->setBreak( $newReference, $value );
  228. $pSheet->setBreak( $key, PHPExcel_Worksheet::BREAK_NONE );
  229. }
  230. }
  231. // Update worksheet: comments
  232. $aComments = $pSheet->getComments();
  233. $aNewComments = array(); // the new array of all comments
  234. foreach ($aComments as $key => &$value) {
  235. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  236. $aNewComments[$newReference] = $value;
  237. }
  238. $pSheet->setComments($aNewComments); // replace the comments array
  239. // Update worksheet: hyperlinks
  240. $aHyperlinkCollection = array_reverse($pSheet->getHyperlinkCollection(), true);
  241. foreach ($aHyperlinkCollection as $key => $value) {
  242. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  243. if ($key != $newReference) {
  244. $pSheet->setHyperlink( $newReference, $value );
  245. $pSheet->setHyperlink( $key, null );
  246. }
  247. }
  248. // Update worksheet: data validations
  249. $aDataValidationCollection = array_reverse($pSheet->getDataValidationCollection(), true);
  250. foreach ($aDataValidationCollection as $key => $value) {
  251. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  252. if ($key != $newReference) {
  253. $pSheet->setDataValidation( $newReference, $value );
  254. $pSheet->setDataValidation( $key, null );
  255. }
  256. }
  257. // Update worksheet: merge cells
  258. $aMergeCells = $pSheet->getMergeCells();
  259. $aNewMergeCells = array(); // the new array of all merge cells
  260. foreach ($aMergeCells as $key => &$value) {
  261. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  262. $aNewMergeCells[$newReference] = $newReference;
  263. }
  264. $pSheet->setMergeCells($aNewMergeCells); // replace the merge cells array
  265. // Update worksheet: protected cells
  266. $aProtectedCells = array_reverse($pSheet->getProtectedCells(), true);
  267. foreach ($aProtectedCells as $key => $value) {
  268. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  269. if ($key != $newReference) {
  270. $pSheet->protectCells( $newReference, $value, true );
  271. $pSheet->unprotectCells( $key );
  272. }
  273. }
  274. // Update worksheet: autofilter
  275. $autoFilter = $pSheet->getAutoFilter();
  276. $autoFilterRange = $autoFilter->getRange();
  277. if (!empty($autoFilterRange)) {
  278. if ($pNumCols != 0) {
  279. $autoFilterColumns = array_keys($autoFilter->getColumns());
  280. if (count($autoFilterColumns) > 0) {
  281. list($column,$row) = sscanf($pBefore,'%[A-Z]%d');
  282. $columnIndex = PHPExcel_Cell::columnIndexFromString($column);
  283. list($rangeStart,$rangeEnd) = PHPExcel_Cell::rangeBoundaries($autoFilterRange);
  284. if ($columnIndex <= $rangeEnd[0]) {
  285. if ($pNumCols < 0) {
  286. // If we're actually deleting any columns that fall within the autofilter range,
  287. // then we delete any rules for those columns
  288. $deleteColumn = $columnIndex + $pNumCols - 1;
  289. $deleteCount = abs($pNumCols);
  290. for ($i = 1; $i <= $deleteCount; ++$i) {
  291. if (in_array(PHPExcel_Cell::stringFromColumnIndex($deleteColumn),$autoFilterColumns)) {
  292. $autoFilter->clearColumn(PHPExcel_Cell::stringFromColumnIndex($deleteColumn));
  293. }
  294. ++$deleteColumn;
  295. }
  296. }
  297. $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
  298. // Shuffle columns in autofilter range
  299. if ($pNumCols > 0) {
  300. // For insert, we shuffle from end to beginning to avoid overwriting
  301. $startColID = PHPExcel_Cell::stringFromColumnIndex($startCol-1);
  302. $toColID = PHPExcel_Cell::stringFromColumnIndex($startCol+$pNumCols-1);
  303. $endColID = PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0]);
  304. $startColRef = $startCol;
  305. $endColRef = $rangeEnd[0];
  306. $toColRef = $rangeEnd[0]+$pNumCols;
  307. do {
  308. $autoFilter->shiftColumn(PHPExcel_Cell::stringFromColumnIndex($endColRef-1),PHPExcel_Cell::stringFromColumnIndex($toColRef-1));
  309. --$endColRef;
  310. --$toColRef;
  311. } while ($startColRef <= $endColRef);
  312. } else {
  313. // For delete, we shuffle from beginning to end to avoid overwriting
  314. $startColID = PHPExcel_Cell::stringFromColumnIndex($startCol-1);
  315. $toColID = PHPExcel_Cell::stringFromColumnIndex($startCol+$pNumCols-1);
  316. $endColID = PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0]);
  317. do {
  318. $autoFilter->shiftColumn($startColID,$toColID);
  319. ++$startColID;
  320. ++$toColID;
  321. } while ($startColID != $endColID);
  322. }
  323. }
  324. }
  325. }
  326. $pSheet->setAutoFilter( $this->updateCellReference($autoFilterRange, $pBefore, $pNumCols, $pNumRows) );
  327. }
  328. // Update worksheet: freeze pane
  329. if ($pSheet->getFreezePane() != '') {
  330. $pSheet->freezePane( $this->updateCellReference($pSheet->getFreezePane(), $pBefore, $pNumCols, $pNumRows) );
  331. }
  332. // Page setup
  333. if ($pSheet->getPageSetup()->isPrintAreaSet()) {
  334. $pSheet->getPageSetup()->setPrintArea( $this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows) );
  335. }
  336. // Update worksheet: drawings
  337. $aDrawings = $pSheet->getDrawingCollection();
  338. foreach ($aDrawings as $objDrawing) {
  339. $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows);
  340. if ($objDrawing->getCoordinates() != $newReference) {
  341. $objDrawing->setCoordinates($newReference);
  342. }
  343. }
  344. // Update workbook: named ranges
  345. if (count($pSheet->getParent()->getNamedRanges()) > 0) {
  346. foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) {
  347. if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) {
  348. $namedRange->setRange(
  349. $this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows)
  350. );
  351. }
  352. }
  353. }
  354. // Garbage collect
  355. $pSheet->garbageCollect();
  356. }
  357. /**
  358. * Update references within formulas
  359. *
  360. * @param string $pFormula Formula to update
  361. * @param int $pBefore Insert before this one
  362. * @param int $pNumCols Number of columns to insert
  363. * @param int $pNumRows Number of rows to insert
  364. * @return string Updated formula
  365. * @throws Exception
  366. */
  367. public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '') {
  368. // Update cell references in the formula
  369. $formulaBlocks = explode('"',$pFormula);
  370. $i = false;
  371. foreach($formulaBlocks as &$formulaBlock) {
  372. // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
  373. if ($i = !$i) {
  374. $adjustCount = 0;
  375. $newCellTokens = $cellTokens = array();
  376. // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
  377. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_ROWRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  378. if ($matchCount > 0) {
  379. foreach($matches as $match) {
  380. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  381. $fromString .= $match[3].':'.$match[4];
  382. $modified3 = substr($this->updateCellReference('$A'.$match[3],$pBefore,$pNumCols,$pNumRows),2);
  383. $modified4 = substr($this->updateCellReference('$A'.$match[4],$pBefore,$pNumCols,$pNumRows),2);
  384. if ($match[3].':'.$match[4] !== $modified3.':'.$modified4) {
  385. if (($match[2] == '') || (trim($match[2],"'") == $sheetName)) {
  386. $toString = ($match[2] > '') ? $match[2].'!' : '';
  387. $toString .= $modified3.':'.$modified4;
  388. // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
  389. $column = 100000;
  390. $row = 10000000+trim($match[3],'$');
  391. $cellIndex = $column.$row;
  392. $newCellTokens[$cellIndex] = preg_quote($toString);
  393. $cellTokens[$cellIndex] = '/(?<!\d)'.preg_quote($fromString).'(?!\d)/i';
  394. ++$adjustCount;
  395. }
  396. }
  397. }
  398. }
  399. // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
  400. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_COLRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  401. if ($matchCount > 0) {
  402. foreach($matches as $match) {
  403. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  404. $fromString .= $match[3].':'.$match[4];
  405. $modified3 = substr($this->updateCellReference($match[3].'$1',$pBefore,$pNumCols,$pNumRows),0,-2);
  406. $modified4 = substr($this->updateCellReference($match[4].'$1',$pBefore,$pNumCols,$pNumRows),0,-2);
  407. if ($match[3].':'.$match[4] !== $modified3.':'.$modified4) {
  408. if (($match[2] == '') || (trim($match[2],"'") == $sheetName)) {
  409. $toString = ($match[2] > '') ? $match[2].'!' : '';
  410. $toString .= $modified3.':'.$modified4;
  411. // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
  412. $column = PHPExcel_Cell::columnIndexFromString(trim($match[3],'$')) + 100000;
  413. $row = 10000000;
  414. $cellIndex = $column.$row;
  415. $newCellTokens[$cellIndex] = preg_quote($toString);
  416. $cellTokens[$cellIndex] = '/(?<![A-Z])'.preg_quote($fromString).'(?![A-Z])/i';
  417. ++$adjustCount;
  418. }
  419. }
  420. }
  421. }
  422. // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
  423. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_CELLRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  424. if ($matchCount > 0) {
  425. foreach($matches as $match) {
  426. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  427. $fromString .= $match[3].':'.$match[4];
  428. $modified3 = $this->updateCellReference($match[3],$pBefore,$pNumCols,$pNumRows);
  429. $modified4 = $this->updateCellReference($match[4],$pBefore,$pNumCols,$pNumRows);
  430. if ($match[3].$match[4] !== $modified3.$modified4) {
  431. if (($match[2] == '') || (trim($match[2],"'") == $sheetName)) {
  432. $toString = ($match[2] > '') ? $match[2].'!' : '';
  433. $toString .= $modified3.':'.$modified4;
  434. list($column,$row) = PHPExcel_Cell::coordinateFromString($match[3]);
  435. // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
  436. $column = PHPExcel_Cell::columnIndexFromString(trim($column,'$')) + 100000;
  437. $row = trim($row,'$') + 10000000;
  438. $cellIndex = $column.$row;
  439. $newCellTokens[$cellIndex] = preg_quote($toString);
  440. $cellTokens[$cellIndex] = '/(?<![A-Z])'.preg_quote($fromString).'(?!\d)/i';
  441. ++$adjustCount;
  442. }
  443. }
  444. }
  445. }
  446. // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
  447. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_CELLREF.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  448. if ($matchCount > 0) {
  449. foreach($matches as $match) {
  450. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  451. $fromString .= $match[3];
  452. $modified3 = $this->updateCellReference($match[3],$pBefore,$pNumCols,$pNumRows);
  453. if ($match[3] !== $modified3) {
  454. if (($match[2] == '') || (trim($match[2],"'") == $sheetName)) {
  455. $toString = ($match[2] > '') ? $match[2].'!' : '';
  456. $toString .= $modified3;
  457. list($column,$row) = PHPExcel_Cell::coordinateFromString($match[3]);
  458. // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
  459. $column = PHPExcel_Cell::columnIndexFromString(trim($column,'$')) + 100000;
  460. $row = trim($row,'$') + 10000000;
  461. $cellIndex = $column.$row;
  462. $newCellTokens[$cellIndex] = preg_quote($toString);
  463. $cellTokens[$cellIndex] = '/(?<![A-Z])'.preg_quote($fromString).'(?!\d)/i';
  464. ++$adjustCount;
  465. }
  466. }
  467. }
  468. }
  469. if ($adjustCount > 0) {
  470. if ($pNumCols > 0) {
  471. krsort($cellTokens);
  472. krsort($newCellTokens);
  473. } else {
  474. ksort($cellTokens);
  475. ksort($newCellTokens);
  476. }
  477. // Update cell references in the formula
  478. $formulaBlock = str_replace('\\','',preg_replace($cellTokens,$newCellTokens,$formulaBlock));
  479. }
  480. }
  481. }
  482. unset($formulaBlock);
  483. // Then rebuild the formula string
  484. return implode('"',$formulaBlocks);
  485. }
  486. /**
  487. * Update cell reference
  488. *
  489. * @param string $pCellRange Cell range
  490. * @param int $pBefore Insert before this one
  491. * @param int $pNumCols Number of columns to increment
  492. * @param int $pNumRows Number of rows to increment
  493. * @return string Updated cell range
  494. * @throws Exception
  495. */
  496. public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) {
  497. // Is it in another worksheet? Will not have to update anything.
  498. if (strpos($pCellRange, "!") !== false) {
  499. return $pCellRange;
  500. // Is it a range or a single cell?
  501. } elseif (strpos($pCellRange, ':') === false && strpos($pCellRange, ',') === false) {
  502. // Single cell
  503. return $this->_updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
  504. } elseif (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
  505. // Range
  506. return $this->_updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
  507. } else {
  508. // Return original
  509. return $pCellRange;
  510. }
  511. }
  512. /**
  513. * Update named formulas (i.e. containing worksheet references / named ranges)
  514. *
  515. * @param PHPExcel $pPhpExcel Object to update
  516. * @param string $oldName Old name (name to replace)
  517. * @param string $newName New name
  518. */
  519. public function updateNamedFormulas(PHPExcel $pPhpExcel, $oldName = '', $newName = '') {
  520. if ($oldName == '') {
  521. return;
  522. }
  523. foreach ($pPhpExcel->getWorksheetIterator() as $sheet) {
  524. foreach ($sheet->getCellCollection(false) as $cellID) {
  525. $cell = $sheet->getCell($cellID);
  526. if (($cell !== NULL) && ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA)) {
  527. $formula = $cell->getValue();
  528. if (strpos($formula, $oldName) !== false) {
  529. $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
  530. $formula = str_replace($oldName . "!", $newName . "!", $formula);
  531. $cell->setValueExplicit($formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
  532. }
  533. }
  534. }
  535. }
  536. }
  537. /**
  538. * Update cell range
  539. *
  540. * @param string $pCellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
  541. * @param int $pBefore Insert before this one
  542. * @param int $pNumCols Number of columns to increment
  543. * @param int $pNumRows Number of rows to increment
  544. * @return string Updated cell range
  545. * @throws Exception
  546. */
  547. private function _updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) {
  548. if (strpos($pCellRange,':') !== false || strpos($pCellRange, ',') !== false) {
  549. // Update range
  550. $range = PHPExcel_Cell::splitRange($pCellRange);
  551. $ic = count($range);
  552. for ($i = 0; $i < $ic; ++$i) {
  553. $jc = count($range[$i]);
  554. for ($j = 0; $j < $jc; ++$j) {
  555. if (ctype_alpha($range[$i][$j])) {
  556. $r = PHPExcel_Cell::coordinateFromString($this->_updateSingleCellReference($range[$i][$j].'1', $pBefore, $pNumCols, $pNumRows));
  557. $range[$i][$j] = $r[0];
  558. } elseif(ctype_digit($range[$i][$j])) {
  559. $r = PHPExcel_Cell::coordinateFromString($this->_updateSingleCellReference('A'.$range[$i][$j], $pBefore, $pNumCols, $pNumRows));
  560. $range[$i][$j] = $r[1];
  561. } else {
  562. $range[$i][$j] = $this->_updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
  563. }
  564. }
  565. }
  566. // Recreate range string
  567. return PHPExcel_Cell::buildRange($range);
  568. } else {
  569. throw new Exception("Only cell ranges may be passed to this method.");
  570. }
  571. }
  572. /**
  573. * Update single cell reference
  574. *
  575. * @param string $pCellReference Single cell reference
  576. * @param int $pBefore Insert before this one
  577. * @param int $pNumCols Number of columns to increment
  578. * @param int $pNumRows Number of rows to increment
  579. * @return string Updated cell reference
  580. * @throws Exception
  581. */
  582. private function _updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0) {
  583. if (strpos($pCellReference, ':') === false && strpos($pCellReference, ',') === false) {
  584. // Get coordinates of $pBefore
  585. list($beforeColumn, $beforeRow) = PHPExcel_Cell::coordinateFromString( $pBefore );
  586. // Get coordinates of $pCellReference
  587. list($newColumn, $newRow) = PHPExcel_Cell::coordinateFromString( $pCellReference );
  588. // Verify which parts should be updated
  589. $updateColumn = (($newColumn{0} != '$') && ($beforeColumn{0} != '$') &&
  590. PHPExcel_Cell::columnIndexFromString($newColumn) >= PHPExcel_Cell::columnIndexFromString($beforeColumn));
  591. $updateRow = (($newRow{0} != '$') && ($beforeRow{0} != '$') &&
  592. $newRow >= $beforeRow);
  593. // Create new column reference
  594. if ($updateColumn) {
  595. $newColumn = PHPExcel_Cell::stringFromColumnIndex( PHPExcel_Cell::columnIndexFromString($newColumn) - 1 + $pNumCols );
  596. }
  597. // Create new row reference
  598. if ($updateRow) {
  599. $newRow = $newRow + $pNumRows;
  600. }
  601. // Return new reference
  602. return $newColumn . $newRow;
  603. } else {
  604. throw new Exception("Only single cell references may be passed to this method.");
  605. }
  606. }
  607. /**
  608. * __clone implementation. Cloning should not be allowed in a Singleton!
  609. *
  610. * @throws Exception
  611. */
  612. public final function __clone() {
  613. throw new Exception("Cloning a Singleton is not allowed!");
  614. }
  615. }