diff --git a/src/PhpSpreadsheet/Cell/Cell.php b/src/PhpSpreadsheet/Cell/Cell.php index e721673be9..e9e41d7c7b 100644 --- a/src/PhpSpreadsheet/Cell/Cell.php +++ b/src/PhpSpreadsheet/Cell/Cell.php @@ -71,6 +71,9 @@ class Cell */ private $formulaAttributes; + /** @var IgnoredErrors */ + private $ignoredErrors; + /** * Update the cell into the cell collection. * @@ -119,6 +122,7 @@ public function __construct($value, ?string $dataType, Worksheet $worksheet) } elseif (self::getValueBinder()->bindValue($this, $value) === false) { throw new Exception('Value could not be bound to cell.'); } + $this->ignoredErrors = new IgnoredErrors(); } /** @@ -796,4 +800,9 @@ public function __toString() { return (string) $this->getValue(); } + + public function getIgnoredErrors(): IgnoredErrors + { + return $this->ignoredErrors; + } } diff --git a/src/PhpSpreadsheet/Cell/IgnoredErrors.php b/src/PhpSpreadsheet/Cell/IgnoredErrors.php new file mode 100644 index 0000000000..ee4b515620 --- /dev/null +++ b/src/PhpSpreadsheet/Cell/IgnoredErrors.php @@ -0,0 +1,66 @@ +numberStoredAsText = $value; + + return $this; + } + + public function getNumberStoredAsText(): bool + { + return $this->numberStoredAsText; + } + + public function setFormula(bool $value): self + { + $this->formula = $value; + + return $this; + } + + public function getFormula(): bool + { + return $this->formula; + } + + public function setTwoDigitTextYear(bool $value): self + { + $this->twoDigitTextYear = $value; + + return $this; + } + + public function getTwoDigitTextYear(): bool + { + return $this->twoDigitTextYear; + } + + public function setEvalError(bool $value): self + { + $this->evalError = $value; + + return $this; + } + + public function getEvalError(): bool + { + return $this->evalError; + } +} diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index 3a76b05b51..310fef74f8 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -956,6 +956,12 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet ++$cIndex; } } + if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) { + foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) { + $ignoredError = self::testSimpleXml($ignoredErrorx); + $this->processIgnoredErrors($ignoredError, $docSheet); + } + } if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) { $protAttr = $xmlSheetNS->sheetProtection->attributes() ?? []; @@ -2263,4 +2269,48 @@ private static function extractPalette(?SimpleXMLElement $sxml): array return $array; } + + private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void + { + $attributes = self::getAttributes($xml); + $sqref = (string) ($attributes['sqref'] ?? ''); + $numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? ''); + $formula = (string) ($attributes['formula'] ?? ''); + $twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? ''); + $evalError = (string) ($attributes['evalError'] ?? ''); + if (!empty($sqref)) { + $explodedSqref = explode(' ', $sqref); + $pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/'; + foreach ($explodedSqref as $sqref1) { + if (preg_match($pattern1, $sqref1, $matches) === 1) { + $firstRow = $matches[2]; + $firstCol = $matches[1]; + if (array_key_exists(3, $matches)) { + $lastCol = $matches[4]; + $lastRow = $matches[5]; + } else { + $lastCol = $firstCol; + $lastRow = $firstRow; + } + ++$lastCol; + for ($row = $firstRow; $row <= $lastRow; ++$row) { + for ($col = $firstCol; $col !== $lastCol; ++$col) { + if ($numberStoredAsText === '1') { + $sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true); + } + if ($formula === '1') { + $sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true); + } + if ($twoDigitTextYear === '1') { + $sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true); + } + if ($evalError === '1') { + $sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true); + } + } + } + } + } + } + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php index 53c4512457..5e453b3d86 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php @@ -18,6 +18,18 @@ class Worksheet extends WriterPart { + /** @var string */ + private $numberStoredAsText = ''; + + /** @var string */ + private $formula = ''; + + /** @var string */ + private $twoDigitTextYear = ''; + + /** @var string */ + private $evalError = ''; + /** * Write worksheet to XML format. * @@ -28,6 +40,10 @@ class Worksheet extends WriterPart */ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable = [], $includeCharts = false) { + $this->numberStoredAsText = ''; + $this->formula = ''; + $this->twoDigitTextYear = ''; + $this->evalError = ''; // Create XML writer $objWriter = null; if ($this->getParentWriter()->getUseDiskCaching()) { @@ -118,6 +134,9 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable // AlternateContent $this->writeAlternateContent($objWriter, $worksheet); + // IgnoredErrors + $this->writeIgnoredErrors($objWriter); + // Table $this->writeTable($objWriter, $worksheet); @@ -131,6 +150,32 @@ public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable return $objWriter->getData(); } + private function writeIgnoredError(XMLWriter $objWriter, bool &$started, string $attr, string $cells): void + { + if ($cells !== '') { + if (!$started) { + $objWriter->startElement('ignoredErrors'); + $started = true; + } + $objWriter->startElement('ignoredError'); + $objWriter->writeAttribute('sqref', substr($cells, 1)); + $objWriter->writeAttribute($attr, '1'); + $objWriter->endElement(); + } + } + + private function writeIgnoredErrors(XMLWriter $objWriter): void + { + $started = false; + $this->writeIgnoredError($objWriter, $started, 'numberStoredAsText', $this->numberStoredAsText); + $this->writeIgnoredError($objWriter, $started, 'formula', $this->formula); + $this->writeIgnoredError($objWriter, $started, 'twoDigitTextYear', $this->twoDigitTextYear); + $this->writeIgnoredError($objWriter, $started, 'evalError', $this->evalError); + if ($started) { + $objWriter->endElement(); + } + } + /** * Write SheetPr. */ @@ -1134,7 +1179,20 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w array_pop($columnsInRow); foreach ($columnsInRow as $column) { // Write cell - $this->writeCell($objWriter, $worksheet, "{$column}{$currentRow}", $aFlippedStringTable); + $coord = "$column$currentRow"; + if ($worksheet->getCell($coord)->getIgnoredErrors()->getNumberStoredAsText()) { + $this->numberStoredAsText .= " $coord"; + } + if ($worksheet->getCell($coord)->getIgnoredErrors()->getFormula()) { + $this->formula .= " $coord"; + } + if ($worksheet->getCell($coord)->getIgnoredErrors()->getTwoDigitTextYear()) { + $this->twoDigitTextYear .= " $coord"; + } + if ($worksheet->getCell($coord)->getIgnoredErrors()->getEvalError()) { + $this->evalError .= " $coord"; + } + $this->writeCell($objWriter, $worksheet, $coord, $aFlippedStringTable); } } diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/IgnoredErrorTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/IgnoredErrorTest.php new file mode 100644 index 0000000000..bb476d6d69 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/IgnoredErrorTest.php @@ -0,0 +1,68 @@ +load(self::FILENAME); + $spreadsheet = $this->writeAndReload($originalSpreadsheet, 'Xlsx'); + $originalSpreadsheet->disconnectWorksheets(); + $sheet = $spreadsheet->getActiveSheet(); + self::assertFalse($sheet->getCell('A1')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheet->getCell('A2')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertFalse($sheet->getCell('A3')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheet->getCell('A4')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertFalse($sheet->getCell('H2')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheet->getCell('H3')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertFalse($sheet->getCell('I2')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheet->getCell('I3')->getIgnoredErrors()->getNumberStoredAsText()); + + self::assertFalse($sheet->getCell('H3')->getIgnoredErrors()->getFormula()); + self::assertFalse($sheet->getCell('D2')->getIgnoredErrors()->getFormula()); + self::assertTrue($sheet->getCell('D3')->getIgnoredErrors()->getFormula()); + + self::assertFalse($sheet->getCell('A11')->getIgnoredErrors()->getTwoDigitTextYear()); + self::assertTrue($sheet->getCell('A12')->getIgnoredErrors()->getTwoDigitTextYear()); + + self::assertFalse($sheet->getCell('C12')->getIgnoredErrors()->getEvalError()); + self::assertTrue($sheet->getCell('C11')->getIgnoredErrors()->getEvalError()); + + $sheetLast = $spreadsheet->getSheetByNameOrThrow('Last'); + self::assertFalse($sheetLast->getCell('D2')->getIgnoredErrors()->getFormula()); + self::assertFalse($sheetLast->getCell('D3')->getIgnoredErrors()->getFormula(), 'prior sheet ignoredErrors shouldn\'t bleed'); + self::assertFalse($sheetLast->getCell('A1')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertFalse($sheetLast->getCell('A2')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheetLast->getCell('A3')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertFalse($sheetLast->getCell('A4')->getIgnoredErrors()->getNumberStoredAsText(), 'prior sheet numberStoredAsText shouldn\'t bleed'); + + $spreadsheet->disconnectWorksheets(); + } + + public function testSetIgnoredError(): void + { + $originalSpreadsheet = new Spreadsheet(); + $originalSheet = $originalSpreadsheet->getActiveSheet(); + $originalSheet->getCell('A1')->setValueExplicit('0', DataType::TYPE_STRING); + $originalSheet->getCell('A2')->setValueExplicit('1', DataType::TYPE_STRING); + $originalSheet->getStyle('A1:A2')->setQuotePrefix(true); + $originalSheet->getCell('A2')->getIgnoredErrors()->setNumberStoredAsText(true); + $spreadsheet = $this->writeAndReload($originalSpreadsheet, 'Xlsx'); + $originalSpreadsheet->disconnectWorksheets(); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('0', $sheet->getCell('A1')->getValue()); + self::assertSame('1', $sheet->getCell('A2')->getValue()); + self::assertFalse($sheet->getCell('A1')->getIgnoredErrors()->getNumberStoredAsText()); + self::assertTrue($sheet->getCell('A2')->getIgnoredErrors()->getNumberStoredAsText()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/XLSX/ignoreerror.xlsx b/tests/data/Reader/XLSX/ignoreerror.xlsx new file mode 100644 index 0000000000..2034bfc932 Binary files /dev/null and b/tests/data/Reader/XLSX/ignoreerror.xlsx differ