Skip to content

Commit 1ea119c

Browse files
author
MarkBaker
committed
Merge branch 'master' into CalculationEngine-Array-Formulae-Initial-Work
2 parents ec3281b + 09cf6ab commit 1ea119c

File tree

3 files changed

+236
-121
lines changed

3 files changed

+236
-121
lines changed

src/PhpSpreadsheet/ReferenceHelper.php

Lines changed: 172 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
66
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
77
use PhpOffice\PhpSpreadsheet\Cell\DataType;
8+
use PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter;
89
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
910

1011
class ReferenceHelper
@@ -358,8 +359,12 @@ protected function adjustRowDimensions(Worksheet $worksheet, $beforeCellAddress,
358359
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
359360
* @param Worksheet $worksheet The worksheet that we're editing
360361
*/
361-
public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfRows, Worksheet $worksheet): void
362-
{
362+
public function insertNewBefore(
363+
string $beforeCellAddress,
364+
int $numberOfColumns,
365+
int $numberOfRows,
366+
Worksheet $worksheet
367+
): void {
363368
$remove = ($numberOfColumns < 0 || $numberOfRows < 0);
364369
$allCoordinates = $worksheet->getCoordinates();
365370

@@ -372,30 +377,12 @@ public function insertNewBefore($beforeCellAddress, $numberOfColumns, $numberOfR
372377

373378
// 1. Clear column strips if we are removing columns
374379
if ($numberOfColumns < 0 && $beforeColumn - 2 + $numberOfColumns > 0) {
375-
for ($i = 1; $i <= $highestRow - 1; ++$i) {
376-
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
377-
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
378-
$worksheet->removeConditionalStyles($coordinate);
379-
if ($worksheet->cellExists($coordinate)) {
380-
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
381-
$worksheet->getCell($coordinate)->setXfIndex(0);
382-
}
383-
}
384-
}
380+
$this->clearColumnStrips($highestRow, $beforeColumn, $numberOfColumns, $worksheet);
385381
}
386382

387383
// 2. Clear row strips if we are removing rows
388384
if ($numberOfRows < 0 && $beforeRow - 1 + $numberOfRows > 0) {
389-
for ($i = $beforeColumn - 1; $i <= Coordinate::columnIndexFromString($highestColumn) - 1; ++$i) {
390-
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
391-
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
392-
$worksheet->removeConditionalStyles($coordinate);
393-
if ($worksheet->cellExists($coordinate)) {
394-
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
395-
$worksheet->getCell($coordinate)->setXfIndex(0);
396-
}
397-
}
398-
}
385+
$this->clearRowStrips($highestColumn, $beforeColumn, $beforeRow, $numberOfRows, $worksheet);
399386
}
400387

401388
// Find missing coordinates. This is important when inserting column before the last column
@@ -476,47 +463,11 @@ function ($coordinate) use ($allCoordinates) {
476463
$highestRow = $worksheet->getHighestRow();
477464

478465
if ($numberOfColumns > 0 && $beforeColumn - 2 > 0) {
479-
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
480-
// Style
481-
$coordinate = Coordinate::stringFromColumnIndex($beforeColumn - 1) . $i;
482-
if ($worksheet->cellExists($coordinate)) {
483-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
484-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
485-
$worksheet->getConditionalStyles($coordinate) : false;
486-
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
487-
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
488-
if ($conditionalStyles) {
489-
$cloned = [];
490-
foreach ($conditionalStyles as $conditionalStyle) {
491-
$cloned[] = clone $conditionalStyle;
492-
}
493-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
494-
}
495-
}
496-
}
497-
}
466+
$this->duplicateStylesByColumn($worksheet, $beforeColumn, $beforeRow, $highestRow, $numberOfColumns);
498467
}
499468

500469
if ($numberOfRows > 0 && $beforeRow - 1 > 0) {
501-
for ($i = $beforeColumn; $i <= Coordinate::columnIndexFromString($highestColumn); ++$i) {
502-
// Style
503-
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
504-
if ($worksheet->cellExists($coordinate)) {
505-
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
506-
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
507-
$worksheet->getConditionalStyles($coordinate) : false;
508-
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
509-
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
510-
if ($conditionalStyles) {
511-
$cloned = [];
512-
foreach ($conditionalStyles as $conditionalStyle) {
513-
$cloned[] = clone $conditionalStyle;
514-
}
515-
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
516-
}
517-
}
518-
}
519-
}
470+
$this->duplicateStylesByRow($worksheet, $beforeColumn, $beforeRow, $highestColumn, $numberOfRows);
520471
}
521472

