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.
This commit is contained in:
oleibman 2021-11-14 10:06:46 -08:00 committed by GitHub
parent f831f48b71
commit 4ac0c47ac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 89 additions and 20 deletions

View File

@ -460,6 +460,7 @@ class DataValidation
$this->error .
$this->promptTitle .
$this->prompt .
$this->sqref .
__CLASS__
);
}

View File

@ -85,6 +85,9 @@ class Worksheet extends WriterPart
// conditionalFormatting
$this->writeConditionalFormatting($objWriter, $worksheet);
// dataValidations
$this->writeDataValidations($objWriter, $worksheet);
// hyperlinks
$this->writeHyperlinks($objWriter, $worksheet);
@ -118,8 +121,6 @@ class Worksheet extends WriterPart
// 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();
@ -681,16 +682,11 @@ class Worksheet extends WriterPart
// 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');
$objWriter->startElement('dataValidation');
if ($dv->getType() != '') {
$objWriter->writeAttribute('type', $dv->getType());
@ -705,7 +701,6 @@ class Worksheet extends WriterPart
}
$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'));
@ -723,24 +718,19 @@ class Worksheet extends WriterPart
$objWriter->writeAttribute('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();
}
}

View File

@ -0,0 +1,78 @@
<?php
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as Writer;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
class Issue2368Test extends AbstractFunctional
{
public function testBoolWrite(): void
{
// DataValidations were incorrectly written twice.
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->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, '<extLst>'));
self::assertSame(2, substr_count($contents, 'dataValidations')); // start and end tags
}
}
public function testMultipleRange(): void
{
// DataValidations which were identical except for sqref were incorrectly merged.
$filename = 'tests/data/Writer/XLSX/issue.2368new.xlsx';
$reader = IOFactory::createReader('Xlsx');
$spreadsheet = $reader->load($filename);
$sheet = $spreadsheet->getActiveSheet();
$validations = $sheet->getDataValidationCollection();
/** @var string[] */
$ranges = [];
foreach ($validations as $validation) {
$ranges[] = $validation->getSqref();
}
self::assertContains('A1:A5', $ranges);
self::assertContains('A10:A14', $ranges);
self::assertContains('A20:A24', $ranges);
self::assertSame('"yes,no"', $sheet->getCell('A3')->getDataValidation()->getFormula1());
self::assertSame('"yes,no"', $sheet->getCell('A10')->getDataValidation()->getFormula1());
self::assertSame('"yes,no"', $sheet->getCell('A24')->getDataValidation()->getFormula1());
$reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx');
$spreadsheet->disconnectWorksheets();
$sheet2 = $reloadedSpreadsheet->getActiveSheet();
$validation2 = $sheet2->getDataValidationCollection();
/** @var string[] */
$range2 = [];
foreach ($validation2 as $validation) {
$range2[] = $validation->getSqref();
}
self::assertContains('A1:A5', $range2);
self::assertContains('A10:A14', $range2);
self::assertContains('A20:A24', $range2);
self::assertSame('"yes,no"', $sheet2->getCell('A3')->getDataValidation()->getFormula1());
self::assertSame('"yes,no"', $sheet2->getCell('A10')->getDataValidation()->getFormula1());
self::assertSame('"yes,no"', $sheet2->getCell('A24')->getDataValidation()->getFormula1());
$reloadedSpreadsheet->disconnectWorksheets();
}
}

Binary file not shown.