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.

913 lines
46 KiB

  1. <?php
  2. /**
  3. * PHPExcel_ReferenceHelper (Singleton)
  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
  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_ReferenceHelper
  28. {
  29. /** Constants */
  30. /** Regular Expressions */
  31. const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?<![:a-z\$])(\$?[a-z]{1,3}\$?\d+)(?=[^:!\d\'])';
  32. const REFHELPER_REGEXP_CELLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}\$?\d+):(\$?[a-z]{1,3}\$?\d+)';
  33. const REFHELPER_REGEXP_ROWRANGE = '((\w*|\'[^!]*\')!)?(\$?\d+):(\$?\d+)';
  34. const REFHELPER_REGEXP_COLRANGE = '((\w*|\'[^!]*\')!)?(\$?[a-z]{1,3}):(\$?[a-z]{1,3})';
  35. /**
  36. * Instance of this class
  37. *
  38. * @var PHPExcel_ReferenceHelper
  39. */
  40. private static $instance;
  41. /**
  42. * Get an instance of this class
  43. *
  44. * @return PHPExcel_ReferenceHelper
  45. */
  46. public static function getInstance()
  47. {
  48. if (!isset(self::$instance) || (self::$instance === null)) {
  49. self::$instance = new PHPExcel_ReferenceHelper();
  50. }
  51. return self::$instance;
  52. }
  53. /**
  54. * Create a new PHPExcel_ReferenceHelper
  55. */
  56. protected function __construct()
  57. {
  58. }
  59. /**
  60. * Compare two column addresses
  61. * Intended for use as a Callback function for sorting column addresses by column
  62. *
  63. * @param string $a First column to test (e.g. 'AA')
  64. * @param string $b Second column to test (e.g. 'Z')
  65. * @return integer
  66. */
  67. public static function columnSort($a, $b)
  68. {
  69. return strcasecmp(strlen($a) . $a, strlen($b) . $b);
  70. }
  71. /**
  72. * Compare two column addresses
  73. * Intended for use as a Callback function for reverse sorting column addresses by column
  74. *
  75. * @param string $a First column to test (e.g. 'AA')
  76. * @param string $b Second column to test (e.g. 'Z')
  77. * @return integer
  78. */
  79. public static function columnReverseSort($a, $b)
  80. {
  81. return 1 - strcasecmp(strlen($a) . $a, strlen($b) . $b);
  82. }
  83. /**
  84. * Compare two cell addresses
  85. * Intended for use as a Callback function for sorting cell addresses by column and row
  86. *
  87. * @param string $a First cell to test (e.g. 'AA1')
  88. * @param string $b Second cell to test (e.g. 'Z1')
  89. * @return integer
  90. */
  91. public static function cellSort($a, $b)
  92. {
  93. sscanf($a, '%[A-Z]%d', $ac, $ar);
  94. sscanf($b, '%[A-Z]%d', $bc, $br);
  95. if ($ar == $br) {
  96. return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
  97. }
  98. return ($ar < $br) ? -1 : 1;
  99. }
  100. /**
  101. * Compare two cell addresses
  102. * Intended for use as a Callback function for sorting cell addresses by column and row
  103. *
  104. * @param string $a First cell to test (e.g. 'AA1')
  105. * @param string $b Second cell to test (e.g. 'Z1')
  106. * @return integer
  107. */
  108. public static function cellReverseSort($a, $b)
  109. {
  110. sscanf($a, '%[A-Z]%d', $ac, $ar);
  111. sscanf($b, '%[A-Z]%d', $bc, $br);
  112. if ($ar == $br) {
  113. return 1 - strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
  114. }
  115. return ($ar < $br) ? 1 : -1;
  116. }
  117. /**
  118. * Test whether a cell address falls within a defined range of cells
  119. *
  120. * @param string $cellAddress Address of the cell we're testing
  121. * @param integer $beforeRow Number of the row we're inserting/deleting before
  122. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  123. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  124. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  125. * @return boolean
  126. */
  127. private static function cellAddressInDeleteRange($cellAddress, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)
  128. {
  129. list($cellColumn, $cellRow) = PHPExcel_Cell::coordinateFromString($cellAddress);
  130. $cellColumnIndex = PHPExcel_Cell::columnIndexFromString($cellColumn);
  131. // Is cell within the range of rows/columns if we're deleting
  132. if ($pNumRows < 0 &&
  133. ($cellRow >= ($beforeRow + $pNumRows)) &&
  134. ($cellRow < $beforeRow)) {
  135. return true;
  136. } elseif ($pNumCols < 0 &&
  137. ($cellColumnIndex >= ($beforeColumnIndex + $pNumCols)) &&
  138. ($cellColumnIndex < $beforeColumnIndex)) {
  139. return true;
  140. }
  141. return false;
  142. }
  143. /**
  144. * Update page breaks when inserting/deleting rows/columns
  145. *
  146. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  147. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  148. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  149. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  150. * @param integer $beforeRow Number of the row we're inserting/deleting before
  151. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  152. */
  153. protected function adjustPageBreaks(PHPExcel_Worksheet $pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  154. {
  155. $aBreaks = $pSheet->getBreaks();
  156. ($pNumCols > 0 || $pNumRows > 0) ?
  157. uksort($aBreaks, array('PHPExcel_ReferenceHelper','cellReverseSort')) :
  158. uksort($aBreaks, array('PHPExcel_ReferenceHelper','cellSort'));
  159. foreach ($aBreaks as $key => $value) {
  160. if (self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
  161. // If we're deleting, then clear any defined breaks that are within the range
  162. // of rows/columns that we're deleting
  163. $pSheet->setBreak($key, PHPExcel_Worksheet::BREAK_NONE);
  164. } else {
  165. // Otherwise update any affected breaks by inserting a new break at the appropriate point
  166. // and removing the old affected break
  167. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  168. if ($key != $newReference) {
  169. $pSheet->setBreak($newReference, $value)
  170. ->setBreak($key, PHPExcel_Worksheet::BREAK_NONE);
  171. }
  172. }
  173. }
  174. }
  175. /**
  176. * Update cell comments when inserting/deleting rows/columns
  177. *
  178. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  179. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  180. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  181. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  182. * @param integer $beforeRow Number of the row we're inserting/deleting before
  183. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  184. */
  185. protected function adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  186. {
  187. $aComments = $pSheet->getComments();
  188. $aNewComments = array(); // the new array of all comments
  189. foreach ($aComments as $key => &$value) {
  190. // Any comments inside a deleted range will be ignored
  191. if (!self::cellAddressInDeleteRange($key, $beforeRow, $pNumRows, $beforeColumnIndex, $pNumCols)) {
  192. // Otherwise build a new array of comments indexed by the adjusted cell reference
  193. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  194. $aNewComments[$newReference] = $value;
  195. }
  196. }
  197. // Replace the comments array with the new set of comments
  198. $pSheet->setComments($aNewComments);
  199. }
  200. /**
  201. * Update hyperlinks when inserting/deleting rows/columns
  202. *
  203. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  204. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  205. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  206. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  207. * @param integer $beforeRow Number of the row we're inserting/deleting before
  208. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  209. */
  210. protected function adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  211. {
  212. $aHyperlinkCollection = $pSheet->getHyperlinkCollection();
  213. ($pNumCols > 0 || $pNumRows > 0) ? uksort($aHyperlinkCollection, array('PHPExcel_ReferenceHelper','cellReverseSort')) : uksort($aHyperlinkCollection, array('PHPExcel_ReferenceHelper','cellSort'));
  214. foreach ($aHyperlinkCollection as $key => $value) {
  215. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  216. if ($key != $newReference) {
  217. $pSheet->setHyperlink($newReference, $value);
  218. $pSheet->setHyperlink($key, null);
  219. }
  220. }
  221. }
  222. /**
  223. * Update data validations when inserting/deleting rows/columns
  224. *
  225. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  226. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  227. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  228. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  229. * @param integer $beforeRow Number of the row we're inserting/deleting before
  230. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  231. */
  232. protected function adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  233. {
  234. $aDataValidationCollection = $pSheet->getDataValidationCollection();
  235. ($pNumCols > 0 || $pNumRows > 0) ? uksort($aDataValidationCollection, array('PHPExcel_ReferenceHelper','cellReverseSort')) : uksort($aDataValidationCollection, array('PHPExcel_ReferenceHelper','cellSort'));
  236. foreach ($aDataValidationCollection as $key => $value) {
  237. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  238. if ($key != $newReference) {
  239. $pSheet->setDataValidation($newReference, $value);
  240. $pSheet->setDataValidation($key, null);
  241. }
  242. }
  243. }
  244. /**
  245. * Update merged cells when inserting/deleting rows/columns
  246. *
  247. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  248. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  249. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  250. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  251. * @param integer $beforeRow Number of the row we're inserting/deleting before
  252. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  253. */
  254. protected function adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  255. {
  256. $aMergeCells = $pSheet->getMergeCells();
  257. $aNewMergeCells = array(); // the new array of all merge cells
  258. foreach ($aMergeCells as $key => &$value) {
  259. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  260. $aNewMergeCells[$newReference] = $newReference;
  261. }
  262. $pSheet->setMergeCells($aNewMergeCells); // replace the merge cells array
  263. }
  264. /**
  265. * Update protected cells when inserting/deleting rows/columns
  266. *
  267. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  268. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  269. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  270. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  271. * @param integer $beforeRow Number of the row we're inserting/deleting before
  272. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  273. */
  274. protected function adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  275. {
  276. $aProtectedCells = $pSheet->getProtectedCells();
  277. ($pNumCols > 0 || $pNumRows > 0) ?
  278. uksort($aProtectedCells, array('PHPExcel_ReferenceHelper','cellReverseSort')) :
  279. uksort($aProtectedCells, array('PHPExcel_ReferenceHelper','cellSort'));
  280. foreach ($aProtectedCells as $key => $value) {
  281. $newReference = $this->updateCellReference($key, $pBefore, $pNumCols, $pNumRows);
  282. if ($key != $newReference) {
  283. $pSheet->protectCells($newReference, $value, true);
  284. $pSheet->unprotectCells($key);
  285. }
  286. }
  287. }
  288. /**
  289. * Update column dimensions when inserting/deleting rows/columns
  290. *
  291. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  292. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  293. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  294. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  295. * @param integer $beforeRow Number of the row we're inserting/deleting before
  296. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  297. */
  298. protected function adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  299. {
  300. $aColumnDimensions = array_reverse($pSheet->getColumnDimensions(), true);
  301. if (!empty($aColumnDimensions)) {
  302. foreach ($aColumnDimensions as $objColumnDimension) {
  303. $newReference = $this->updateCellReference($objColumnDimension->getColumnIndex() . '1', $pBefore, $pNumCols, $pNumRows);
  304. list($newReference) = PHPExcel_Cell::coordinateFromString($newReference);
  305. if ($objColumnDimension->getColumnIndex() != $newReference) {
  306. $objColumnDimension->setColumnIndex($newReference);
  307. }
  308. }
  309. $pSheet->refreshColumnDimensions();
  310. }
  311. }
  312. /**
  313. * Update row dimensions when inserting/deleting rows/columns
  314. *
  315. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  316. * @param string $pBefore Insert/Delete before this cell address (e.g. 'A1')
  317. * @param integer $beforeColumnIndex Index number of the column we're inserting/deleting before
  318. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  319. * @param integer $beforeRow Number of the row we're inserting/deleting before
  320. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  321. */
  322. protected function adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows)
  323. {
  324. $aRowDimensions = array_reverse($pSheet->getRowDimensions(), true);
  325. if (!empty($aRowDimensions)) {
  326. foreach ($aRowDimensions as $objRowDimension) {
  327. $newReference = $this->updateCellReference('A' . $objRowDimension->getRowIndex(), $pBefore, $pNumCols, $pNumRows);
  328. list(, $newReference) = PHPExcel_Cell::coordinateFromString($newReference);
  329. if ($objRowDimension->getRowIndex() != $newReference) {
  330. $objRowDimension->setRowIndex($newReference);
  331. }
  332. }
  333. $pSheet->refreshRowDimensions();
  334. $copyDimension = $pSheet->getRowDimension($beforeRow - 1);
  335. for ($i = $beforeRow; $i <= $beforeRow - 1 + $pNumRows; ++$i) {
  336. $newDimension = $pSheet->getRowDimension($i);
  337. $newDimension->setRowHeight($copyDimension->getRowHeight());
  338. $newDimension->setVisible($copyDimension->getVisible());
  339. $newDimension->setOutlineLevel($copyDimension->getOutlineLevel());
  340. $newDimension->setCollapsed($copyDimension->getCollapsed());
  341. }
  342. }
  343. }
  344. /**
  345. * Insert a new column or row, updating all possible related data
  346. *
  347. * @param string $pBefore Insert before this cell address (e.g. 'A1')
  348. * @param integer $pNumCols Number of columns to insert/delete (negative values indicate deletion)
  349. * @param integer $pNumRows Number of rows to insert/delete (negative values indicate deletion)
  350. * @param PHPExcel_Worksheet $pSheet The worksheet that we're editing
  351. * @throws PHPExcel_Exception
  352. */
  353. public function insertNewBefore($pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, PHPExcel_Worksheet $pSheet = null)
  354. {
  355. $remove = ($pNumCols < 0 || $pNumRows < 0);
  356. $aCellCollection = $pSheet->getCellCollection();
  357. // Get coordinates of $pBefore
  358. $beforeColumn = 'A';
  359. $beforeRow = 1;
  360. list($beforeColumn, $beforeRow) = PHPExcel_Cell::coordinateFromString($pBefore);
  361. $beforeColumnIndex = PHPExcel_Cell::columnIndexFromString($beforeColumn);
  362. // Clear cells if we are removing columns or rows
  363. $highestColumn = $pSheet->getHighestColumn();
  364. $highestRow = $pSheet->getHighestRow();
  365. // 1. Clear column strips if we are removing columns
  366. if ($pNumCols < 0 && $beforeColumnIndex - 2 + $pNumCols > 0) {
  367. for ($i = 1; $i <= $highestRow - 1; ++$i) {
  368. for ($j = $beforeColumnIndex - 1 + $pNumCols; $j <= $beforeColumnIndex - 2; ++$j) {
  369. $coordinate = PHPExcel_Cell::stringFromColumnIndex($j) . $i;
  370. $pSheet->removeConditionalStyles($coordinate);
  371. if ($pSheet->cellExists($coordinate)) {
  372. $pSheet->getCell($coordinate)->setValueExplicit('', PHPExcel_Cell_DataType::TYPE_NULL);
  373. $pSheet->getCell($coordinate)->setXfIndex(0);
  374. }
  375. }
  376. }
  377. }
  378. // 2. Clear row strips if we are removing rows
  379. if ($pNumRows < 0 && $beforeRow - 1 + $pNumRows > 0) {
  380. for ($i = $beforeColumnIndex - 1; $i <= PHPExcel_Cell::columnIndexFromString($highestColumn) - 1; ++$i) {
  381. for ($j = $beforeRow + $pNumRows; $j <= $beforeRow - 1; ++$j) {
  382. $coordinate = PHPExcel_Cell::stringFromColumnIndex($i) . $j;
  383. $pSheet->removeConditionalStyles($coordinate);
  384. if ($pSheet->cellExists($coordinate)) {
  385. $pSheet->getCell($coordinate)->setValueExplicit('', PHPExcel_Cell_DataType::TYPE_NULL);
  386. $pSheet->getCell($coordinate)->setXfIndex(0);
  387. }
  388. }
  389. }
  390. }
  391. // Loop through cells, bottom-up, and change cell coordinates
  392. if ($remove) {
  393. // It's faster to reverse and pop than to use unshift, especially with large cell collections
  394. $aCellCollection = array_reverse($aCellCollection);
  395. }
  396. while ($cellID = array_pop($aCellCollection)) {
  397. $cell = $pSheet->getCell($cellID);
  398. $cellIndex = PHPExcel_Cell::columnIndexFromString($cell->getColumn());
  399. if ($cellIndex-1 + $pNumCols < 0) {
  400. continue;
  401. }
  402. // New coordinates
  403. $newCoordinates = PHPExcel_Cell::stringFromColumnIndex($cellIndex-1 + $pNumCols) . ($cell->getRow() + $pNumRows);
  404. // Should the cell be updated? Move value and cellXf index from one cell to another.
  405. if (($cellIndex >= $beforeColumnIndex) && ($cell->getRow() >= $beforeRow)) {
  406. // Update cell styles
  407. $pSheet->getCell($newCoordinates)->setXfIndex($cell->getXfIndex());
  408. // Insert this cell at its new location
  409. if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
  410. // Formula should be adjusted
  411. $pSheet->getCell($newCoordinates)
  412. ->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
  413. } else {
  414. // Formula should not be adjusted
  415. $pSheet->getCell($newCoordinates)->setValue($cell->getValue());
  416. }
  417. // Clear the original cell
  418. $pSheet->getCellCacheController()->deleteCacheData($cellID);
  419. } else {
  420. /* We don't need to update styles for rows/columns before our insertion position,
  421. but we do still need to adjust any formulae in those cells */
  422. if ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA) {
  423. // Formula should be adjusted
  424. $cell->setValue($this->updateFormulaReferences($cell->getValue(), $pBefore, $pNumCols, $pNumRows, $pSheet->getTitle()));
  425. }
  426. }
  427. }
  428. // Duplicate styles for the newly inserted cells
  429. $highestColumn = $pSheet->getHighestColumn();
  430. $highestRow = $pSheet->getHighestRow();
  431. if ($pNumCols > 0 && $beforeColumnIndex - 2 > 0) {
  432. for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
  433. // Style
  434. $coordinate = PHPExcel_Cell::stringFromColumnIndex($beforeColumnIndex - 2) . $i;
  435. if ($pSheet->cellExists($coordinate)) {
  436. $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
  437. $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
  438. $pSheet->getConditionalStyles($coordinate) : false;
  439. for ($j = $beforeColumnIndex - 1; $j <= $beforeColumnIndex - 2 + $pNumCols; ++$j) {
  440. $pSheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
  441. if ($conditionalStyles) {
  442. $cloned = array();
  443. foreach ($conditionalStyles as $conditionalStyle) {
  444. $cloned[] = clone $conditionalStyle;
  445. }
  446. $pSheet->setConditionalStyles(PHPExcel_Cell::stringFromColumnIndex($j) . $i, $cloned);
  447. }
  448. }
  449. }
  450. }
  451. }
  452. if ($pNumRows > 0 && $beforeRow - 1 > 0) {
  453. for ($i = $beforeColumnIndex - 1; $i <= PHPExcel_Cell::columnIndexFromString($highestColumn) - 1; ++$i) {
  454. // Style
  455. $coordinate = PHPExcel_Cell::stringFromColumnIndex($i) . ($beforeRow - 1);
  456. if ($pSheet->cellExists($coordinate)) {
  457. $xfIndex = $pSheet->getCell($coordinate)->getXfIndex();
  458. $conditionalStyles = $pSheet->conditionalStylesExists($coordinate) ?
  459. $pSheet->getConditionalStyles($coordinate) : false;
  460. for ($j = $beforeRow; $j <= $beforeRow - 1 + $pNumRows; ++$j) {
  461. $pSheet->getCell(PHPExcel_Cell::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
  462. if ($conditionalStyles) {
  463. $cloned = array();
  464. foreach ($conditionalStyles as $conditionalStyle) {
  465. $cloned[] = clone $conditionalStyle;
  466. }
  467. $pSheet->setConditionalStyles(PHPExcel_Cell::stringFromColumnIndex($i) . $j, $cloned);
  468. }
  469. }
  470. }
  471. }
  472. }
  473. // Update worksheet: column dimensions
  474. $this->adjustColumnDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  475. // Update worksheet: row dimensions
  476. $this->adjustRowDimensions($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  477. // Update worksheet: page breaks
  478. $this->adjustPageBreaks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  479. // Update worksheet: comments
  480. $this->adjustComments($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  481. // Update worksheet: hyperlinks
  482. $this->adjustHyperlinks($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  483. // Update worksheet: data validations
  484. $this->adjustDataValidations($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  485. // Update worksheet: merge cells
  486. $this->adjustMergeCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  487. // Update worksheet: protected cells
  488. $this->adjustProtectedCells($pSheet, $pBefore, $beforeColumnIndex, $pNumCols, $beforeRow, $pNumRows);
  489. // Update worksheet: autofilter
  490. $autoFilter = $pSheet->getAutoFilter();
  491. $autoFilterRange = $autoFilter->getRange();
  492. if (!empty($autoFilterRange)) {
  493. if ($pNumCols != 0) {
  494. $autoFilterColumns = array_keys($autoFilter->getColumns());
  495. if (count($autoFilterColumns) > 0) {
  496. sscanf($pBefore, '%[A-Z]%d', $column, $row);
  497. $columnIndex = PHPExcel_Cell::columnIndexFromString($column);
  498. list($rangeStart, $rangeEnd) = PHPExcel_Cell::rangeBoundaries($autoFilterRange);
  499. if ($columnIndex <= $rangeEnd[0]) {
  500. if ($pNumCols < 0) {
  501. // If we're actually deleting any columns that fall within the autofilter range,
  502. // then we delete any rules for those columns
  503. $deleteColumn = $columnIndex + $pNumCols - 1;
  504. $deleteCount = abs($pNumCols);
  505. for ($i = 1; $i <= $deleteCount; ++$i) {
  506. if (in_array(PHPExcel_Cell::stringFromColumnIndex($deleteColumn), $autoFilterColumns)) {
  507. $autoFilter->clearColumn(PHPExcel_Cell::stringFromColumnIndex($deleteColumn));
  508. }
  509. ++$deleteColumn;
  510. }
  511. }
  512. $startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
  513. // Shuffle columns in autofilter range
  514. if ($pNumCols > 0) {
  515. // For insert, we shuffle from end to beginning to avoid overwriting
  516. $startColID = PHPExcel_Cell::stringFromColumnIndex($startCol-1);
  517. $toColID = PHPExcel_Cell::stringFromColumnIndex($startCol+$pNumCols-1);
  518. $endColID = PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0]);
  519. $startColRef = $startCol;
  520. $endColRef = $rangeEnd[0];
  521. $toColRef = $rangeEnd[0]+$pNumCols;
  522. do {
  523. $autoFilter->shiftColumn(PHPExcel_Cell::stringFromColumnIndex($endColRef-1), PHPExcel_Cell::stringFromColumnIndex($toColRef-1));
  524. --$endColRef;
  525. --$toColRef;
  526. } while ($startColRef <= $endColRef);
  527. } else {
  528. // For delete, we shuffle from beginning to end to avoid overwriting
  529. $startColID = PHPExcel_Cell::stringFromColumnIndex($startCol-1);
  530. $toColID = PHPExcel_Cell::stringFromColumnIndex($startCol+$pNumCols-1);
  531. $endColID = PHPExcel_Cell::stringFromColumnIndex($rangeEnd[0]);
  532. do {
  533. $autoFilter->shiftColumn($startColID, $toColID);
  534. ++$startColID;
  535. ++$toColID;
  536. } while ($startColID != $endColID);
  537. }
  538. }
  539. }
  540. }
  541. $pSheet->setAutoFilter($this->updateCellReference($autoFilterRange, $pBefore, $pNumCols, $pNumRows));
  542. }
  543. // Update worksheet: freeze pane
  544. if ($pSheet->getFreezePane() != '') {
  545. $pSheet->freezePane($this->updateCellReference($pSheet->getFreezePane(), $pBefore, $pNumCols, $pNumRows));
  546. }
  547. // Page setup
  548. if ($pSheet->getPageSetup()->isPrintAreaSet()) {
  549. $pSheet->getPageSetup()->setPrintArea($this->updateCellReference($pSheet->getPageSetup()->getPrintArea(), $pBefore, $pNumCols, $pNumRows));
  550. }
  551. // Update worksheet: drawings
  552. $aDrawings = $pSheet->getDrawingCollection();
  553. foreach ($aDrawings as $objDrawing) {
  554. $newReference = $this->updateCellReference($objDrawing->getCoordinates(), $pBefore, $pNumCols, $pNumRows);
  555. if ($objDrawing->getCoordinates() != $newReference) {
  556. $objDrawing->setCoordinates($newReference);
  557. }
  558. }
  559. // Update workbook: named ranges
  560. if (count($pSheet->getParent()->getNamedRanges()) > 0) {
  561. foreach ($pSheet->getParent()->getNamedRanges() as $namedRange) {
  562. if ($namedRange->getWorksheet()->getHashCode() == $pSheet->getHashCode()) {
  563. $namedRange->setRange($this->updateCellReference($namedRange->getRange(), $pBefore, $pNumCols, $pNumRows));
  564. }
  565. }
  566. }
  567. // Garbage collect
  568. $pSheet->garbageCollect();
  569. }
  570. /**
  571. * Update references within formulas
  572. *
  573. * @param string $pFormula Formula to update
  574. * @param int $pBefore Insert before this one
  575. * @param int $pNumCols Number of columns to insert
  576. * @param int $pNumRows Number of rows to insert
  577. * @param string $sheetName Worksheet name/title
  578. * @return string Updated formula
  579. * @throws PHPExcel_Exception
  580. */
  581. public function updateFormulaReferences($pFormula = '', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0, $sheetName = '')
  582. {
  583. // Update cell references in the formula
  584. $formulaBlocks = explode('"', $pFormula);
  585. $i = false;
  586. foreach ($formulaBlocks as &$formulaBlock) {
  587. // Ignore blocks that were enclosed in quotes (alternating entries in the $formulaBlocks array after the explode)
  588. if ($i = !$i) {
  589. $adjustCount = 0;
  590. $newCellTokens = $cellTokens = array();
  591. // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5)
  592. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_ROWRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  593. if ($matchCount > 0) {
  594. foreach ($matches as $match) {
  595. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  596. $fromString .= $match[3].':'.$match[4];
  597. $modified3 = substr($this->updateCellReference('$A'.$match[3], $pBefore, $pNumCols, $pNumRows), 2);
  598. $modified4 = substr($this->updateCellReference('$A'.$match[4], $pBefore, $pNumCols, $pNumRows), 2);
  599. if ($match[3].':'.$match[4] !== $modified3.':'.$modified4) {
  600. if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
  601. $toString = ($match[2] > '') ? $match[2].'!' : '';
  602. $toString .= $modified3.':'.$modified4;
  603. // 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
  604. $column = 100000;
  605. $row = 10000000 + trim($match[3], '$');
  606. $cellIndex = $column.$row;
  607. $newCellTokens[$cellIndex] = preg_quote($toString);
  608. $cellTokens[$cellIndex] = '/(?<!\d\$\!)'.preg_quote($fromString).'(?!\d)/i';
  609. ++$adjustCount;
  610. }
  611. }
  612. }
  613. }
  614. // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E)
  615. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_COLRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  616. if ($matchCount > 0) {
  617. foreach ($matches as $match) {
  618. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  619. $fromString .= $match[3].':'.$match[4];
  620. $modified3 = substr($this->updateCellReference($match[3].'$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
  621. $modified4 = substr($this->updateCellReference($match[4].'$1', $pBefore, $pNumCols, $pNumRows), 0, -2);
  622. if ($match[3].':'.$match[4] !== $modified3.':'.$modified4) {
  623. if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
  624. $toString = ($match[2] > '') ? $match[2].'!' : '';
  625. $toString .= $modified3.':'.$modified4;
  626. // 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
  627. $column = PHPExcel_Cell::columnIndexFromString(trim($match[3], '$')) + 100000;
  628. $row = 10000000;
  629. $cellIndex = $column.$row;
  630. $newCellTokens[$cellIndex] = preg_quote($toString);
  631. $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])'.preg_quote($fromString).'(?![A-Z])/i';
  632. ++$adjustCount;
  633. }
  634. }
  635. }
  636. }
  637. // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5)
  638. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_CELLRANGE.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  639. if ($matchCount > 0) {
  640. foreach ($matches as $match) {
  641. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  642. $fromString .= $match[3].':'.$match[4];
  643. $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
  644. $modified4 = $this->updateCellReference($match[4], $pBefore, $pNumCols, $pNumRows);
  645. if ($match[3].$match[4] !== $modified3.$modified4) {
  646. if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
  647. $toString = ($match[2] > '') ? $match[2].'!' : '';
  648. $toString .= $modified3.':'.$modified4;
  649. list($column, $row) = PHPExcel_Cell::coordinateFromString($match[3]);
  650. // 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
  651. $column = PHPExcel_Cell::columnIndexFromString(trim($column, '$')) + 100000;
  652. $row = trim($row, '$') + 10000000;
  653. $cellIndex = $column.$row;
  654. $newCellTokens[$cellIndex] = preg_quote($toString);
  655. $cellTokens[$cellIndex] = '/(?<![A-Z]\$\!)'.preg_quote($fromString).'(?!\d)/i';
  656. ++$adjustCount;
  657. }
  658. }
  659. }
  660. }
  661. // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5)
  662. $matchCount = preg_match_all('/'.self::REFHELPER_REGEXP_CELLREF.'/i', ' '.$formulaBlock.' ', $matches, PREG_SET_ORDER);
  663. if ($matchCount > 0) {
  664. foreach ($matches as $match) {
  665. $fromString = ($match[2] > '') ? $match[2].'!' : '';
  666. $fromString .= $match[3];
  667. $modified3 = $this->updateCellReference($match[3], $pBefore, $pNumCols, $pNumRows);
  668. if ($match[3] !== $modified3) {
  669. if (($match[2] == '') || (trim($match[2], "'") == $sheetName)) {
  670. $toString = ($match[2] > '') ? $match[2].'!' : '';
  671. $toString .= $modified3;
  672. list($column, $row) = PHPExcel_Cell::coordinateFromString($match[3]);
  673. // 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
  674. $column = PHPExcel_Cell::columnIndexFromString(trim($column, '$')) + 100000;
  675. $row = trim($row, '$') + 10000000;
  676. $cellIndex = $row . $column;
  677. $newCellTokens[$cellIndex] = preg_quote($toString);
  678. $cellTokens[$cellIndex] = '/(?<![A-Z\$\!])'.preg_quote($fromString).'(?!\d)/i';
  679. ++$adjustCount;
  680. }
  681. }
  682. }
  683. }
  684. if ($adjustCount > 0) {
  685. if ($pNumCols > 0 || $pNumRows > 0) {
  686. krsort($cellTokens);
  687. krsort($newCellTokens);
  688. } else {
  689. ksort($cellTokens);
  690. ksort($newCellTokens);
  691. } // Update cell references in the formula
  692. $formulaBlock = str_replace('\\', '', preg_replace($cellTokens, $newCellTokens, $formulaBlock));
  693. }
  694. }
  695. }
  696. unset($formulaBlock);
  697. // Then rebuild the formula string
  698. return implode('"', $formulaBlocks);
  699. }
  700. /**
  701. * Update cell reference
  702. *
  703. * @param string $pCellRange Cell range
  704. * @param int $pBefore Insert before this one
  705. * @param int $pNumCols Number of columns to increment
  706. * @param int $pNumRows Number of rows to increment
  707. * @return string Updated cell range
  708. * @throws PHPExcel_Exception
  709. */
  710. public function updateCellReference($pCellRange = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
  711. {
  712. // Is it in another worksheet? Will not have to update anything.
  713. if (strpos($pCellRange, "!") !== false) {
  714. return $pCellRange;
  715. // Is it a range or a single cell?
  716. } elseif (strpos($pCellRange, ':') === false && strpos($pCellRange, ',') === false) {
  717. // Single cell
  718. return $this->updateSingleCellReference($pCellRange, $pBefore, $pNumCols, $pNumRows);
  719. } elseif (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
  720. // Range
  721. return $this->updateCellRange($pCellRange, $pBefore, $pNumCols, $pNumRows);
  722. } else {
  723. // Return original
  724. return $pCellRange;
  725. }
  726. }
  727. /**
  728. * Update named formulas (i.e. containing worksheet references / named ranges)
  729. *
  730. * @param PHPExcel $pPhpExcel Object to update
  731. * @param string $oldName Old name (name to replace)
  732. * @param string $newName New name
  733. */
  734. public function updateNamedFormulas(PHPExcel $pPhpExcel, $oldName = '', $newName = '')
  735. {
  736. if ($oldName == '') {
  737. return;
  738. }
  739. foreach ($pPhpExcel->getWorksheetIterator() as $sheet) {
  740. foreach ($sheet->getCellCollection(false) as $cellID) {
  741. $cell = $sheet->getCell($cellID);
  742. if (($cell !== null) && ($cell->getDataType() == PHPExcel_Cell_DataType::TYPE_FORMULA)) {
  743. $formula = $cell->getValue();
  744. if (strpos($formula, $oldName) !== false) {
  745. $formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
  746. $formula = str_replace($oldName . "!", $newName . "!", $formula);
  747. $cell->setValueExplicit($formula, PHPExcel_Cell_DataType::TYPE_FORMULA);
  748. }
  749. }
  750. }
  751. }
  752. }
  753. /**
  754. * Update cell range
  755. *
  756. * @param string $pCellRange Cell range (e.g. 'B2:D4', 'B:C' or '2:3')
  757. * @param int $pBefore Insert before this one
  758. * @param int $pNumCols Number of columns to increment
  759. * @param int $pNumRows Number of rows to increment
  760. * @return string Updated cell range
  761. * @throws PHPExcel_Exception
  762. */
  763. private function updateCellRange($pCellRange = 'A1:A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
  764. {
  765. if (strpos($pCellRange, ':') !== false || strpos($pCellRange, ',') !== false) {
  766. // Update range
  767. $range = PHPExcel_Cell::splitRange($pCellRange);
  768. $ic = count($range);
  769. for ($i = 0; $i < $ic; ++$i) {
  770. $jc = count($range[$i]);
  771. for ($j = 0; $j < $jc; ++$j) {
  772. if (ctype_alpha($range[$i][$j])) {
  773. $r = PHPExcel_Cell::coordinateFromString($this->updateSingleCellReference($range[$i][$j].'1', $pBefore, $pNumCols, $pNumRows));
  774. $range[$i][$j] = $r[0];
  775. } elseif (ctype_digit($range[$i][$j])) {
  776. $r = PHPExcel_Cell::coordinateFromString($this->updateSingleCellReference('A'.$range[$i][$j], $pBefore, $pNumCols, $pNumRows));
  777. $range[$i][$j] = $r[1];
  778. } else {
  779. $range[$i][$j] = $this->updateSingleCellReference($range[$i][$j], $pBefore, $pNumCols, $pNumRows);
  780. }
  781. }
  782. }
  783. // Recreate range string
  784. return PHPExcel_Cell::buildRange($range);
  785. } else {
  786. throw new PHPExcel_Exception("Only cell ranges may be passed to this method.");
  787. }
  788. }
  789. /**
  790. * Update single cell reference
  791. *
  792. * @param string $pCellReference Single cell reference
  793. * @param int $pBefore Insert before this one
  794. * @param int $pNumCols Number of columns to increment
  795. * @param int $pNumRows Number of rows to increment
  796. * @return string Updated cell reference
  797. * @throws PHPExcel_Exception
  798. */
  799. private function updateSingleCellReference($pCellReference = 'A1', $pBefore = 'A1', $pNumCols = 0, $pNumRows = 0)
  800. {
  801. if (strpos($pCellReference, ':') === false && strpos($pCellReference, ',') === false) {
  802. // Get coordinates of $pBefore
  803. list($beforeColumn, $beforeRow) = PHPExcel_Cell::coordinateFromString($pBefore);
  804. // Get coordinates of $pCellReference
  805. list($newColumn, $newRow) = PHPExcel_Cell::coordinateFromString($pCellReference);
  806. // Verify which parts should be updated
  807. $updateColumn = (($newColumn[0] != '$') && ($beforeColumn[0] != '$') && (PHPExcel_Cell::columnIndexFromString($newColumn) >= PHPExcel_Cell::columnIndexFromString($beforeColumn)));
  808. $updateRow = (($newRow[0] != '$') && ($beforeRow[0] != '$') && $newRow >= $beforeRow);
  809. // Create new column reference
  810. if ($updateColumn) {
  811. $newColumn = PHPExcel_Cell::stringFromColumnIndex(PHPExcel_Cell::columnIndexFromString($newColumn) - 1 + $pNumCols);
  812. }
  813. // Create new row reference
  814. if ($updateRow) {
  815. $newRow = $newRow + $pNumRows;
  816. }
  817. // Return new reference
  818. return $newColumn . $newRow;
  819. } else {
  820. throw new PHPExcel_Exception("Only single cell references may be passed to this method.");
  821. }
  822. }
  823. /**
  824. * __clone implementation. Cloning should not be allowed in a Singleton!
  825. *
  826. * @throws PHPExcel_Exception
  827. */
  828. final public function __clone()
  829. {
  830. throw new PHPExcel_Exception("Cloning a Singleton is not allowed!");
  831. }
  832. }