522473
// Update worksheet: column dimensions
@@ -544,59 +495,7 @@ function ($coordinate) use ($allCoordinates) {
544495
$this->adjustProtectedCells($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
545496

546497
// Update worksheet: autofilter
547-
$autoFilter = $worksheet->getAutoFilter();
548-
$autoFilterRange = $autoFilter->getRange();
549-
if (!empty($autoFilterRange)) {
550-
if ($numberOfColumns != 0) {
551-
$autoFilterColumns = $autoFilter->getColumns();
552-
if (count($autoFilterColumns) > 0) {
553-
$column = '';
554-
$row = 0;
555-
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
556-
$columnIndex = Coordinate::columnIndexFromString($column);
557-
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
558-
if ($columnIndex <= $rangeEnd[0]) {
559-
if ($numberOfColumns < 0) {
560-
// If we're actually deleting any columns that fall within the autofilter range,
561-
// then we delete any rules for those columns
562-
$deleteColumn = $columnIndex + $numberOfColumns - 1;
563-
$deleteCount = abs($numberOfColumns);
564-
for ($i = 1; $i <= $deleteCount; ++$i) {
565-
if (isset($autoFilterColumns[Coordinate::stringFromColumnIndex($deleteColumn + 1)])) {
566-
$autoFilter->clearColumn(Coordinate::stringFromColumnIndex($deleteColumn + 1));
567-
}
568-
++$deleteColumn;
569-
}
570-
}
571-
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
572-
573-
// Shuffle columns in autofilter range
574-
if ($numberOfColumns > 0) {
575-
$startColRef = $startCol;
576-
$endColRef = $rangeEnd[0];
577-
$toColRef = $rangeEnd[0] + $numberOfColumns;
578-
579-
do {
580-
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
581-
--$endColRef;
582-
--$toColRef;
583-
} while ($startColRef <= $endColRef);
584-
} else {
585-
// For delete, we shuffle from beginning to end to avoid overwriting
586-
$startColID = Coordinate::stringFromColumnIndex($startCol);
587-
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
588-
$endColID = Coordinate::stringFromColumnIndex($rangeEnd[0] + 1);
589-
do {
590-
$autoFilter->shiftColumn($startColID, $toColID);
591-
++$startColID;
592-
++$toColID;
593-
} while ($startColID != $endColID);
594-
}
595-
}
596-
}
597-
}
598-
$worksheet->setAutoFilter($this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows));
599-
}
498+
$this->adjustAutoFilter($worksheet, $beforeCellAddress, $numberOfColumns, $numberOfRows);
600499

601500
// Update worksheet: freeze pane
602501
if ($worksheet->getFreezePane()) {
@@ -611,7 +510,9 @@ function ($coordinate) use ($allCoordinates) {
611510

612511
// Page setup
613512
if ($worksheet->getPageSetup()->isPrintAreaSet()) {
614-
$worksheet->getPageSetup()->setPrintArea($this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows));
513+
$worksheet->getPageSetup()->setPrintArea(
514+
$this->updateCellReference($worksheet->getPageSetup()->getPrintArea(), $beforeCellAddress, $numberOfColumns, $numberOfRows)
515+
);
615516
}
616517

617518
// Update worksheet: drawings
@@ -958,7 +859,7 @@ public function updateNamedFormulas(Spreadsheet $spreadsheet, $oldName = '', $ne
958859
foreach ($spreadsheet->getWorksheetIterator() as $sheet) {
959860
foreach ($sheet->getCoordinates(false) as $coordinate) {
960861
$cell = $sheet->getCell($coordinate);
961-
if (($cell !== null) && ($cell->getDataType() == DataType::TYPE_FORMULA)) {
862+
if (($cell !== null) && ($cell->getDataType() === DataType::TYPE_FORMULA)) {
962863
$formula = $cell->getValue();
963864
if (strpos($formula, $oldName) !== false) {
964865
$formula = str_replace("'" . $oldName . "'!", "'" . $newName . "'!", $formula);
@@ -1048,6 +949,162 @@ private function updateSingleCellReference($cellReference = 'A1', $beforeCellAdd
1048949
return $newColumn . $newRow;
1049950
}
1050951

952+
private function clearColumnStrips(int $highestRow, int $beforeColumn, int $numberOfColumns, Worksheet $worksheet): void
953+
{
954+
for ($i = 1; $i <= $highestRow - 1; ++$i) {
955+
for ($j = $beforeColumn - 1 + $numberOfColumns; $j <= $beforeColumn - 2; ++$j) {
956+
$coordinate = Coordinate::stringFromColumnIndex($j + 1) . $i;
957+
$worksheet->removeConditionalStyles($coordinate);
958+
if ($worksheet->cellExists($coordinate)) {
959+
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
960+
$worksheet->getCell($coordinate)->setXfIndex(0);
961+
}
962+
}
963+
}
964+
}
965+
966+
private function clearRowStrips(string $highestColumn, int $beforeColumn, int $beforeRow, int $numberOfRows, Worksheet $worksheet): void
967+
{
968+
$lastColumnIndex = Coordinate::columnIndexFromString($highestColumn) - 1;
969+
970+
for ($i = $beforeColumn - 1; $i <= $lastColumnIndex; ++$i) {
971+
for ($j = $beforeRow + $numberOfRows; $j <= $beforeRow - 1; ++$j) {
972+
$coordinate = Coordinate::stringFromColumnIndex($i + 1) . $j;
973+
$worksheet->removeConditionalStyles($coordinate);
974+
if ($worksheet->cellExists($coordinate)) {
975+
$worksheet->getCell($coordinate)->setValueExplicit('', DataType::TYPE_NULL);
976+
$worksheet->getCell($coordinate)->setXfIndex(0);
977+
}
978+
}
979+
}
980+
}
981+
982+
private function adjustAutoFilter(Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
983+
{
984+
$autoFilter = $worksheet->getAutoFilter();
985+
$autoFilterRange = $autoFilter->getRange();
986+
if (!empty($autoFilterRange)) {
987+
if ($numberOfColumns !== 0) {
988+
$autoFilterColumns = $autoFilter->getColumns();
989+
if (count($autoFilterColumns) > 0) {
990+
$column = '';
991+
$row = 0;
992+
sscanf($beforeCellAddress, '%[A-Z]%d', $column, $row);
993+
$columnIndex = Coordinate::columnIndexFromString($column);
994+
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($autoFilterRange);
995+
if ($columnIndex <= $rangeEnd[0]) {
996+
if ($numberOfColumns < 0) {
997+
$this->adjustAutoFilterDeleteRules($columnIndex, $numberOfColumns, $autoFilterColumns, $autoFilter);
998+
}
999+
$startCol = ($columnIndex > $rangeStart[0]) ? $columnIndex : $rangeStart[0];
1000+
1001+
// Shuffle columns in autofilter range
1002+
if ($numberOfColumns > 0) {
1003+
$this->adjustAutoFilterInsert($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
1004+
} else {
1005+
$this->adjustAutoFilterDelete($startCol, $numberOfColumns, $rangeEnd[0], $autoFilter);
1006+
}
1007+
}
1008+
}
1009+
}
1010+
1011+
$worksheet->setAutoFilter(
1012+
$this->updateCellReference($autoFilterRange, $beforeCellAddress, $numberOfColumns, $numberOfRows)
1013+
);
1014+
}
1015+
}
1016+
1017+
private function adjustAutoFilterDeleteRules(int $columnIndex, int $numberOfColumns, array $autoFilterColumns, AutoFilter $autoFilter): void
1018+
{
1019+
// If we're actually deleting any columns that fall within the autofilter range,
1020+
// then we delete any rules for those columns
1021+
$deleteColumn = $columnIndex + $numberOfColumns - 1;
1022+
$deleteCount = abs($numberOfColumns);
1023+
1024+
for ($i = 1; $i <= $deleteCount; ++$i) {
1025+
$columnName = Coordinate::stringFromColumnIndex($deleteColumn + 1);
1026+
if (isset($autoFilterColumns[$columnName])) {
1027+
$autoFilter->clearColumn($columnName);
1028+
}
1029+
++$deleteColumn;
1030+
}
1031+
}
1032+
1033+
private function adjustAutoFilterInsert(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
1034+
{
1035+
$startColRef = $startCol;
1036+
$endColRef = $rangeEnd;
1037+
$toColRef = $rangeEnd + $numberOfColumns;
1038+
1039+
do {
1040+
$autoFilter->shiftColumn(Coordinate::stringFromColumnIndex($endColRef), Coordinate::stringFromColumnIndex($toColRef));
1041+
--$endColRef;
1042+
--$toColRef;
1043+
} while ($startColRef <= $endColRef);
1044+
}
1045+
1046+
private function adjustAutoFilterDelete(int $startCol, int $numberOfColumns, int $rangeEnd, AutoFilter $autoFilter): void
1047+
{
1048+
// For delete, we shuffle from beginning to end to avoid overwriting
1049+
$startColID = Coordinate::stringFromColumnIndex($startCol);
1050+
$toColID = Coordinate::stringFromColumnIndex($startCol + $numberOfColumns);
1051+
$endColID = Coordinate::stringFromColumnIndex($rangeEnd + 1);
1052+
1053+
do {
1054+
$autoFilter->shiftColumn($startColID, $toColID);
1055+
++$startColID;
1056+
++$toColID;
1057+
} while ($startColID !== $endColID);
1058+
}
1059+
1060+
private function duplicateStylesByColumn(Worksheet $worksheet, int $beforeColumn, int $beforeRow, int $highestRow, int $numberOfColumns): void
1061+
{
1062+
$beforeColumnName = Coordinate::stringFromColumnIndex($beforeColumn - 1);
1063+
for ($i = $beforeRow; $i <= $highestRow - 1; ++$i) {
1064+
// Style
1065+
$coordinate = $beforeColumnName . $i;
1066+
if ($worksheet->cellExists($coordinate)) {
1067+
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
1068+
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
1069+
$worksheet->getConditionalStyles($coordinate) : false;
1070+
for ($j = $beforeColumn; $j <= $beforeColumn - 1 + $numberOfColumns; ++$j) {
1071+
$worksheet->getCellByColumnAndRow($j, $i)->setXfIndex($xfIndex);
1072+
if ($conditionalStyles) {
1073+
$cloned = [];
1074+
foreach ($conditionalStyles as $conditionalStyle) {
1075+
$cloned[] = clone $conditionalStyle;
1076+
}
1077+
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($j) . $i, $cloned);
1078+
}
1079+
}
1080+
}
1081+
}
1082+
}
1083+
1084+
private function duplicateStylesByRow(Worksheet $worksheet, int $beforeColumn, int $beforeRow, string $highestColumn, int $numberOfRows): void
1085+
{
1086+
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
1087+
for ($i = $beforeColumn; $i <= $highestColumnIndex; ++$i) {
1088+
// Style
1089+
$coordinate = Coordinate::stringFromColumnIndex($i) . ($beforeRow - 1);
1090+
if ($worksheet->cellExists($coordinate)) {
1091+
$xfIndex = $worksheet->getCell($coordinate)->getXfIndex();
1092+
$conditionalStyles = $worksheet->conditionalStylesExists($coordinate) ?
1093+
$worksheet->getConditionalStyles($coordinate) : false;
1094+
for ($j = $beforeRow; $j <= $beforeRow - 1 + $numberOfRows; ++$j) {
1095+
$worksheet->getCell(Coordinate::stringFromColumnIndex($i) . $j)->setXfIndex($xfIndex);
1096+
if ($conditionalStyles) {
1097+
$cloned = [];
1098+
foreach ($conditionalStyles as $conditionalStyle) {
1099+
$cloned[] = clone $conditionalStyle;
1100+
}
1101+
$worksheet->setConditionalStyles(Coordinate::stringFromColumnIndex($i) . $j, $cloned);
1102+
}
1103+
}
1104+
}
1105+
}
1106+
}
1107+
10511108
/**
10521109
* __clone implementation. Cloning should not be allowed in a Singleton!
10531110
*/

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,25 +2235,25 @@ public function removeColumn($column, $numberOfColumns = 1)
22352235
$highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
22362236
$pColumnIndex = Coordinate::columnIndexFromString($column);
22372237

2238-
if ($pColumnIndex > $highestColumnIndex) {
2239-
return $this;
2240-
}
2241-
22422238
$holdColumnDimensions = $this->removeColumnDimensions($pColumnIndex, $numberOfColumns);
22432239

22442240
$column = Coordinate::stringFromColumnIndex($pColumnIndex + $numberOfColumns);
22452241
$objReferenceHelper = ReferenceHelper::getInstance();
22462242
$objReferenceHelper->insertNewBefore($column . '1', -$numberOfColumns, 0, $this);
22472243

2244+
$this->columnDimensions = $holdColumnDimensions;
2245+
2246+
if ($pColumnIndex > $highestColumnIndex) {
2247+
return $this;
2248+
}
2249+
22482250
$maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
22492251

22502252
for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $numberOfColumns); $c < $n; ++$c) {
22512253
$this->getCellCollection()->removeColumn($highestColumn);
22522254
$highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
22532255
}
22542256

2255-
$this->columnDimensions = $holdColumnDimensions;
2256-
22572257
$this->garbageCollect();
22582258

22592259
return $this;

0 commit comments

Comments
 (0)