From 1f05ce01981a32cb27f200d5ce76927de037a2ac Mon Sep 17 00:00:00 2001 From: Owen Leibman Date: Sat, 6 Nov 2021 01:24:04 -0700 Subject: [PATCH] Support Data Validations in More Versions of Excel Attempt to deal with #2368, this time for good. Some deleted code was accidentally restored just before release 19, causing errors in spreadsheets with Data Validations. PR #2369 removed the duplicated code, and the fix was confirmed in current versions of Excel for Windows, Google sheets, and other versions of Excel. However, there were problems reported in earlier version of Excel for Windows, and some, versions of Excel for Mac, not all but including a recent one. This change, which is simpler than the original (no need for extLst) fix for DataValidations, is tested with Excel 2007 and Excel 2003 as well as more recent versions. I do not have a Mac on which to test. --- src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php | 68 ++++++------------- .../Writer/Xlsx/Issue2368Test.php | 37 ++++++++++ 2 files changed, 59 insertions(+), 46 deletions(-) create mode 100644 tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2368Test.php diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 72b13fc5e4..b8db44ff0b 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -4,6 +4,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Cell\DataValidation; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Settings; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; @@ -121,8 +122,6 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $pStringTable // ConditionalFormattingRuleExtensionList // (Must be inserted last. Not insert last, an Excel parse error will occur) $this->writeExtLst($objWriter, $worksheet); - // dataValidations - $this->writeDataValidations($objWriter, $worksheet); $objWriter->endElement(); @@ -473,6 +472,13 @@ private static function writeAttributeNotNull(XMLWriter $objWriter, string $attr } } + private static function writeAttributeNotNullString(XMLWriter $objWriter, string $attr, string $val): void + { + if ($val !== '') { + $objWriter->writeAttribute($attr, $val); + } + } + private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void { if ($condition) { @@ -673,8 +679,6 @@ private function writeConditionalFormatting(XMLWriter $objWriter, Phpspreadsheet /** * Write DataValidations. - * - * @param XMLWriter $objWriter XML Writer */ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $worksheet): void { @@ -684,66 +688,38 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh // Write data validations? if (!empty($dataValidationCollection)) { $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection); - $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->startElement('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('x14:dataValidation'); - - if ($dv->getType() != '') { - $objWriter->writeAttribute('type', $dv->getType()); - } - - if ($dv->getErrorStyle() != '') { - $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle()); - } - - if ($dv->getOperator() != '') { - $objWriter->writeAttribute('operator', $dv->getOperator()); - } + $objWriter->startElement('dataValidation'); + self::writeAttributeNotNullString($objWriter, 'type', $dv->getType()); + self::writeAttributeNotNullString($objWriter, 'errorStyle', $dv->getErrorStyle()); + self::writeAttributeNotNullString($objWriter, 'operator', $dv->getOperator()); $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0')); - // showDropDown is really hideDropDown Excel renders as true = hide, false = show $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0')); $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0')); $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0')); - if ($dv->getErrorTitle() !== '') { - $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle()); - } - if ($dv->getError() !== '') { - $objWriter->writeAttribute('error', $dv->getError()); - } - if ($dv->getPromptTitle() !== '') { - $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle()); - } - if ($dv->getPrompt() !== '') { - $objWriter->writeAttribute('prompt', $dv->getPrompt()); - } + self::writeAttributeNotNullString($objWriter, 'errorTitle', $dv->getErrorTitle()); + self::writeAttributeNotNullString($objWriter, 'error', $dv->getError()); + self::writeAttributeNotNullString($objWriter, 'promptTitle', $dv->getPromptTitle()); + self::writeAttributeNotNullString($objWriter, 'prompt', $dv->getPrompt()); + + $objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate); if ($dv->getFormula1() !== '') { - $objWriter->startElement('x14:formula1'); - $objWriter->writeElement('xm:f', $dv->getFormula1()); - $objWriter->endElement(); + $objWriter->writeElement('formula1', $dv->getFormula1()); } if ($dv->getFormula2() !== '') { - $objWriter->startElement('x14:formula2'); - $objWriter->writeElement('xm:f', $dv->getFormula2()); - $objWriter->endElement(); + $objWriter->writeElement('formula2', $dv->getFormula2()); } - $objWriter->writeElement('xm:sqref', $dv->getSqref() ?? $coordinate); $objWriter->endElement(); } - $objWriter->endElement(); // dataValidations - $objWriter->endElement(); // ext - $objWriter->endElement(); // extLst + $objWriter->endElement(); } } diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2368Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2368Test.php new file mode 100644 index 0000000000..5a30bdf2a2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue2368Test.php @@ -0,0 +1,37 @@ +getActiveSheet(); + $validation = $sheet->getDataValidation('A1:A10'); + $validation->setType(DataValidation::TYPE_LIST); + $validation->setShowDropDown(true); + $validation->setFormula1('"Option 1, Option 2"'); + + $outputFilename = File::temporaryFilename(); + $writer = new Writer($spreadsheet); + $writer->save($outputFilename); + $zipfile = "zip://$outputFilename#xl/worksheets/sheet1.xml"; + $contents = file_get_contents($zipfile); + unlink($outputFilename); + $spreadsheet->disconnectWorksheets(); + if ($contents === false) { + self::fail('Unable to open file'); + } else { + self::assertSame(0, substr_count($contents, '')); + self::assertSame(2, substr_count($contents, 'dataValidations')); // start and end tags + } + } +}