diff --git a/CHANGELOG.md b/CHANGELOG.md index 5887b78f..faf7ae4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added - Support for chart fill color - @CrazyBite [#158](https://github.com/PHPOffice/PhpSpreadsheet/pull/158) -- Support for read Hyperlink for xml - [@GreatHumorist](https://github.com/GreatHumorist) [#223](https://github.com/PHPOffice/PhpSpreadsheet/pull/223) +- Support for read Hyperlink for xml - @GreatHumorist [#223](https://github.com/PHPOffice/PhpSpreadsheet/pull/223) +- Support for cell value validation according to data validation rules - @SailorMax [#257](https://github.com/PHPOffice/PhpSpreadsheet/pull/257) ### Changed diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index ac14573f..a8b0bf13 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -415,7 +415,19 @@ class Cell } /** - * Does this cell contain a Hyperlink? + * Does this cell contain valid value? + * + * @return bool + */ + public function hasValidValue() + { + $validator = new DataValidator(); + + return $validator->isValid($this); + } + + /** + * Does this cell contain a Hyperlink? * * @throws Exception * diff --git a/src/PhpSpreadsheet/Cell/DataValidator.php b/src/PhpSpreadsheet/Cell/DataValidator.php new file mode 100644 index 00000000..430d81b9 --- /dev/null +++ b/src/PhpSpreadsheet/Cell/DataValidator.php @@ -0,0 +1,77 @@ +hasDataValidation()) { + return true; + } + + $cellValue = $cell->getValue(); + $dataValidation = $cell->getDataValidation(); + + if (!$dataValidation->getAllowBlank() && ($cellValue === null || $cellValue === '')) { + return false; + } + + // TODO: write check on all cases + switch ($dataValidation->getType()) { + case DataValidation::TYPE_LIST: + return $this->isValueInList($cell); + } + + return false; + } + + /** + * Does this cell contain valid value, based on list? + * + * @param Cell $cell Cell to check the value + * + * @return bool + */ + private function isValueInList(Cell $cell) + { + $cellValue = $cell->getValue(); + $dataValidation = $cell->getDataValidation(); + + $formula1 = $dataValidation->getFormula1(); + if (!empty($formula1)) { + // inline values list + if ($formula1[0] === '"') { + return in_array(strtolower($cellValue), explode(',', strtolower(trim($formula1, '"'))), true); + } elseif (strpos($formula1, ':') > 0) { + // values list cells + $matchFormula = '=MATCH(' . $cell->getCoordinate() . ', ' . $formula1 . ', 0)'; + $calculation = Calculation::getInstance($cell->getWorksheet()->getParent()); + + try { + $result = $calculation->calculateFormula($matchFormula, $cell->getCoordinate(), $cell); + + return $result !== Functions::NA(); + } catch (Exception $ex) { + return false; + } + } + } + + return true; + } +} diff --git a/tests/PhpSpreadsheetTests/Cell/DataValidatorTest.php b/tests/PhpSpreadsheetTests/Cell/DataValidatorTest.php new file mode 100644 index 00000000..44d16e7e --- /dev/null +++ b/tests/PhpSpreadsheetTests/Cell/DataValidatorTest.php @@ -0,0 +1,73 @@ +getActiveSheet(); + $testCell = $sheet->getCell('A1'); + + self::assertTrue($testCell->hasValidValue(), 'a cell without any validation data is always valid'); + } + + public function testUnsupportedType() + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $testCell = $sheet->getCell('A1'); + + $validation = $testCell->getDataValidation(); + $validation->setType(DataValidation::TYPE_CUSTOM); + $validation->setAllowBlank(true); + + self::assertFalse($testCell->hasValidValue(), 'cannot assert that value is valid when the validation type is not supported'); + } + + public function testList() + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $testCell = $sheet->getCell('A1'); + + $validation = $testCell->getDataValidation(); + $validation->setType(DataValidation::TYPE_LIST); + + // blank value + $testCell->setValue(''); + $validation->setAllowBlank(true); + self::assertTrue($testCell->hasValidValue(), 'cell can be empty'); + $validation->setAllowBlank(false); + self::assertFalse($testCell->hasValidValue(), 'cell can not be empty'); + + // inline list + $validation->setFormula1('"yes,no"'); + $testCell->setValue('foo'); + self::assertFalse($testCell->hasValidValue(), "cell value ('foo') is not allowed"); + $testCell->setValue('yes'); + self::assertTrue($testCell->hasValidValue(), "cell value ('yes') has to be allowed"); + + // list from cells + $sheet->getCell('B1')->setValue(5); + $sheet->getCell('B2')->setValue(6); + $sheet->getCell('B3')->setValue(7); + $testCell = $sheet->getCell('A1'); // redefine $testCell, because it has broken coordinates after using other cells + $validation->setFormula1('B1:B3'); + $testCell->setValue('10'); + self::assertFalse($testCell->hasValidValue(), "cell value ('10') is not allowed"); + $testCell = $sheet->getCell('A1'); // redefine $testCell, because it has broken coordinates after using other cells + $testCell->setValue('5'); + self::assertTrue($testCell->hasValidValue(), "cell value ('5') has to be allowed"); + + $testCell = $sheet->getCell('A1'); // redefine $testCell, because it has broken coordinates after using other cells + $validation->setFormula1('broken : cell : coordinates'); + + self::assertFalse($testCell->hasValidValue(), 'invalid formula should not throw exceptions'); + } +}