diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php
index 896b4e9c..51cf3c52 100644
--- a/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -1698,27 +1698,33 @@ class Worksheet implements IComparable
{
// Uppercase coordinate
$range = strtoupper($range);
+ // Convert 'A:C' to 'A1:C1048576'
+ $range = self::pregReplace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $range);
+ // Convert '1:3' to 'A1:XFD3'
+ $range = self::pregReplace('/^(\\d+):(\\d+)$/', 'A${1}:XFD${2}', $range);
- if (strpos($range, ':') !== false) {
+ if (preg_match('/^([A-Z]+)(\\d+):([A-Z]+)(\\d+)$/', $range, $matches) === 1) {
$this->mergeCells[$range] = $range;
-
- // make sure cells are created
-
- // get the cells in the range
- $aReferences = Coordinate::extractAllCellReferencesInRange($range);
+ $firstRow = (int) $matches[2];
+ $lastRow = (int) $matches[4];
+ $firstColumn = $matches[1];
+ $lastColumn = $matches[3];
+ $firstColumnIndex = Coordinate::columnIndexFromString($firstColumn);
+ $lastColumnIndex = Coordinate::columnIndexFromString($lastColumn);
+ $numberRows = $lastRow - $firstRow;
+ $numberColumns = $lastColumnIndex - $firstColumnIndex;
// create upper left cell if it does not already exist
- $upperLeft = $aReferences[0];
+ $upperLeft = "$firstColumn$firstRow";
if (!$this->cellExists($upperLeft)) {
$this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL);
}
// Blank out the rest of the cells in the range (if they exist)
- $count = count($aReferences);
- for ($i = 1; $i < $count; ++$i) {
- if ($this->cellExists($aReferences[$i])) {
- $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL);
- }
+ if ($numberRows > $numberColumns) {
+ $this->clearMergeCellsByColumn($firstColumn, $lastColumn, $firstRow, $lastRow, $upperLeft);
+ } else {
+ $this->clearMergeCellsByRow($firstColumn, $lastColumnIndex, $firstRow, $lastRow, $upperLeft);
}
} else {
throw new Exception('Merge must be set on a range of cells.');
@@ -1727,6 +1733,47 @@ class Worksheet implements IComparable
return $this;
}
+ private function clearMergeCellsByColumn(string $firstColumn, string $lastColumn, int $firstRow, int $lastRow, string $upperLeft): void
+ {
+ foreach ($this->getColumnIterator($firstColumn, $lastColumn) as $column) {
+ $iterator = $column->getCellIterator($firstRow);
+ $iterator->setIterateOnlyExistingCells(true);
+ foreach ($iterator as $cell) {
+ if ($cell !== null) {
+ $row = $cell->getRow();
+ if ($row > $lastRow) {
+ break;
+ }
+ $thisCell = $cell->getColumn() . $row;
+ if ($upperLeft !== $thisCell) {
+ $cell->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+ }
+ }
+ }
+ }
+
+ private function clearMergeCellsByRow(string $firstColumn, int $lastColumnIndex, int $firstRow, int $lastRow, string $upperLeft): void
+ {
+ foreach ($this->getRowIterator($firstRow, $lastRow) as $row) {
+ $iterator = $row->getCellIterator($firstColumn);
+ $iterator->setIterateOnlyExistingCells(true);
+ foreach ($iterator as $cell) {
+ if ($cell !== null) {
+ $column = $cell->getColumn();
+ $columnIndex = Coordinate::columnIndexFromString($column);
+ if ($columnIndex > $lastColumnIndex) {
+ break;
+ }
+ $thisCell = $column . $cell->getRow();
+ if ($upperLeft !== $thisCell) {
+ $cell->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+ }
+ }
+ }
+ }
+
/**
* Set merge on a cell range by using numeric cell coordinates.
*
diff --git a/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php b/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php
index 6a710436..f6e98474 100644
--- a/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/MergedCellTest.php
@@ -3,43 +3,43 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
+use PhpOffice\PhpSpreadsheet\Exception as SpreadException;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PHPUnit\Framework\TestCase;
class MergedCellTest extends TestCase
{
- /**
- * @var Spreadsheet
- */
- protected $spreadSheet;
-
- protected function setUp(): void
- {
- $this->spreadSheet = new Spreadsheet();
-
- $dataSheet = $this->spreadSheet->getActiveSheet();
- $dataSheet->setCellValue('A1', 1.1);
- $dataSheet->setCellValue('A2', 2.2);
- $dataSheet->mergeCells('A2:A4');
- $dataSheet->setCellValue('A5', 3.3);
- }
-
/**
* @param mixed $expectedResult
*
- * @dataProvider providerWorksheetFormulae
+ * @dataProvider providerWorksheetFormulaeColumns
*/
- public function testMergedCellBehaviour(string $formula, $expectedResult): void
+ public function testMergedCellColumns(string $formula, $expectedResult): void
{
- $worksheet = $this->spreadSheet->getActiveSheet();
+ $spreadSheet = new Spreadsheet();
+
+ $dataSheet = $spreadSheet->getActiveSheet();
+ $dataSheet->setCellValue('A5', 3.3);
+ $dataSheet->setCellValue('A3', 3.3);
+ $dataSheet->setCellValue('A2', 2.2);
+ $dataSheet->setCellValue('A1', 1.1);
+ $dataSheet->setCellValue('B2', 2.2);
+ $dataSheet->setCellValue('B1', 1.1);
+ $dataSheet->setCellValue('C2', 4.4);
+ $dataSheet->setCellValue('C1', 3.3);
+ $dataSheet->mergeCells('A2:A4');
+ $dataSheet->mergeCells('B:B');
+ $worksheet = $spreadSheet->getActiveSheet();
$worksheet->setCellValue('A7', $formula);
$result = $worksheet->getCell('A7')->getCalculatedValue();
self::assertSame($expectedResult, $result);
+ $spreadSheet->disconnectWorksheets();
}
- public function providerWorksheetFormulae(): array
+ public function providerWorksheetFormulaeColumns(): array
{
return [
['=SUM(A1:A5)', 6.6],
@@ -48,6 +48,74 @@ class MergedCellTest extends TestCase
['=SUM(A3:A4)', 0],
['=A2+A3+A4', 2.2],
['=A2/A3', Functions::DIV0()],
+ ['=SUM(B1:C2)', 8.8],
];
}
+
+ /**
+ * @param mixed $expectedResult
+ *
+ * @dataProvider providerWorksheetFormulaeRows
+ */
+ public function testMergedCellRows(string $formula, $expectedResult): void
+ {
+ $spreadSheet = new Spreadsheet();
+
+ $dataSheet = $spreadSheet->getActiveSheet();
+ $dataSheet->setCellValue('A1', 1.1);
+ $dataSheet->setCellValue('B1', 2.2);
+ $dataSheet->setCellValue('C1', 3.3);
+ $dataSheet->setCellValue('E1', 3.3);
+ $dataSheet->setCellValue('A2', 1.1);
+ $dataSheet->setCellValue('B2', 2.2);
+ $dataSheet->setCellValue('A3', 3.3);
+ $dataSheet->setCellValue('B3', 4.4);
+ $dataSheet->mergeCells('B1:D1');
+ $dataSheet->mergeCells('A2:B2');
+ $worksheet = $spreadSheet->getActiveSheet();
+
+ $worksheet->setCellValue('A7', $formula);
+
+ $result = $worksheet->getCell('A7')->getCalculatedValue();
+ self::assertSame($expectedResult, $result);
+ $spreadSheet->disconnectWorksheets();
+ }
+
+ public function providerWorksheetFormulaeRows(): array
+ {
+ return [
+ ['=SUM(A1:E1)', 6.6],
+ ['=COUNT(A1:E1)', 3],
+ ['=COUNTA(A1:E1)', 3],
+ ['=SUM(C1:D1)', 0],
+ ['=B1+C1+D1', 2.2],
+ ['=B1/C1', Functions::DIV0()],
+ ['=SUM(A2:B3)', 8.8],
+ ];
+ }
+
+ private function setBadRange(Worksheet $sheet, string $range): void
+ {
+ try {
+ $sheet->mergeCells($range);
+ self::fail("Expected invalid merge range $range");
+ } catch (SpreadException $e) {
+ self::assertSame('Merge must be set on a range of cells.', $e->getMessage());
+ }
+ }
+
+ public function testMergedBadRange(): void
+ {
+ $spreadSheet = new Spreadsheet();
+
+ $dataSheet = $spreadSheet->getActiveSheet();
+ $this->setBadRange($dataSheet, 'B1');
+ $this->setBadRange($dataSheet, 'Invalid');
+ $this->setBadRange($dataSheet, '1');
+ $this->setBadRange($dataSheet, 'C');
+ $this->setBadRange($dataSheet, 'B1:C');
+ $this->setBadRange($dataSheet, 'B:C2');
+
+ $spreadSheet->disconnectWorksheets();
+ }
}
diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php
new file mode 100644
index 00000000..6b090fe8
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue2501Test.php
@@ -0,0 +1,70 @@
+', $data);
+ }
+ $file = 'zip://';
+ $file .= self::$testbook;
+ $file .= '#xl/worksheets/sheet2.xml';
+ $data = file_get_contents($file);
+ // confirm that file contains expected merged ranges
+ if ($data === false) {
+ self::fail('Unable to read file');
+ } else {
+ self::assertStringContainsString('', $data);
+ }
+ }
+
+ public function testIssue2501(): void
+ {
+ // Merged cell range specified as 1:1"
+ $filename = self::$testbook;
+ $reader = IOFactory::createReader('Xlsx');
+ $spreadsheet = $reader->load($filename);
+ $sheet = $spreadsheet->getSheetByName('Columns');
+ $expected = [
+ 'A1:A1048576',
+ 'B1:D1048576',
+ 'E2:E4',
+ ];
+ if ($sheet === null) {
+ self::fail('Unable to find sheet Columns');
+ } else {
+ self::assertSame($expected, array_values($sheet->getMergeCells()));
+ }
+ $sheet = $spreadsheet->getSheetByName('Rows');
+ $expected = [
+ 'A1:XFD1',
+ 'A2:XFD4',
+ 'B5:D5',
+ ];
+ if ($sheet === null) {
+ self::fail('Unable to find sheet Rows');
+ } else {
+ self::assertSame($expected, array_values($sheet->getMergeCells()));
+ }
+
+ $spreadsheet->disconnectWorksheets();
+ }
+}
diff --git a/tests/data/Reader/XLSX/issue.2501.b.xlsx b/tests/data/Reader/XLSX/issue.2501.b.xlsx
new file mode 100644
index 00000000..fddf197b
Binary files /dev/null and b/tests/data/Reader/XLSX/issue.2501.b.xlsx differ