Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request - repeatSourceRange Functionality in Worksheet #3895

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions src/PhpSpreadsheet/Worksheet/Worksheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -3613,4 +3613,96 @@ public function copyCells(string $fromCell, string $toCells, bool $copyStyle = t
}
}
}

/**
* Copy a range area, with merged cells applied to another location.
* Acts similarly to Excel copy/paste merged areas to specific cell.
*
* @param string $sourceRange cell range, e.g. C3:C10
* @param int $repetitions the number of times to repeat the source range
* @param int $groupSize the number of cells in the source range to repeat
*/
public function repeatSourceRange(string $sourceRange, int $repetitions = 2, int $groupSize = 2): void
{
// Get the start and end coordinates of the source range
[$sourceStart, $sourceEnd] = Coordinate::rangeBoundaries($sourceRange);
$sourceStartColumnIndex = $sourceStart[0];
$sourceEndColumnIndex = $sourceEnd[0];

// Find the merged cells within the source range
$mergedCellRanges = $this->getMergeCellsFromCollection($sourceStart, $sourceEnd);

// Copy the cells and merge them in the new locations
$groupCount = 0;
$rowOffset = 0;

for ($i = 1; $i <= $repetitions + 1; ++$i) {
if ($i % $groupSize != 1 && $i > $groupSize) {
$offset = ($i - 1) % $groupSize * ($sourceEndColumnIndex - $sourceStartColumnIndex + 1);
} else {
$offset = ($i - 1) % $groupSize * ($sourceEndColumnIndex - $sourceStartColumnIndex + 2);
}

if ($i % $groupSize != 1) {
$offset += $groupCount;
}

// Copy the styles
foreach ($this->rangeToArray($sourceRange, false, true, true, true) as $row => $columns) {
foreach ($columns as $column => $cell) {
$coordinate = $column . $row;
$destinationCoordinate = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($column) + $offset) . ($row + $rowOffset);
$this->duplicateStyle($this->getStyle($coordinate), $destinationCoordinate);
}
}

// Merge the cells
$this->mergeCellsOfTheRanges($mergedCellRanges, $offset, $rowOffset);

// Increase group count and row offset after every groupSize repetitions
if ($i % $groupSize == 0) {
$rowOffset += $sourceEnd[1] - $sourceStart[1] + 2; // 2 is the number of rows to skip
++$groupCount;
}
}
}

/**
* Will get the coordinates to the Start/End Cell with Column and Row indexes.
*
* @param array $sourceStart Source Start column/row index
* @param array $sourceEnd Source End column/row index
*/
private function getMergeCellsFromCollection(array $sourceStart, array $sourceEnd): array
{
$mergeCells = [];
foreach ($this->getMergeCells() as $mergedCellRange) {
[$mergedStart, $mergedEnd] = Coordinate::rangeBoundaries($mergedCellRange);
if ($mergedStart[0] >= $sourceStart[0] && $mergedEnd[0] <= $sourceEnd[0] && $mergedStart[1] >= $sourceStart[1] && $mergedEnd[1] <= $sourceEnd[1]) {
$mergedCellRanges[] = $mergedCellRange;
}
}

return $mergeCells;
}

/**
* Will get the coordinates to the start/end cell with column and row indexes.
*
* @param array $mergedCellRanges array of merged cell ranges
* @param int $offset separation between the source and destination ranges by column
* @param int $rowOffset separation of group index with cell collection by row
*/
private function mergeCellsOfTheRanges(array $mergedCellRanges, int $offset, int $rowOffset): void
{
foreach ($mergedCellRanges as $mergedCellRange) {
[$mergedStart, $mergedEnd] = Coordinate::rangeBoundaries($mergedCellRange);
$mergedStartColumnIndex = $mergedStart[0];
$mergedEndColumnIndex = $mergedEnd[0];
$destinationStartColumn = Coordinate::stringFromColumnIndex($mergedStartColumnIndex + $offset);
$destinationEndColumn = Coordinate::stringFromColumnIndex($mergedEndColumnIndex + $offset);
$destinationRange = $destinationStartColumn . ($mergedStart[1] + $rowOffset) . ':' . $destinationEndColumn . ($mergedEnd[1] + $rowOffset);
$this->mergeCells($destinationRange);
}
}
}
35 changes: 35 additions & 0 deletions tests/PhpSpreadsheetTests/Worksheet/WorksheetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -625,4 +625,39 @@ public static function rangeToArrayProvider(): array
],
];
}

public function testRepeatSourceRangeBasic(): void
{
$spreadsheet = new Spreadsheet(); // Create a Spreadsheet
$worksheet = $spreadsheet->getActiveSheet(); // Get the active Worksheet

// Add cells to the worksheet
$worksheet->getCell('A1')->setValue('Test1');
$worksheet->getCell('B1')->setValue('Test2');
$worksheet->getCell('A2')->setValue('Test3');
$worksheet->getCell('B2')->setValue('Test4');
$worksheet->getCell('A3')->setValue('Test5');
$worksheet->getCell('B3')->setValue('Test6');

// Add merged cells to the worksheet
$worksheet->mergeCells('A1:B1');
$worksheet->mergeCells('A2:B2');
$worksheet->mergeCells('A3:B3');

$sourceRange = 'A1:B3';
$repetitions = 2;
$groupSize = 1;

$worksheet->repeatSourceRange($sourceRange, $repetitions, $groupSize);

// Assert that cells are copied correctly
self::assertEquals('Test1', $worksheet->getCell('A1')->getValue());
self::assertEquals('Test3', $worksheet->getCell('A2')->getValue());
self::assertEquals('Test5', $worksheet->getCell('A3')->getValue());

// Assert that merged cells are copied correctly
self::assertContains('A1:B1', $worksheet->getMergeCells());
self::assertContains('A2:B2', $worksheet->getMergeCells());
self::assertContains('A3:B3', $worksheet->getMergeCells());
}
}
Loading