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

#131: Merge data validations on write #193

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Ignore inlineStr type if formula element exists - @ncrypthic [#570](https://github.com/PHPOffice/PHPExcel/issues/570)
- Excel 2007 Reader freezes because of conditional formatting - @rentalhost [#575](https://github.com/PHPOffice/PHPExcel/issues/575)
- Readers will now parse files containing worksheet titles over 31 characters [#176](https://github.com/PHPOffice/PhpSpreadsheet/pull/176)
- Merge data-validations to reduce written worksheet size - @billblume [#131](https://github.com/PHPOffice/PhpSpreadSheet/issues/131)

### General

80 changes: 80 additions & 0 deletions src/PhpSpreadsheet/Cell.php
Original file line number Diff line number Diff line change
@@ -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.
*
1 change: 1 addition & 0 deletions src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
Original file line number Diff line number Diff line change
@@ -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));

16 changes: 16 additions & 0 deletions tests/PhpSpreadsheetTests/CellTest.php
Original file line number Diff line number Diff line change
@@ -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';
}
}
58 changes: 58 additions & 0 deletions tests/data/CellMergeRangesInCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

return [
[
[
'A1:A3' => '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',
],
],
];