diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d60ca306b..68d957ade0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed - Refactor Helper/Html. [PR #4359](https://github.com/PHPOffice/PhpSpreadsheet/pull/4359) +- Better handling of defined names on sheets whose titles include apostrophes. [Issue #4356](https://github.com/PHPOffice/PhpSpreadsheet/issues/4356) [Issue #4362](https://github.com/PHPOffice/PhpSpreadsheet/issues/4362) [Issue #4376](https://github.com/PHPOffice/PhpSpreadsheet/issues/4376) [PR #4360](https://github.com/PHPOffice/PhpSpreadsheet/pull/4360) ## 2025-02-08 - 4.0.0 diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 45807e5772..665c04f107 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4408,7 +4408,9 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool if ($rangeWS1 !== '') { $rangeWS1 .= '!'; } - $rangeSheetRef = trim($rangeSheetRef, "'"); + if (str_starts_with($rangeSheetRef, "'")) { + $rangeSheetRef = Worksheet::unApostrophizeTitle($rangeSheetRef); + } [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true); if ($rangeWS2 !== '') { $rangeWS2 .= '!'; @@ -4766,18 +4768,18 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell } } if (str_contains($operand1Data['reference'] ?? '', '!')) { - [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true); + [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true, true); } else { $sheet1 = ($pCellWorksheet !== null) ? $pCellWorksheet->getTitle() : ''; } $sheet1 ??= ''; - [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true); + [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true, true); if (empty($sheet2)) { $sheet2 = $sheet1; } - if (trim($sheet1, "'") === trim($sheet2, "'")) { + if ($sheet1 === $sheet2) { if ($operand1Data['reference'] === null && $cell !== null) { if (is_array($operand1Data['value'])) { $operand1Data['reference'] = $cell->getCoordinate(); @@ -5495,7 +5497,7 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet = $worksheetName = $worksheet->getTitle(); if (str_contains($range, '!')) { - [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true); $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } @@ -5557,7 +5559,7 @@ public function extractNamedRange(string &$range = 'A1', ?Worksheet $worksheet = if ($worksheet !== null) { if (str_contains($range, '!')) { - [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true); + [$worksheetName, $range] = Worksheet::extractSheetTitle($range, true, true); $worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName); } diff --git a/src/PhpSpreadsheet/Calculation/Information/Value.php b/src/PhpSpreadsheet/Calculation/Information/Value.php index 49361ef066..18274651f2 100644 --- a/src/PhpSpreadsheet/Calculation/Information/Value.php +++ b/src/PhpSpreadsheet/Calculation/Information/Value.php @@ -45,7 +45,7 @@ public static function isRef(mixed $value, ?Cell $cell = null): bool $cellValue = Functions::trimTrailingRange($value); if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/ui', $cellValue) === 1) { - [$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true); + [$worksheet, $cellValue] = Worksheet::extractSheetTitle($cellValue, true, true); if (!empty($worksheet) && $cell->getWorksheet()->getParentOrThrow()->getSheetByName($worksheet) === null) { return false; } diff --git a/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php b/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php index 2bccab8da6..83ea458a20 100644 --- a/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php +++ b/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php @@ -15,7 +15,7 @@ public static function single(string $cellReference, Cell $cell): mixed { $worksheet = $cell->getWorksheet(); - [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true); + [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true, true); if (preg_match('/^([$]?[a-z]{1,3})([$]?([0-9]{1,7})):([$]?[a-z]{1,3})([$]?([0-9]{1,7}))$/i', "$referenceCellCoordinate", $matches) === 1) { $ourRow = $cell->getRow(); $firstRow = (int) $matches[3]; @@ -44,7 +44,7 @@ public static function anchorArray(string $cellReference, Cell $cell): array|str //$coordinate = $cell->getCoordinate(); $worksheet = $cell->getWorksheet(); - [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true); + [$referenceWorksheetName, $referenceCellCoordinate] = Worksheet::extractSheetTitle($cellReference, true, true); $referenceCell = ($referenceWorksheetName === '') ? $worksheet->getCell((string) $referenceCellCoordinate) : $worksheet->getParentOrThrow() diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php index 191144bfd1..21c2030cd0 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php @@ -61,8 +61,7 @@ public static function extractWorksheet(string $cellAddress, Cell $cell): array { $sheetName = ''; if (str_contains($cellAddress, '!')) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true); } $worksheet = ($sheetName !== '') diff --git a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php index 260ccc3a59..9d201ff22f 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; use PhpOffice\PhpSpreadsheet\Cell\Cell; use PhpOffice\PhpSpreadsheet\Cell\Coordinate; +use PhpOffice\PhpSpreadsheet\Worksheet\Validations; use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Offset @@ -55,6 +56,10 @@ public static function OFFSET(?string $cellAddress = null, mixed $rows = 0, mixe if (!is_object($cell)) { return ExcelError::REF(); } + $sheet = $cell->getParent()?->getParent(); // worksheet + if ($sheet !== null) { + $cellAddress = Validations::definedNameToCoordinate($cellAddress, $sheet); + } [$cellAddress, $worksheet] = self::extractWorksheet($cellAddress, $cell); @@ -62,12 +67,11 @@ public static function OFFSET(?string $cellAddress = null, mixed $rows = 0, mixe if (strpos($cellAddress, ':')) { [$startCell, $endCell] = explode(':', $cellAddress); } - [$startCellColumn, $startCellRow] = Coordinate::coordinateFromString($startCell); - [$endCellColumn, $endCellRow] = Coordinate::coordinateFromString($endCell); + [$startCellColumn, $startCellRow] = Coordinate::indexesFromString($startCell); + [, $endCellRow, $endCellColumn] = Coordinate::indexesFromString($endCell); $startCellRow += $rows; - $startCellColumn = Coordinate::columnIndexFromString($startCellColumn) - 1; - $startCellColumn += $columns; + $startCellColumn += $columns - 1; if (($startCellRow <= 0) || ($startCellColumn < 0)) { return ExcelError::REF(); @@ -103,8 +107,7 @@ private static function extractWorksheet(?string $cellAddress, Cell $cell): arra $sheetName = ''; if (str_contains($cellAddress, '!')) { - [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true); - $sheetName = trim($sheetName, "'"); + [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true, true); } $worksheet = ($sheetName !== '') diff --git a/src/PhpSpreadsheet/Chart/DataSeriesValues.php b/src/PhpSpreadsheet/Chart/DataSeriesValues.php index 70f90bf78c..6da4bc4aa9 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -446,7 +446,7 @@ public function refresh(Worksheet $worksheet, bool $flatten = true): void } unset($dataValue); } else { - [$worksheet, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true); + [, $cellRange] = Worksheet::extractSheetTitle($this->dataSource, true); $dimensions = Coordinate::rangeDimension(str_replace('$', '', $cellRange ?? '')); if (($dimensions[0] == 1) || ($dimensions[1] == 1)) { $this->dataValues = Functions::flattenArray($newDataValues); diff --git a/src/PhpSpreadsheet/Reader/Gnumeric.php b/src/PhpSpreadsheet/Reader/Gnumeric.php index ed81efb224..0a9274b039 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -517,8 +517,8 @@ private function processDefinedNames(?SimpleXMLElement $gnmXML): void continue; } - [$worksheetName] = Worksheet::extractSheetTitle($value, true); - $worksheetName = trim($worksheetName, "'"); + $value = str_replace("\\'", "''", $value); + [$worksheetName] = Worksheet::extractSheetTitle($value, true, true); $worksheet = $this->spreadsheet->getSheetByName($worksheetName); // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet if ($worksheet !== null) { diff --git a/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php b/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php index a99e3ea743..713ea550d0 100644 --- a/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php +++ b/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php @@ -60,7 +60,7 @@ protected function readDefinedExpressions(DOMElement $workbookData): void */ private function addDefinedName(string $baseAddress, string $definedName, string $value): void { - [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true); + [$sheetReference] = Worksheet::extractSheetTitle($baseAddress, true, true); $worksheet = $this->spreadsheet->getSheetByName($sheetReference); // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet if ($worksheet !== null) { diff --git a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php index aeda44aa22..7e24e7f11d 100644 --- a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php +++ b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php @@ -588,8 +588,8 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads // $range should look like one of these // Foo!$C$7:$J$66 // Bar!$A$1:$IV$2 - $explodes = Worksheet::extractSheetTitle($range, true); - $sheetName = trim($explodes[0], "'"); + $explodes = Worksheet::extractSheetTitle($range, true, true); + $sheetName = (string) $explodes[0]; if (!str_contains($explodes[1], ':')) { $explodes[1] = $explodes[1] . ':' . $explodes[1]; } @@ -617,8 +617,9 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads // Sheet!$A$1:$B$65536 // Sheet!$A$1:$IV$2 if (str_contains($range, '!')) { - $explodes = Worksheet::extractSheetTitle($range, true); - if ($docSheet = $xls->spreadsheet->getSheetByName($explodes[0])) { + $explodes = Worksheet::extractSheetTitle($range, true, true); + $docSheet = $xls->spreadsheet->getSheetByName($explodes[0]); + if ($docSheet) { $extractedRange = $explodes[1]; $extractedRange = str_replace('$', '', $extractedRange); @@ -646,11 +647,9 @@ protected function loadSpreadsheetFromFile2(string $filename, Xls $xls): Spreads /** @var non-empty-string $formula */ $formula = $definedName['formula']; if (str_contains($formula, '!')) { - $explodes = Worksheet::extractSheetTitle($formula, true); - if ( - ($docSheet = $xls->spreadsheet->getSheetByName($explodes[0])) - || ($docSheet = $xls->spreadsheet->getSheetByName(trim($explodes[0], "'"))) - ) { + $explodes = Worksheet::extractSheetTitle($formula, true, true); + $docSheet = $xls->spreadsheet->getSheetByName($explodes[0]); + if ($docSheet) { $extractedRange = $explodes[1]; $localOnly = ($definedName['scope'] === 0) ? false : true; diff --git a/src/PhpSpreadsheet/Reader/Xlsx.php b/src/PhpSpreadsheet/Reader/Xlsx.php index e7cd0b7119..4b5e893e05 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1819,11 +1819,10 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet $definedNameValueParts = preg_split("/[ ,](?=([^']*'[^']*')*[^']*$)/miuU", $extractedRange); if (is_array($definedNameValueParts)) { // Extract sheet name - [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true); - $extractedSheetName = trim((string) $extractedSheetName, "'"); + [$extractedSheetName] = Worksheet::extractSheetTitle((string) $definedNameValueParts[0], true, true); // Locate sheet - $locatedSheet = $excel->getSheetByName($extractedSheetName); + $locatedSheet = $excel->getSheetByName("$extractedSheetName"); } } diff --git a/src/PhpSpreadsheet/Style/Style.php b/src/PhpSpreadsheet/Style/Style.php index 68ca39e06e..022a49ac75 100644 --- a/src/PhpSpreadsheet/Style/Style.php +++ b/src/PhpSpreadsheet/Style/Style.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Exception; use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; class Style extends Supervisor { @@ -189,7 +190,8 @@ public function applyFromArray(array $styleArray, bool $advancedBorders = true): // Uppercase coordinate and strip any Worksheet reference from the selected range $pRange = strtoupper($pRange); if (str_contains($pRange, '!')) { - $pRangeWorksheet = StringHelper::strToUpper(trim(substr($pRange, 0, (int) strrpos($pRange, '!')), "'")); + $pRangeWorksheet = StringHelper::strToUpper(substr($pRange, 0, (int) strrpos($pRange, '!'))); + $pRangeWorksheet = Worksheet::unApostrophizeTitle($pRangeWorksheet); if ($pRangeWorksheet !== '' && StringHelper::strToUpper($this->getActiveSheet()->getTitle()) !== $pRangeWorksheet) { throw new Exception('Invalid Worksheet for specified Range'); } diff --git a/src/PhpSpreadsheet/Worksheet/Validations.php b/src/PhpSpreadsheet/Worksheet/Validations.php index 75de218955..4a8720dbb1 100644 --- a/src/PhpSpreadsheet/Worksheet/Validations.php +++ b/src/PhpSpreadsheet/Worksheet/Validations.php @@ -2,6 +2,7 @@ namespace PhpOffice\PhpSpreadsheet\Worksheet; +use Composer\Pcre\Preg; use PhpOffice\PhpSpreadsheet\Cell\AddressRange; use PhpOffice\PhpSpreadsheet\Cell\CellAddress; use PhpOffice\PhpSpreadsheet\Cell\CellRange; @@ -45,7 +46,7 @@ public static function validateCellOrCellRange(AddressRange|CellAddress|int|stri if (is_string($cellRange) || is_numeric($cellRange)) { // Convert a single column reference like 'A' to 'A:A', // a single row reference like '1' to '1:1' - $cellRange = (string) preg_replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange); + $cellRange = Preg::replace('/^([A-Z]+|\d+)$/', '${1}:${1}', (string) $cellRange); } elseif (is_object($cellRange) && $cellRange instanceof CellAddress) { $cellRange = new CellRange($cellRange, $cellRange); } @@ -62,7 +63,7 @@ public static function validateCellOrCellRange(AddressRange|CellAddress|int|stri */ public static function convertWholeRowColumn(?string $addressRange): string { - return (string) preg_replace( + return Preg::replace( ['/^([A-Z]+):([A-Z]+)$/i', '/^(\d+):(\d+)$/'], [self::SETMAXROW, self::SETMAXCOL], $addressRange ?? '' @@ -114,11 +115,11 @@ public static function definedNameToCoordinate(string $coordinate, Worksheet $wo // Uppercase coordinate $coordinate = strtoupper($coordinate); // Eliminate leading equal sign - $testCoordinate = (string) preg_replace('/^=/', '', $coordinate); + $testCoordinate = Preg::replace('/^=/', '', $coordinate); $defined = $worksheet->getParentOrThrow()->getDefinedName($testCoordinate, $worksheet); if ($defined !== null) { if ($defined->getWorksheet() === $worksheet && !$defined->isFormula()) { - $coordinate = (string) preg_replace('/^=/', '', $defined->getValue()); + $coordinate = Preg::replace('/^=/', '', $defined->getValue()); } } diff --git a/src/PhpSpreadsheet/Worksheet/Worksheet.php b/src/PhpSpreadsheet/Worksheet/Worksheet.php index 376185ed1a..b992b5e883 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -23,6 +23,7 @@ use PhpOffice\PhpSpreadsheet\ReferenceHelper; use PhpOffice\PhpSpreadsheet\RichText\RichText; use PhpOffice\PhpSpreadsheet\Shared; +use PhpOffice\PhpSpreadsheet\Shared\StringHelper; use PhpOffice\PhpSpreadsheet\Spreadsheet; use PhpOffice\PhpSpreadsheet\Style\Alignment; use PhpOffice\PhpSpreadsheet\Style\Color; @@ -404,15 +405,15 @@ public static function getInvalidCharacters(): array */ private static function checkSheetCodeName(string $sheetCodeName): string { - $charCount = Shared\StringHelper::countCharacters($sheetCodeName); + $charCount = StringHelper::countCharacters($sheetCodeName); if ($charCount == 0) { throw new Exception('Sheet code name cannot be empty.'); } // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'" if ( (str_replace(self::INVALID_CHARACTERS, '', $sheetCodeName) !== $sheetCodeName) - || (Shared\StringHelper::substring($sheetCodeName, -1, 1) == '\'') - || (Shared\StringHelper::substring($sheetCodeName, 0, 1) == '\'') + || (StringHelper::substring($sheetCodeName, -1, 1) == '\'') + || (StringHelper::substring($sheetCodeName, 0, 1) == '\'') ) { throw new Exception('Invalid character found in sheet code name'); } @@ -440,7 +441,7 @@ private static function checkSheetTitle(string $sheetTitle): string } // Enforce maximum characters allowed for sheet title - if (Shared\StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { + if (StringHelper::countCharacters($sheetTitle) > self::SHEET_TITLE_MAXIMUM_LENGTH) { throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.'); } @@ -869,19 +870,19 @@ public function setTitle(string $title, bool $updateFormulaCellReferences = true if ($this->parent->sheetNameExists($title)) { // Use name, but append with lowest possible integer - if (Shared\StringHelper::countCharacters($title) > 29) { - $title = Shared\StringHelper::substring($title, 0, 29); + if (StringHelper::countCharacters($title) > 29) { + $title = StringHelper::substring($title, 0, 29); } $i = 1; while ($this->parent->sheetNameExists($title . ' ' . $i)) { ++$i; if ($i == 10) { - if (Shared\StringHelper::countCharacters($title) > 28) { - $title = Shared\StringHelper::substring($title, 0, 28); + if (StringHelper::countCharacters($title) > 28) { + $title = StringHelper::substring($title, 0, 28); } } elseif ($i == 100) { - if (Shared\StringHelper::countCharacters($title) > 27) { - $title = Shared\StringHelper::substring($title, 0, 27); + if (StringHelper::countCharacters($title) > 27) { + $title = StringHelper::substring($title, 0, 27); } } } @@ -1189,7 +1190,7 @@ private function getWorksheetAndCoordinate(string $coordinate): array // Worksheet reference? if (str_contains($coordinate, '!')) { - $worksheetReference = self::extractSheetTitle($coordinate, true); + $worksheetReference = self::extractSheetTitle($coordinate, true, true); $sheet = $this->getParentOrThrow()->getSheetByName($worksheetReference[0]); $finalCoordinate = strtoupper($worksheetReference[1]); @@ -1222,9 +1223,8 @@ private function getWorksheetAndCoordinate(string $coordinate): array if (Coordinate::coordinateIsRange($finalCoordinate)) { throw new Exception('Cell coordinate string can not be a range of cells.'); - } elseif (str_contains($finalCoordinate, '$')) { - throw new Exception('Cell coordinate must not be absolute.'); } + $finalCoordinate = str_replace('$', '', $finalCoordinate); return [$sheet, $finalCoordinate]; } @@ -1395,7 +1395,11 @@ public function getColumnStyle(string $column): ?Style */ public function getStyle(AddressRange|CellAddress|int|string|array $cellCoordinate): Style { + if (is_string($cellCoordinate)) { + $cellCoordinate = Validations::definedNameToCoordinate($cellCoordinate, $this); + } $cellCoordinate = Validations::validateCellOrCellRange($cellCoordinate); + $cellCoordinate = str_replace('$', '', $cellCoordinate); // set this sheet as active $this->getParentOrThrow()->setActiveSheetIndex($this->getParentOrThrow()->getIndex($this)); @@ -2047,10 +2051,10 @@ public function getTableByName(string $name): ?Table */ protected function getTableIndexByName(string $name): ?int { - $name = Shared\StringHelper::strToUpper($name); + $name = StringHelper::strToUpper($name); foreach ($this->tableCollection as $index => $table) { /** @var Table $table */ - if (Shared\StringHelper::strToUpper($table->getName()) === $name) { + if (StringHelper::strToUpper($table->getName()) === $name) { return $index; } } @@ -3182,7 +3186,7 @@ public function getHashInt(): int * * @return ($range is non-empty-string ? ($returnRange is true ? array{0: string, 1: string} : string) : ($returnRange is true ? array{0: null, 1: null} : null)) */ - public static function extractSheetTitle(?string $range, bool $returnRange = false): array|null|string + public static function extractSheetTitle(?string $range, bool $returnRange = false, bool $unapostrophize = false): array|null|string { if (empty($range)) { return $returnRange ? [null, null] : null; @@ -3194,12 +3198,27 @@ public static function extractSheetTitle(?string $range, bool $returnRange = fal } if ($returnRange) { - return [substr($range, 0, $sep), substr($range, $sep + 1)]; + $title = substr($range, 0, $sep); + if ($unapostrophize) { + $title = self::unApostrophizeTitle($title); + } + + return [$title, substr($range, $sep + 1)]; } return substr($range, $sep + 1); } + public static function unApostrophizeTitle(?string $title): string + { + $title ??= ''; + if ($title[0] === "'" && substr($title, -1) === "'") { + $title = str_replace("''", "'", substr($title, 1, -1)); + } + + return $title; + } + /** * Get hyperlink. * @@ -3571,19 +3590,19 @@ public function setCodeName(string $codeName, bool $validate = true): static if ($this->parent->sheetCodeNameExists($codeName)) { // Use name, but append with lowest possible integer - if (Shared\StringHelper::countCharacters($codeName) > 29) { - $codeName = Shared\StringHelper::substring($codeName, 0, 29); + if (StringHelper::countCharacters($codeName) > 29) { + $codeName = StringHelper::substring($codeName, 0, 29); } $i = 1; while ($this->getParentOrThrow()->sheetCodeNameExists($codeName . '_' . $i)) { ++$i; if ($i == 10) { - if (Shared\StringHelper::countCharacters($codeName) > 28) { - $codeName = Shared\StringHelper::substring($codeName, 0, 28); + if (StringHelper::countCharacters($codeName) > 28) { + $codeName = StringHelper::substring($codeName, 0, 28); } } elseif ($i == 100) { - if (Shared\StringHelper::countCharacters($codeName) > 27) { - $codeName = Shared\StringHelper::substring($codeName, 0, 27); + if (StringHelper::countCharacters($codeName) > 27) { + $codeName = StringHelper::substring($codeName, 0, 27); } } } diff --git a/src/PhpSpreadsheet/Writer/Xls/Parser.php b/src/PhpSpreadsheet/Writer/Xls/Parser.php index f552dad8ac..c6581adcb5 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Parser.php +++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -671,7 +671,7 @@ private function convertRange2d(string $range, int $class = 0): string private function convertRange3d(string $token): string { // Split the ref at the ! symbol - [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true); + [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true, true); // Convert the external reference part (different for BIFF8) $ext_ref = $this->getRefIndex($ext_ref ?? ''); @@ -723,7 +723,7 @@ private function convertRef2d(string $cell): string private function convertRef3d(string $cell): string { // Split the ref at the ! symbol - [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true); + [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true, true); // Convert the external reference part (different for BIFF8) $ext_ref = $this->getRefIndex($ext_ref ?? ''); diff --git a/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php b/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php index d96ac096e5..50b1a8f6e5 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php @@ -24,7 +24,7 @@ public static function writeAutoFilter(XMLWriter $objWriter, ActualWorksheet $wo $range = Coordinate::splitRange($autoFilterRange); $range = $range[0]; // Strip any worksheet ref - [$ws, $range[0]] = ActualWorksheet::extractSheetTitle($range[0], true); + [, $range[0]] = ActualWorksheet::extractSheetTitle($range[0], true); $range = implode(':', $range); $objWriter->writeAttribute('ref', str_replace('$', '', $range)); diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php index da977cf917..939aa3e33e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php @@ -6,46 +6,41 @@ use PhpOffice\PhpSpreadsheet\NamedRange; use PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef\AllSetupTeardown; +use PHPUnit\Framework\Attributes\DataProvider; class IsRefTest extends AllSetupTeardown { - private bool $skipA13 = true; - - public function testIsRef(): void + #[DataProvider('providerIsRef')] + public function testIsRef(mixed $expected, string $ref): void { + if ($expected === 'incomplete') { + self::markTestIncomplete('Calculation is too complicated'); + } $sheet = $this->getSheet(); $sheet->getParentOrThrow()->addDefinedName(new NamedRange('NAMED_RANGE', $sheet, 'C1')); + $sheet->getCell('A1')->setValue("=ISREF($ref)"); + self::assertSame($expected, $sheet->getCell('A1')->getCalculatedValue()); + } - $sheet->getCell('A1')->setValue('=ISREF(B1)'); - $sheet->getCell('A2')->setValue('=ISREF(B1:B2)'); - $sheet->getCell('A3')->setValue('=ISREF(B1:D4 C1:C5)'); - $sheet->getCell('A4')->setValue('=ISREF("PHP")'); - $sheet->getCell('A5')->setValue('=ISREF(B1*B2)'); - $sheet->getCell('A6')->setValue('=ISREF(Worksheet2!B1)'); - $sheet->getCell('A7')->setValue('=ISREF(NAMED_RANGE)'); - $sheet->getCell('A8')->setValue('=ISREF(INDIRECT("' . $sheet->getTitle() . '" & "!" & "A1"))'); - $sheet->getCell('A9')->setValue('=ISREF(INDIRECT("A1"))'); - $sheet->getCell('A10')->setValue('=ISREF(INDIRECT("Invalid Worksheet" & "!" & "A1"))'); - $sheet->getCell('A11')->setValue('=ISREF(INDIRECT("Invalid Worksheet" & "!A1"))'); - $sheet->getCell('A12')->setValue('=ISREF(ZZZ1)'); - $sheet->getCell('A13')->setValue('=ISREF(CHOOSE(2, A1, B1, C1))'); - - self::assertTrue($sheet->getCell('A1')->getCalculatedValue()); // Cell Reference - self::assertTrue($sheet->getCell('A2')->getCalculatedValue()); // Cell Range - self::assertTrue($sheet->getCell('A3')->getCalculatedValue()); // Complex Cell Range - self::assertFalse($sheet->getCell('A4')->getCalculatedValue()); // Text String - self::assertFalse($sheet->getCell('A5')->getCalculatedValue()); // Result of a math expression - self::assertTrue($sheet->getCell('A6')->getCalculatedValue()); // Cell Reference with worksheet - self::assertTrue($sheet->getCell('A7')->getCalculatedValue()); // Named Range - self::assertTrue($sheet->getCell('A8')->getCalculatedValue()); // Indirect to a Cell Reference - self::assertTrue($sheet->getCell('A9')->getCalculatedValue()); // Indirect to a Worksheet/Cell Reference - self::assertFalse($sheet->getCell('A10')->getCalculatedValue()); // Indirect to an Invalid Worksheet/Cell Reference - self::assertFalse($sheet->getCell('A11')->getCalculatedValue()); // Indirect to an Invalid Worksheet/Cell Reference - self::assertFalse($sheet->getCell('A12')->getCalculatedValue()); // Invalid Cell Reference - if ($this->skipA13) { - self::markTestIncomplete('Calculation for A13 is too complicated'); - } - self::assertTrue($sheet->getCell('A13')->getCalculatedValue()); // returned Cell Reference + public static function providerIsRef(): array + { + return [ + 'cell reference' => [true, 'B1'], + 'invalid cell reference' => [false, 'ZZZ1'], + 'cell range' => [true, 'B1:B2'], + 'complex cell range' => [true, 'B1:D4 C1:C5'], + 'text string' => [false, '"PHP"'], + 'math expression' => [false, 'B1*B2'], + 'unquoted sheet name' => [true, 'Worksheet2!B1'], + 'quoted sheet name' => [true, "'Worksheet2'!B1:B2"], + 'quoted sheet name with apostrophe' => [true, "'Work''sheet2'!B1:B2"], + 'named range' => [true, 'NAMED_RANGE'], + 'unknown named range' => ['#NAME?', 'xNAMED_RANGE'], + 'indirect to a cell reference' => [true, 'INDIRECT("A1")'], + 'indirect to a worksheet/cell reference' => [true, 'INDIRECT("\'Worksheet\'!A1")'], + 'indirect to invalid worksheet/cell reference' => [false, 'INDIRECT("\'Invalid Worksheet\'!A1")'], + 'returned cell reference' => ['incomplete', 'CHOOSE(2, A1, B1, C1)'], + ]; } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php index a57b91eb3f..d019d428b8 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php @@ -54,4 +54,22 @@ public function testCOLUMNLocalDefinedName(): void $result = $sheet->getCell('B3')->getCalculatedValue(); self::assertSame('#NAME?', $result); } + + public function testCOLUMNSheetWithApostrophe(): void + { + $this->setArrayAsValue(); + $sheet = $this->getSheet(); + + $sheet1 = $this->getSpreadsheet()->createSheet(); + $sheet1->setTitle("apo''strophe"); + $this->getSpreadsheet()->addNamedRange(new NamedRange('newnr', $sheet1, '$F$5:$H$5', true)); // defined locally, only usable on sheet1 + + $sheet1->getCell('B3')->setValue('=COLUMN(newnr)'); + $result = $sheet1->getCell('B3')->getCalculatedValue(); + self::assertSame(6, $result); + + $sheet->getCell('B3')->setValue('=COLUMN(newnr)'); + $result = $sheet->getCell('B3')->getCalculatedValue(); + self::assertSame('#NAME?', $result); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php index c6a66c4c27..f1ad6919b7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php @@ -6,10 +6,11 @@ use PhpOffice\PhpSpreadsheet\Calculation\LookupRef; use PhpOffice\PhpSpreadsheet\NamedRange; +use PHPUnit\Framework\Attributes\DataProvider; class OffsetTest extends AllSetupTeardown { - #[\PHPUnit\Framework\Attributes\DataProvider('providerOFFSET')] + #[DataProvider('providerOFFSET')] public function testOFFSET(mixed $expectedResult, null|string $cellReference = null): void { $result = LookupRef\Offset::OFFSET($cellReference); @@ -58,4 +59,29 @@ public function testOffsetNamedRange(): void self::assertSame(2, $workSheet->getCell('B2')->getCalculatedValue()); } + + public function testOffsetNamedRangeApostropheSheet(): void + { + $workSheet = $this->getSheet(); + $workSheet->setTitle("apo'strophe"); + $workSheet->setCellValue('A1', 1); + $workSheet->setCellValue('A2', 2); + + $this->getSpreadsheet()->addNamedRange(new NamedRange('demo', $workSheet, '=$A$1')); + + $workSheet->setCellValue('B1', '=demo'); + $workSheet->setCellValue('B2', '=OFFSET(demo, 1, 0)'); + + self::assertSame(2, $workSheet->getCell('B2')->getCalculatedValue()); + } + + public function testOffsetMultiCellNamedRange(): void + { + $sheet = $this->getSheet(); + $sheet->setCellValue('D13', 'Hello'); + $this->getSpreadsheet() + ->addNamedRange(new NamedRange('CELLAREA', $sheet, '$B$6:$F$22')); + $sheet->setCellValue('D1', '=OFFSET(CELLAREA,7,2,1,1)'); + self::assertSame('Hello', $sheet->getCell('D1')->getCalculatedValue()); + } } diff --git a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php index 4e06f9ca03..c529700ee0 100644 --- a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php @@ -175,6 +175,18 @@ public static function providerBinaryOperations(): array ], "=MIN('sheet1'!A:A) + 'sheet1'!A1", ], + 'Combined Cell Reference and Column Range Unquoted Sheet' => [ + [ + ['type' => 'Column Reference', 'value' => 'sheet1!A1', 'reference' => 'sheet1!A1'], + ['type' => 'Column Reference', 'value' => 'sheet1!A1048576', 'reference' => 'sheet1!A1048576'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], + ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], + ['type' => 'Cell Reference', 'value' => 'sheet1!A1', 'reference' => 'sheet1!A1'], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], + ], + '=MIN(sheet1!A:A) + sheet1!A1', + ], 'Combined Cell Reference and Column Range with quote' => [ [ ['type' => 'Column Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], diff --git a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php index 2918341eba..02a64eab7f 100644 --- a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php +++ b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php @@ -6,6 +6,7 @@ use PhpOffice\PhpSpreadsheet\Reader\BaseReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PHPUnit\Framework\Attributes\DataProvider; class PrintAreaTest extends AbstractFunctional { @@ -17,7 +18,7 @@ public static function providerFormats(): array ]; } - #[\PHPUnit\Framework\Attributes\DataProvider('providerFormats')] + #[DataProvider('providerFormats')] public function testPageSetup(string $format): void { // Create new workbook with 6 sheets and different print areas @@ -41,6 +42,7 @@ public function testPageSetup(string $format): void $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, $format, function (BaseReader $reader): void { $reader->setLoadSheetsOnly(['Sheet 1', 'Sheet 3', 'Sheet 4', 'Sheet 5', 'Sheet 6']); }); + $spreadsheet->disconnectWorksheets(); $actual1 = self::getPrintArea($reloadedSpreadsheet, 'Sheet 1'); $actual3 = self::getPrintArea($reloadedSpreadsheet, 'Sheet 3'); @@ -52,6 +54,7 @@ public function testPageSetup(string $format): void self::assertSame('A4:B4,D1:E4', $actual4, 'should be able to write and read page setup with multiple print areas'); self::assertSame('A1:J10', $actual5, 'add by column and row'); self::assertSame('A1:J10,L1:L10', $actual6, 'multiple add by column and row'); + $reloadedSpreadsheet->disconnectWorksheets(); } private static function getPrintArea(Spreadsheet $spreadsheet, string $name): string diff --git a/tests/PhpSpreadsheetTests/Reader/Gnumeric/DefinedNameTest.php b/tests/PhpSpreadsheetTests/Reader/Gnumeric/DefinedNameTest.php new file mode 100644 index 0000000000..4733e564e2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Gnumeric/DefinedNameTest.php @@ -0,0 +1,32 @@ +load($filename); + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame($sheetName, $sheet->getTitle()); + self::assertSame('=sheet1first', $sheet->getCell('C1')->getValue()); + self::assertSame(1, $sheet->getCell('C1')->getCalculatedValue()); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php index c3d3655e9d..2c3a36698c 100644 --- a/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php @@ -31,6 +31,28 @@ public function testDefinedNamesValue(): void $spreadsheet->disconnectWorksheets(); } + public function testDefinedNamesApostropheValue(): void + { + $filename = 'tests/data/Reader/Ods/DefinedNames.apostrophe.ods'; + $reader = new Ods(); + $spreadsheet = $reader->load($filename); + $calculation = Calculation::getInstance($spreadsheet); + $calculation->setInstanceArrayReturnType( + Calculation::RETURN_ARRAY_AS_VALUE + ); + $worksheet = $spreadsheet->getActiveSheet(); + self::assertSame("apo'strophe", $worksheet->getTitle()); + + $firstDefinedNameValue = $worksheet->getCell('First')->getValue(); + $secondDefinedNameValue = $worksheet->getCell('Second')->getValue(); + $calculatedFormulaValue = $worksheet->getCell('B2')->getCalculatedValue(); + + self::assertSame(3, $firstDefinedNameValue); + self::assertSame(4, $secondDefinedNameValue); + self::assertSame(12, $calculatedFormulaValue); + $spreadsheet->disconnectWorksheets(); + } + public function testDefinedNamesArray(): void { $filename = 'tests/data/Reader/Ods/DefinedNames.ods'; diff --git a/tests/PhpSpreadsheetTests/Reader/Xls/Issue4356Test.php b/tests/PhpSpreadsheetTests/Reader/Xls/Issue4356Test.php new file mode 100644 index 0000000000..4ac508dd27 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xls/Issue4356Test.php @@ -0,0 +1,44 @@ +getActiveSheet(); + $originalSheet->setTitle("Goodn't sheet name"); + $originalSpreadsheet->addNamedRange( + new NamedRange($nameDefined, $originalSheet, '$A$1') + ); + $originalSheet->setCellValue('A1', 'This is a named cell.'); + $originalSheet->getStyle($nameDefined) + ->getFont() + ->setItalic(true); + $spreadsheet = $this->writeAndReload($originalSpreadsheet, 'Xls'); + $originalSpreadsheet->disconnectWorksheets(); + + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('C1', "=$nameDefined"); + self::assertSame('This is a named cell.', $sheet->getCell('C1')->getCalculatedValue()); + $namedRange2 = $spreadsheet->getNamedRange($nameDefined); + self::assertNotNull($namedRange2); + $sheetx = $namedRange2->getWorksheet(); + self::assertNotNull($sheetx); + $style = $sheetx->getStyle($nameDefined); + self::assertTrue($style->getFont()->getItalic()); + $style->getFont()->setItalic(false); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/ApostropheTest.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/ApostropheTest.php new file mode 100644 index 0000000000..dcb1d116cd --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/ApostropheTest.php @@ -0,0 +1,68 @@ +setInstanceArrayReturnType( + Calculation::RETURN_ARRAY_AS_ARRAY + ); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setTitle('sheet1'); + $sheet->getCell('A1')->setValue(1); + $sheet->getCell('A2')->setValue(2); + $sheet->getCell('A3')->setValue(3); + $sheet->getCell('A4')->setValue(4); + $spreadsheet->addNamedRange(new NamedRange('sheet14cells', $sheet, '$A$1:$A$4')); + $sheet->getCell('C1') + ->setValue('=sheet14cells*sheet14cells'); + $sheet->getCell('E1')->setValue('=ANCHORARRAY(sheet1!C1)'); + $sheet->getCell('G1')->setValue('=SINGLE(sheet1!C1:C4)'); + + $sheet1 = $spreadsheet->createSheet(); + $sheet1->setTitle("Apo'strophe"); + $sheet1->getCell('A1')->setValue(2); + $sheet1->getCell('A2')->setValue(3); + $sheet1->getCell('A3')->setValue(4); + $sheet1->getCell('A4')->setValue(5); + $spreadsheet->addNamedRange(new NamedRange('sheet24cells', $sheet1, '$A$1:$A$4')); + $sheet1->getCell('C1') + ->setValue('=sheet24cells*sheet24cells'); + $sheet1->getCell('E1')->setValue("=ANCHORARRAY('Apo''strophe'!C1)"); + $sheet1->getCell('G1')->setValue("=SINGLE('Apo''strophe'!C1:C4)"); + + $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xlsx'); + $spreadsheet->disconnectWorksheets(); + Calculation::getInstance($reloadedSpreadsheet) + ->setInstanceArrayReturnType( + Calculation::RETURN_ARRAY_AS_ARRAY + ); + $rsheet = $reloadedSpreadsheet->getSheet(0); + // make sure results aren't from cache + $rsheet->getCell('E1')->setCalculatedValue(-1); + $rsheet->getCell('G1')->setCalculatedValue(-1); + self::assertSame([[1], [4], [9], [16]], $rsheet->getCell('C1')->getCalculatedValue()); + self::assertSame([[1], [4], [9], [16]], $rsheet->getCell('E1')->getCalculatedValue()); + self::assertSame(1, $rsheet->getCell('G1')->getCalculatedValue()); + + $rsheet1 = $reloadedSpreadsheet->getSheet(1); + // make sure results aren't from cache + $rsheet1->getCell('E1')->setCalculatedValue(-1); + $rsheet1->getCell('G1')->setCalculatedValue(-1); + self::assertSame([[4], [9], [16], [25]], $rsheet1->getCell('C1')->getCalculatedValue()); + self::assertSame([[4], [9], [16], [25]], $rsheet1->getCell('E1')->getCalculatedValue()); + self::assertSame(4, $rsheet1->getCell('G1')->getCalculatedValue()); + $reloadedSpreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4356Test.php b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4356Test.php new file mode 100644 index 0000000000..16bb8e12fe --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Xlsx/Issue4356Test.php @@ -0,0 +1,43 @@ +getActiveSheet(); + $originalSheet->setTitle("Goodn't sheet name"); + $originalSpreadsheet->addNamedRange( + new NamedRange($nameDefined, $originalSheet, '$A$1') + ); + $originalSheet->setCellValue('A1', 'This is a named cell.'); + $originalSheet->getStyle($nameDefined) + ->getFont() + ->setItalic(true); + $spreadsheet = $this->writeAndReload($originalSpreadsheet, 'Xlsx'); + $originalSpreadsheet->disconnectWorksheets(); + + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('C1', "=$nameDefined"); + self::assertSame('This is a named cell.', $sheet->getCell('C1')->getCalculatedValue()); + $namedRange2 = $spreadsheet->getNamedRange($nameDefined); + self::assertNotNull($namedRange2); + $sheetx = $namedRange2->getWorksheet(); + self::assertNotNull($sheetx); + $style = $sheetx->getStyle($nameDefined); + self::assertTrue($style->getFont()->getItalic()); + $style->getFont()->setItalic(false); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/data/Reader/Gnumeric/apostrophe3a.gnumeric b/tests/data/Reader/Gnumeric/apostrophe3a.gnumeric new file mode 100644 index 0000000000..6950033ae5 Binary files /dev/null and b/tests/data/Reader/Gnumeric/apostrophe3a.gnumeric differ diff --git a/tests/data/Reader/Gnumeric/apostrophe3b.gnumeric b/tests/data/Reader/Gnumeric/apostrophe3b.gnumeric new file mode 100644 index 0000000000..7517660442 Binary files /dev/null and b/tests/data/Reader/Gnumeric/apostrophe3b.gnumeric differ diff --git a/tests/data/Reader/Ods/DefinedNames.apostrophe.ods b/tests/data/Reader/Ods/DefinedNames.apostrophe.ods new file mode 100644 index 0000000000..8b4642adda Binary files /dev/null and b/tests/data/Reader/Ods/DefinedNames.apostrophe.ods differ