diff --git a/src/PhpSpreadsheet/Cell.php b/src/PhpSpreadsheet/Cell.php index 0263853dce..2ed9b47f6b 100644 --- a/src/PhpSpreadsheet/Cell.php +++ b/src/PhpSpreadsheet/Cell.php @@ -948,6 +948,86 @@ public static function extractAllCellReferencesInRange($pRange) return array_values($sortKeys); } + /** + * Convert an associative array of single cell coordinates to values to an associative array + * of cell ranges to values. Only adjacent cell coordinates with the same + * value will be merged. If the value is an object, it must implement the method getHashCode(). + * + * For example, this function converts: + * + * [ 'A1' => 'x', 'A2' => 'x', 'A3' => 'x', 'A4' => 'y' ] + * + * to: + * + * [ 'A1:A3' => 'x', 'A4' => 'y' ] + * + * @param array $pCoordCollection associative array mapping coordinates to values + * + * @return array associative array mapping coordinate ranges to valuea + */ + public static function mergeRangesInCollection(array $pCoordCollection) + { + $hashedValues = []; + + foreach ($pCoordCollection as $coord => $value) { + list($column, $row) = self::coordinateFromString($coord); + $row = (int) (ltrim($row, '$')); + $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value); + + if (!isset($hashedValues[$hashCode])) { + $hashedValues[$hashCode] = (object) [ + 'value' => $value, + 'col' => $column, + 'rows' => [$row], + ]; + } else { + $hashedValues[$hashCode]->rows[] = $row; + } + } + + $mergedCoordCollection = []; + ksort($hashedValues); + + foreach ($hashedValues as $hashedValue) { + sort($hashedValue->rows); + $rowStart = null; + $rowEnd = null; + $ranges = []; + + foreach ($hashedValue->rows as $row) { + if ($rowStart === null) { + $rowStart = $row; + $rowEnd = $row; + } elseif ($rowEnd === $row - 1) { + $rowEnd = $row; + } else { + if ($rowStart == $rowEnd) { + $ranges[] = $hashedValue->col . $rowStart; + } else { + $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; + } + + $rowStart = $row; + $rowEnd = $row; + } + } + + if ($rowStart !== null) { + if ($rowStart == $rowEnd) { + $ranges[] = $hashedValue->col . $rowStart; + } else { + $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd; + } + } + + foreach ($ranges as $range) { + $mergedCoordCollection[$range] = $hashedValue->value; + } + } + + return $mergedCoordCollection; + } + /** * Compare 2 cells. * diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index f5bf2bf751..7d906064f9 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -567,6 +567,7 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh // Write data validations? if (!empty($dataValidationCollection)) { + $dataValidationCollection = Cell::mergeRangesInCollection($dataValidationCollection); $objWriter->startElement('dataValidations'); $objWriter->writeAttribute('count', count($dataValidationCollection)); diff --git a/tests/PhpSpreadsheetTests/CellTest.php b/tests/PhpSpreadsheetTests/CellTest.php index 118978f985..3a7d429ecb 100644 --- a/tests/PhpSpreadsheetTests/CellTest.php +++ b/tests/PhpSpreadsheetTests/CellTest.php @@ -300,4 +300,20 @@ public function providerExtractAllCellReferencesInRange() { return require 'data/CellExtractAllCellReferencesInRange.php'; } + + /** + * @dataProvider providerMergeRangesInCollection + * + * @param mixed $expectedResult + */ + public function testMergeRangesInCollection($expectedResult, ...$args) + { + $result = Cell::mergeRangesInCollection(...$args); + $this->assertEquals($expectedResult, $result); + } + + public function providerMergeRangesInCollection() + { + return require 'data/CellMergeRangesInCollection.php'; + } } diff --git a/tests/data/CellMergeRangesInCollection.php b/tests/data/CellMergeRangesInCollection.php new file mode 100644 index 0000000000..9cb711259b --- /dev/null +++ b/tests/data/CellMergeRangesInCollection.php @@ -0,0 +1,58 @@ + 'x', + 'A4' => 'y', + ], + [ + 'A1' => 'x', + 'A2' => 'x', + 'A3' => 'x', + 'A4' => 'y', + ], + ], + [ + [ + 'A1:A4' => 'x', + 'A6:A7' => 'x', + 'A9' => 'x', + ], + [ + 'A7' => 'x', + 'A1' => 'x', + 'A4' => 'x', + 'A6' => 'x', + 'A2' => 'x', + 'A9' => 'x', + 'A3' => 'x', + ], + ], + [ + [ + 'A1:A3' => 'x', + 'B1:B3' => 'x', + ], + [ + 'A1' => 'x', + 'B3' => 'x', + 'A2' => 'x', + 'B2' => 'x', + 'A3' => 'x', + 'B1' => 'x', + ], + ], + [ + [ + 'A1' => 'x', + 'A2' => 'y', + 'A3' => 'z', + ], + [ + 'A1' => 'x', + 'A2' => 'y', + 'A3' => 'z', + ], + ], +];