From 2761773b3dddf62b9ccfb3919b7b7c935935408a Mon Sep 17 00:00:00 2001 From: Bill Blume Date: Mon, 31 Jul 2017 11:36:54 -0700 Subject: [PATCH] Merge data-validation collections to reduce the final file size Closes #131 Closes #193 --- CHANGELOG.md | 2 + src/PhpSpreadsheet/Cell.php | 80 ++++++++++++++++++++ src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 1 + tests/PhpSpreadsheetTests/CellTest.php | 16 ++++ tests/data/CellMergeRangesInCollection.php | 58 ++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 tests/data/CellMergeRangesInCollection.php diff --git a/CHANGELOG.md b/CHANGELOG.md index f7179613..94204fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- Merge data-validations to reduce written worksheet size - @billblume [#131](https://github.com/PHPOffice/PhpSpreadSheet/issues/131) + ### Fixed diff --git a/src/PhpSpreadsheet/Cell.php b/src/PhpSpreadsheet/Cell.php index 0263853d..2ed9b47f 100644 --- a/src/PhpSpreadsheet/Cell.php +++ b/src/PhpSpreadsheet/Cell.php @@ -948,6 +948,86 @@ class Cell 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 a1a97865..dfaea5a0 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -565,6 +565,7 @@ class Worksheet extends WriterPart // 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 118978f9..3a7d429e 100644 --- a/tests/PhpSpreadsheetTests/CellTest.php +++ b/tests/PhpSpreadsheetTests/CellTest.php @@ -300,4 +300,20 @@ class CellTest extends PHPUnit_Framework_TestCase { 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 00000000..9cb71125 --- /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', + ], + ], +];