diff --git a/docs/topics/recipes.md b/docs/topics/recipes.md index 62260fc6..14c82fe5 100644 --- a/docs/topics/recipes.md +++ b/docs/topics/recipes.md @@ -1114,6 +1114,11 @@ ruleset: $spreadsheet->getActiveSheet()->getCell('B8')->setDataValidation(clone $validation); ``` +Alternatively, one can apply the validation to a range of cells: +```php +$validation->setSqref('B5:B1048576'); +``` + ## Setting a column's width A column's width can be set using the following code: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 099e5104..c997788a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6130,11 +6130,6 @@ parameters: count: 1 path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, string\\|null given\\.$#" - count: 2 - path: src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php - - message: "#^Parameter \\#2 \\$value of method XMLWriter\\:\\:writeAttribute\\(\\) expects string, int given\\.$#" count: 19 diff --git a/src/PhpSpreadsheet/Cell/DataValidation.php b/src/PhpSpreadsheet/Cell/DataValidation.php index dfeb024c..2ff52f32 100644 --- a/src/PhpSpreadsheet/Cell/DataValidation.php +++ b/src/PhpSpreadsheet/Cell/DataValidation.php @@ -478,4 +478,19 @@ class DataValidation } } } + + /** @var ?string */ + private $sqref; + + public function getSqref(): ?string + { + return $this->sqref; + } + + public function setSqref(?string $str): self + { + $this->sqref = $str; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php index 7053a5a5..b699cb57 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php @@ -45,6 +45,7 @@ class DataValidations $docValidation->setPrompt((string) $dataValidation['prompt']); $docValidation->setFormula1((string) $dataValidation->formula1); $docValidation->setFormula2((string) $dataValidation->formula2); + $docValidation->setSqref($range); } } } diff --git a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php index 9b3e47ed..b2bc99f0 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php +++ b/src/PhpSpreadsheet/Reader/Xlsx/SheetViews.php @@ -27,6 +27,7 @@ class SheetViews extends BaseParserClass public function load(): void { + $this->topLeft(); $this->zoomScale(); $this->view(); $this->gridLines(); @@ -74,6 +75,13 @@ class SheetViews extends BaseParserClass } } + private function topLeft(): void + { + if (isset($this->sheetViewAttributes->topLeftCell)) { + $this->worksheet->setTopLeftCell($this->sheetViewAttributes->topLeftCell); + } + } + private function gridLines(): void { if (isset($this->sheetViewAttributes->showGridLines)) { diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index da31fa8b..8dd16df0 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -1966,6 +1966,13 @@ class Worksheet implements IComparable return $this; } + public function setTopLeftCell(string $topLeftCell): self + { + $this->topLeftCell = $topLeftCell; + + return $this; + } + /** * Freeze Pane by using numeric cell coordinates. * diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 58e6c064..98e2ae42 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -85,8 +85,8 @@ class Worksheet extends WriterPart // conditionalFormatting $this->writeConditionalFormatting($objWriter, $pSheet); - // dataValidations - $this->writeDataValidations($objWriter, $pSheet); + // dataValidations moved to end + //$this->writeDataValidations($objWriter, $pSheet); // hyperlinks $this->writeHyperlinks($objWriter, $pSheet); @@ -121,6 +121,8 @@ class Worksheet extends WriterPart // ConditionalFormattingRuleExtensionList // (Must be inserted last. Not insert last, an Excel parse error will occur) $this->writeExtLst($objWriter, $pSheet); + // dataValidations + $this->writeDataValidations($objWriter, $pSheet); $objWriter->endElement(); @@ -143,7 +145,7 @@ class Worksheet extends WriterPart if (!$pSheet->hasCodeName()) { $pSheet->setCodeName($pSheet->getTitle()); } - $objWriter->writeAttribute('codeName', $pSheet->getCodeName()); + self::writeAttributeNotNull($objWriter, 'codeName', $pSheet->getCodeName()); } $autoFilterRange = $pSheet->getAutoFilter()->getRange(); if (!empty($autoFilterRange)) { @@ -247,6 +249,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('rightToLeft', 'true'); } + $topLeftCell = $pSheet->getTopLeftCell(); $activeCell = $pSheet->getActiveCell(); $sqref = $pSheet->getSelectedCells(); @@ -258,8 +261,6 @@ class Worksheet extends WriterPart --$xSplit; --$ySplit; - $topLeftCell = $pSheet->getTopLeftCell(); - // pane $pane = 'topRight'; $objWriter->startElement('pane'); @@ -270,7 +271,7 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('ySplit', $ySplit); $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft'; } - $objWriter->writeAttribute('topLeftCell', $topLeftCell); + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); $objWriter->writeAttribute('activePane', $pane); $objWriter->writeAttribute('state', 'frozen'); $objWriter->endElement(); @@ -284,6 +285,8 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('pane', 'bottomLeft'); $objWriter->endElement(); } + } else { + self::writeAttributeNotNull($objWriter, 'topLeftCell', $topLeftCell); } // Selection @@ -467,6 +470,13 @@ class Worksheet extends WriterPart } } + private static function writeAttributeNotNull(XMLWriter $objWriter, string $attr, ?string $val): void + { + if ($val !== null) { + $objWriter->writeAttribute($attr, $val); + } + } + private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void { if ($condition) { @@ -680,11 +690,16 @@ class Worksheet extends WriterPart // Write data validations? if (!empty($dataValidationCollection)) { $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection); - $objWriter->startElement('dataValidations'); + $objWriter->startElement('extLst'); + $objWriter->startElement('ext'); + $objWriter->writeAttribute('uri', '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}'); + $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main'); + $objWriter->startElement('x14:dataValidations'); $objWriter->writeAttribute('count', count($dataValidationCollection)); + $objWriter->writeAttribute('xmlns:xm', 'http://schemas.microsoft.com/office/excel/2006/main'); foreach ($dataValidationCollection as $coordinate => $dv) { - $objWriter->startElement('dataValidation'); + $objWriter->startElement('x14:dataValidation'); if ($dv->getType() != '') { $objWriter->writeAttribute('type', $dv->getType()); @@ -717,19 +732,24 @@ class Worksheet extends WriterPart $objWriter->writeAttribute('prompt', $dv->getPrompt()); } - $objWriter->writeAttribute('sqref', $coordinate); - if ($dv->getFormula1() !== '') { - $objWriter->writeElement('formula1', $dv->getFormula1()); + $objWriter->startElement('x14:formula1'); + $objWriter->writeElement('xm:f', $dv->getFormula1()); + $objWriter->endElement(); } if ($dv->getFormula2() !== '') { - $objWriter->writeElement('formula2', $dv->getFormula2()); + $objWriter->startElement('x14:formula2'); + $objWriter->writeElement('xm:f', $dv->getFormula2()); + $objWriter->endElement(); } + $objWriter->writeElement('xm:sqref', $dv->getSqref() ?? $coordinate); $objWriter->endElement(); } - $objWriter->endElement(); + $objWriter->endElement(); // dataValidations + $objWriter->endElement(); // ext + $objWriter->endElement(); // extLst } } diff --git a/tests/PhpSpreadsheetTests/Cell/DataValidator2Test.php b/tests/PhpSpreadsheetTests/Cell/DataValidator2Test.php new file mode 100644 index 00000000..9153a5b8 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Cell/DataValidator2Test.php @@ -0,0 +1,75 @@ +load('tests/data/Reader/XLSX/issue.1432b.xlsx'); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('H1', $sheet->getTopLeftCell()); + self::assertSame('K3', $sheet->getSelectedCells()); + + $testCell = $sheet->getCell('K3'); + $validation = $testCell->getDataValidation(); + self::assertSame(DataValidation::TYPE_LIST, $validation->getType()); + + $testCell = $sheet->getCell('R2'); + $validation = $testCell->getDataValidation(); + self::assertSame(DataValidation::TYPE_LIST, $validation->getType()); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $sheet = $reloadedSpreadsheet->getActiveSheet(); + + $cell = 'K3'; + $testCell = $sheet->getCell($cell); + $validation = $testCell->getDataValidation(); + self::assertSame(DataValidation::TYPE_LIST, $validation->getType()); + $testCell->setValue('Y'); + self::assertTrue($testCell->hasValidValue(), 'K3 other sheet has valid value'); + $testCell = $sheet->getCell($cell); + $testCell->setValue('X'); + self::assertFalse($testCell->hasValidValue(), 'K3 other sheet has invalid value'); + + $cell = 'J2'; + $testCell = $sheet->getCell($cell); + $validation = $testCell->getDataValidation(); + self::assertSame(DataValidation::TYPE_LIST, $validation->getType()); + $testCell = $sheet->getCell($cell); + $testCell->setValue('GBP'); + self::assertTrue($testCell->hasValidValue(), 'J2 other sheet has valid value'); + $testCell = $sheet->getCell($cell); + $testCell->setValue('XYZ'); + self::assertFalse($testCell->hasValidValue(), 'J2 other sheet has invalid value'); + + $cell = 'R2'; + $testCell = $sheet->getCell($cell); + $validation = $testCell->getDataValidation(); + self::assertSame(DataValidation::TYPE_LIST, $validation->getType()); + $testCell->setValue('ListItem2'); + self::assertTrue($testCell->hasValidValue(), 'R2 same sheet has valid value'); + $testCell = $sheet->getCell($cell); + $testCell->setValue('ListItem99'); + self::assertFalse($testCell->hasValidValue(), 'R2 same sheet has invalid value'); + + $styles = $sheet->getConditionalStyles('I1:I1048576'); + self::assertCount(1, $styles); + $style = $styles[0]; + self::assertSame(Conditional::CONDITION_CELLIS, $style->getConditionType()); + self::assertSame(Conditional::OPERATOR_BETWEEN, $style->getOperatorType()); + $conditions = $style->getConditions(); + self::assertSame('10', $conditions[0]); + self::assertSame('20', $conditions[1]); + self::assertSame('FF70AD47', $style->getStyle()->getFill()->getEndColor()->getARGB()); + + $spreadsheet->disconnectWorksheets(); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/issue.1432b.xlsx b/tests/data/Reader/XLSX/issue.1432b.xlsx new file mode 100644 index 00000000..0762c24d Binary files /dev/null and b/tests/data/Reader/XLSX/issue.1432b.xlsx differ