Skip to content

Commit 1fe51bf

Browse files
committed
WIP Breaking Change for DataValidation
Fix #797 (marked stale in 2018, but now reopened). Fix #4091. DataValidation is specified in the Xml at the sheet level rather than the cell level. PhpSpreadsheet, however, currently requires a cell and a data validation object for each cell in a range. As a consequence, it may exhaust memory allocating objects that are never really needed. If it reads a spreadsheet which applies DataValidation to an entire column, the reader creates a million cells and a million DataValidation objects. An additional problem is that, when it saves the spreadsheet, it creates a row entry for each of the million rows even if they contain no data. The code is changed to locate DataValidation for a cell by running the DataValidation list to see if the Sqref for any entry matches the cell in question. This eliminates the need to require a DataValidation entry for each cell; indeed, it eliminates the need to create a cell just because it is subject to DataValidation. Initial tests are very encouraging. The memory-exhausting spreadsheet from 797 required over 1GB of memory. With this change in place, that is reduced to 6MB, and DataValidation works as expected for all cells in the range. This change is a work in progress. It needs formal tests. I need to see how it works with inserting or removing rows and columns. I need to see how Excel handles conflicting entries. My early tests indicate that it at least doesn't do anything irrational (like claiming corruption) when dealing with conflicts. It appears that, if I have a DataValidation applied to column A, and another applied to cell A5, whichever appears first in the Xml is the ultimate arbiter of what rule applies to A5. If this change is merged, it will be a breaking range. It will require a new major release (probably PhpSpreadsheet 4.0), and will not happen for at least a couple of months, and probably later.
1 parent f37b119 commit 1fe51bf

File tree

3 files changed

+48
-25
lines changed

3 files changed

+48
-25
lines changed

src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
44

55
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6+
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
67
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
78
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
89
use SimpleXMLElement;
@@ -38,28 +39,23 @@ public function load(): void
3839
$range = strtoupper((string) $dataValidation['sqref']);
3940
$rangeSet = explode(' ', $range);
4041
foreach ($rangeSet as $range) {
41-
$stRange = $this->worksheet->shrinkRangeToFit($range);
42-
43-
// Extract all cell references in $range
44-
foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $reference) {
45-
// Create validation
46-
$docValidation = $this->worksheet->getCell($reference)->getDataValidation();
47-
$docValidation->setType((string) $dataValidation['type']);
48-
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
49-
$docValidation->setOperator((string) $dataValidation['operator']);
50-
$docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN));
51-
// showDropDown is inverted (works as hideDropDown if true)
52-
$docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN));
53-
$docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN));
54-
$docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN));
55-
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
56-
$docValidation->setError((string) $dataValidation['error']);
57-
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
58-
$docValidation->setPrompt((string) $dataValidation['prompt']);
59-
$docValidation->setFormula1(Xlsx::replacePrefixes((string) $dataValidation->formula1));
60-
$docValidation->setFormula2(Xlsx::replacePrefixes((string) $dataValidation->formula2));
61-
$docValidation->setSqref($range);
62-
}
42+
$docValidation = new DataValidation();
43+
$docValidation->setType((string) $dataValidation['type']);
44+
$docValidation->setErrorStyle((string) $dataValidation['errorStyle']);
45+
$docValidation->setOperator((string) $dataValidation['operator']);
46+
$docValidation->setAllowBlank(filter_var($dataValidation['allowBlank'], FILTER_VALIDATE_BOOLEAN));
47+
// showDropDown is inverted (works as hideDropDown if true)
48+
$docValidation->setShowDropDown(!filter_var($dataValidation['showDropDown'], FILTER_VALIDATE_BOOLEAN));
49+
$docValidation->setShowInputMessage(filter_var($dataValidation['showInputMessage'], FILTER_VALIDATE_BOOLEAN));
50+
$docValidation->setShowErrorMessage(filter_var($dataValidation['showErrorMessage'], FILTER_VALIDATE_BOOLEAN));
51+
$docValidation->setErrorTitle((string) $dataValidation['errorTitle']);
52+
$docValidation->setError((string) $dataValidation['error']);
53+
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
54+
$docValidation->setPrompt((string) $dataValidation['prompt']);
55+
$docValidation->setFormula1(Xlsx::replacePrefixes((string) $dataValidation->formula1));
56+
$docValidation->setFormula2(Xlsx::replacePrefixes((string) $dataValidation->formula2));
57+
$docValidation->setSqref($range);
58+
$this->worksheet->setDataValidation($range, $docValidation);
6359
}
6460
}
6561
}

src/PhpSpreadsheet/Worksheet/Worksheet.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3265,6 +3265,14 @@ public function getDataValidation(string $cellCoordinate): DataValidation
32653265
if (isset($this->dataValidationCollection[$cellCoordinate])) {
32663266
return $this->dataValidationCollection[$cellCoordinate];
32673267
}
3268+
foreach ($this->dataValidationCollection as $dataValidation) {
3269+
$sqref = $dataValidation->getSqref() ?? '';
3270+
if (str_contains($sqref, ':')) {
3271+
if (Coordinate::coordinateIsInsideRange($sqref, $cellCoordinate)) {
3272+
return $dataValidation;
3273+
}
3274+
}
3275+
}
32683276

32693277
// else create data validation
32703278
$this->dataValidationCollection[$cellCoordinate] = new DataValidation();
@@ -3297,7 +3305,17 @@ public function setDataValidation(string $cellCoordinate, ?DataValidation $dataV
32973305
*/
32983306
public function dataValidationExists(string $coordinate): bool
32993307
{
3300-
return isset($this->dataValidationCollection[$coordinate]);
3308+
if (isset($this->dataValidationCollection[$coordinate])) {
3309+
return true;
3310+
}
3311+
foreach ($this->dataValidationCollection as $dataValidation) {
3312+
$sqref = $dataValidation->getSqref() ?? '';
3313+
if (str_contains($sqref, ':')) {
3314+
if (Coordinate::coordinateIsInsideRange($sqref, $coordinate)) {
3315+
return true;
3316+
}
3317+
}
3318+
}
33013319
}
33023320

33033321
/**

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,8 +1313,17 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
13131313
$cellsByRow = [];
13141314
foreach ($worksheet->getCoordinates() as $coordinate) {
13151315
[$column, $row] = Coordinate::coordinateFromString($coordinate);
1316-
$cellsByRow[$row] = $cellsByRow[$row] ?? '';
1317-
$cellsByRow[$row] .= "{$column},";
1316+
if (!isset($cellsByRow[$row])) {
1317+
$pCell = $worksheet->getCell("$column$row");
1318+
$xfi = $pCell->getXfIndex();
1319+
$cellValue = $pCell->getValue();
1320+
$writeValue = $cellValue !== '' && $cellValue !== null;
1321+
if (!empty($xfi) || $writeValue) {
1322+
$cellsByRow[$row] = "{$column},";
1323+
}
1324+
} else {
1325+
$cellsByRow[$row] .= "{$column},";
1326+
}
13181327
}
13191328

13201329
$currentRow = 0;

0 commit comments

Comments
 (0)