Skip to content

Commit 4ac0c47

Browse files
authored
Support Data Validations in More Versions of Excel (#2377)
* 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. * Multiple Identical Data Validation Lists Using the same Data Validation List in multiple places on a worksheet caused them all to be merged into the same range. This was because sqref was not part of the hash code; it is now, avoiding this problem. * Must Write Data Validations Before Hyperlinks See discussion in #2389.
1 parent f831f48 commit 4ac0c47

File tree

4 files changed

+89
-20
lines changed

4 files changed

+89
-20
lines changed

src/PhpSpreadsheet/Cell/DataValidation.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ public function getHashCode()
460460
$this->error .
461461
$this->promptTitle .
462462
$this->prompt .
463+
$this->sqref .
463464
__CLASS__
464465
);
465466
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable
8585
// conditionalFormatting
8686
$this->writeConditionalFormatting($objWriter, $worksheet);
8787

88+
// dataValidations
89+
$this->writeDataValidations($objWriter, $worksheet);
90+
8891
// hyperlinks
8992
$this->writeHyperlinks($objWriter, $worksheet);
9093

@@ -118,8 +121,6 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable
118121
// ConditionalFormattingRuleExtensionList
119122
// (Must be inserted last. Not insert last, an Excel parse error will occur)
120123
$this->writeExtLst($objWriter, $worksheet);
121-
// dataValidations
122-
$this->writeDataValidations($objWriter, $worksheet);
123124

124125
$objWriter->endElement();
125126

@@ -681,16 +682,11 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh
681682
// Write data validations?
682683
if (!empty($dataValidationCollection)) {
683684
$dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection);
684-
$objWriter->startElement('extLst');
685-
$objWriter->startElement('ext');
686-
$objWriter->writeAttribute('uri', '{CCE6A557-97BC-4b89-ADB6-D9C93CAAB3DF}');
687-
$objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
688-
$objWriter->startElement('x14:dataValidations');
685+
$objWriter->startElement('dataValidations');
689686
$objWriter->writeAttribute('count', count($dataValidationCollection));
690-
$objWriter->writeAttribute('xmlns:xm', 'http://schemas.microsoft.com/office/excel/2006/main');
691687

692688
foreach ($dataValidationCollection as $coordinate => $dv) {
693-
$objWriter->startElement('x14:dataValidation');
689+
$objWriter->startElement('dataValidation');
694690

695691
if ($dv->getType() != '') {
696692
$objWriter->writeAttribute('type', $dv->getType());
@@ -705,7 +701,6 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh
705701
}
706702

707703
$objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
708-
// showDropDown is really hideDropDown Excel renders as true = hide, false = show
709704
$objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
710705
$objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
711706
$objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
@@ -723,24 +718,19 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh
723718
$objWriter->writeAttribute('prompt', $dv->getPrompt());
724719
}
725720

721+
$objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate);
722+
726723
if ($dv->getFormula1() !== '') {
727-
$objWriter->startElement('x14:formula1');
728-
$objWriter->writeElement('xm:f', $dv->getFormula1());
729-
$objWriter->endElement();
724+
$objWriter->writeElement('formula1', $dv->getFormula1());
730725
}
731726
if ($dv->getFormula2() !== '') {
732-
$objWriter->startElement('x14:formula2');
733-
$objWriter->writeElement('xm:f', $dv->getFormula2());
734-
$objWriter->endElement();
727+
$objWriter->writeElement('formula2', $dv->getFormula2());
735728
}
736-
$objWriter->writeElement('xm:sqref', $dv->getSqref() ?? $coordinate);
737729

738730
$objWriter->endElement();
739731
}
740732

741-
$objWriter->endElement(); // dataValidations
742-
$objWriter->endElement(); // ext
743-
$objWriter->endElement(); // extLst
733+
$objWriter->endElement();
744734
}
745735
}
746736

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
4+
5+
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
6+
use PhpOffice\PhpSpreadsheet\IOFactory;
7+
use PhpOffice\PhpSpreadsheet\Shared\File;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer;
10+
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
11+
12+
class Issue2368Test extends AbstractFunctional
13+
{
14+
public function testBoolWrite(): void
15+
{
16+
// DataValidations were incorrectly written twice.
17+
$spreadsheet = new Spreadsheet();
18+
$sheet = $spreadsheet->getActiveSheet();
19+
$validation = $sheet->getDataValidation('A1:A10');
20+
$validation->setType(DataValidation::TYPE_LIST);
21+
$validation->setShowDropDown(true);
22+
$validation->setFormula1('"Option 1, Option 2"');
23+
24+
$outputFilename = File::temporaryFilename();
25+
$writer = new Writer($spreadsheet);
26+
$writer->save($outputFilename);
27+
$zipfile = "zip://$outputFilename#xl/worksheets/sheet1.xml";
28+
$contents = file_get_contents($zipfile);
29+
unlink($outputFilename);
30+
$spreadsheet->disconnectWorksheets();
31+
if ($contents === false) {
32+
self::fail('Unable to open file');
33+
} else {
34+
self::assertSame(0, substr_count($contents, '<extLst>'));
35+
self::assertSame(2, substr_count($contents, 'dataValidations')); // start and end tags
36+
}
37+
}
38+
39+
public function testMultipleRange(): void
40+
{
41+
// DataValidations which were identical except for sqref were incorrectly merged.
42+
$filename = 'tests/data/Writer/XLSX/issue.2368new.xlsx';
43+
$reader = IOFactory::createReader('Xlsx');
44+
$spreadsheet = $reader->load($filename);
45+
$sheet = $spreadsheet->getActiveSheet();
46+
$validations = $sheet->getDataValidationCollection();
47+
/** @var string[] */
48+
$ranges = [];
49+
foreach ($validations as $validation) {
50+
$ranges[] = $validation->getSqref();
51+
}
52+
self::assertContains('A1:A5', $ranges);
53+
self::assertContains('A10:A14', $ranges);
54+
self::assertContains('A20:A24', $ranges);
55+
self::assertSame('"yes,no"', $sheet->getCell('A3')->getDataValidation()->getFormula1());
56+
self::assertSame('"yes,no"', $sheet->getCell('A10')->getDataValidation()->getFormula1());
57+
self::assertSame('"yes,no"', $sheet->getCell('A24')->getDataValidation()->getFormula1());
58+
59+
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
60+
$spreadsheet->disconnectWorksheets();
61+
62+
$sheet2 = $reloadedSpreadsheet->getActiveSheet();
63+
$validation2 = $sheet2->getDataValidationCollection();
64+
/** @var string[] */
65+
$range2 = [];
66+
foreach ($validation2 as $validation) {
67+
$range2[] = $validation->getSqref();
68+
}
69+
self::assertContains('A1:A5', $range2);
70+
self::assertContains('A10:A14', $range2);
71+
self::assertContains('A20:A24', $range2);
72+
self::assertSame('"yes,no"', $sheet2->getCell('A3')->getDataValidation()->getFormula1());
73+
self::assertSame('"yes,no"', $sheet2->getCell('A10')->getDataValidation()->getFormula1());
74+
self::assertSame('"yes,no"', $sheet2->getCell('A24')->getDataValidation()->getFormula1());
75+
76+
$reloadedSpreadsheet->disconnectWorksheets();
77+
}
78+
}
9.68 KB
Binary file not shown.

0 commit comments

Comments
 (0)