diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcbb0dfd..6b1a22120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,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 45807e577..665c04f10 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4408,7 +4408,9 @@ class Calculation 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 @@ class Calculation } } 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 @@ class Calculation $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 @@ class Calculation 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 49361ef06..18274651f 100644 --- a/src/PhpSpreadsheet/Calculation/Information/Value.php +++ b/src/PhpSpreadsheet/Calculation/Information/Value.php @@ -45,7 +45,7 @@ class Value $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 2bccab8da..83ea458a2 100644 --- a/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php +++ b/src/PhpSpreadsheet/Calculation/Internal/ExcelArrayPseudoFunctions.php @@ -15,7 +15,7 @@ class ExcelArrayPseudoFunctions { $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 @@ class ExcelArrayPseudoFunctions //$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 191144bfd..21c2030cd 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php @@ -61,8 +61,7 @@ class Helpers { $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 260ccc3a5..9d201ff22 100644 --- a/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php +++ b/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Calculation\Functions; 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 @@ class Offset 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 @@ class Offset 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 @@ class Offset $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 70f90bf78..6da4bc4aa 100644 --- a/src/PhpSpreadsheet/Chart/DataSeriesValues.php +++ b/src/PhpSpreadsheet/Chart/DataSeriesValues.php @@ -446,7 +446,7 @@ class DataSeriesValues extends Properties } 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 6b71da029..7019b56eb 100644 --- a/src/PhpSpreadsheet/Reader/Gnumeric.php +++ b/src/PhpSpreadsheet/Reader/Gnumeric.php @@ -525,8 +525,8 @@ class Gnumeric extends BaseReader 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 a99e3ea74..713ea550d 100644 --- a/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php +++ b/src/PhpSpreadsheet/Reader/Ods/DefinedNames.php @@ -60,7 +60,7 @@ class DefinedNames extends BaseLoader */ 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 aeda44aa2..7e24e7f11 100644 --- a/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php +++ b/src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php @@ -588,8 +588,8 @@ class LoadSpreadsheet extends Xls // $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 @@ class LoadSpreadsheet extends Xls // 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 @@ class LoadSpreadsheet extends Xls /** @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 6b93c9763..465f7ee78 100644 --- a/src/PhpSpreadsheet/Reader/Xlsx.php +++ b/src/PhpSpreadsheet/Reader/Xlsx.php @@ -1821,11 +1821,10 @@ class Xlsx extends BaseReader $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 68ca39e06..022a49ac7 100644 --- a/src/PhpSpreadsheet/Style/Style.php +++ b/src/PhpSpreadsheet/Style/Style.php @@ -7,6 +7,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate; 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 @@ class Style extends Supervisor // 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 75de21895..4a8720dbb 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 @@ class Validations 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 @@ class Validations */ 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 @@ class Validations // 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 376185ed1..b992b5e88 100644 --- a/src/PhpSpreadsheet/Worksheet/Worksheet.php +++ b/src/PhpSpreadsheet/Worksheet/Worksheet.php @@ -23,6 +23,7 @@ use PhpOffice\PhpSpreadsheet\Exception; 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 @@ class Worksheet */ 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 @@ class Worksheet } // 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 @@ class Worksheet 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 @@ class Worksheet // 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 @@ class Worksheet 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 @@ class Worksheet */ 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 @@ class Worksheet */ 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 @@ class Worksheet * * @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 @@ class Worksheet } 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 @@ class Worksheet 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 f552dad8a..c6581adcb 100644 --- a/src/PhpSpreadsheet/Writer/Xls/Parser.php +++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php @@ -671,7 +671,7 @@ class Parser 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 @@ class Parser 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 d96ac096e..50b1a8f6e 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/AutoFilter.php @@ -24,7 +24,7 @@ class AutoFilter extends WriterPart $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 da977cf91..939aa3e33 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/Information/IsRefTest.php @@ -6,46 +6,41 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\Information; 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 a57b91eb3..d019d428b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/ColumnOnSpreadsheetTest.php @@ -54,4 +54,22 @@ class ColumnOnSpreadsheetTest extends AllSetupTeardown $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 c6a66c4c2..f1ad6919b 100644 --- a/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/Functions/LookupRef/OffsetTest.php @@ -6,10 +6,11 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation\Functions\LookupRef; 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 @@ class OffsetTest extends AllSetupTeardown 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 4e06f9ca0..c529700ee 100644 --- a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php @@ -175,6 +175,18 @@ class ParseFormulaTest extends TestCase ], "=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 2918341eb..02a64eab7 100644 --- a/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php +++ b/tests/PhpSpreadsheetTests/Functional/PrintAreaTest.php @@ -6,6 +6,7 @@ namespace PhpOffice\PhpSpreadsheetTests\Functional; use PhpOffice\PhpSpreadsheet\Reader\BaseReader; use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PHPUnit\Framework\Attributes\DataProvider; class PrintAreaTest extends AbstractFunctional { @@ -17,7 +18,7 @@ class PrintAreaTest extends AbstractFunctional ]; } - #[\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 @@ class PrintAreaTest extends AbstractFunctional $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 @@ class PrintAreaTest extends AbstractFunctional 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 000000000..4733e564e --- /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 c3d3655e9..2c3a36698 100644 --- a/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php +++ b/tests/PhpSpreadsheetTests/Reader/Ods/DefinedNamesTest.php @@ -31,6 +31,28 @@ class DefinedNamesTest extends TestCase $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 000000000..4ac508dd2 --- /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 000000000..dcb1d116c --- /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 000000000..16bb8e12f --- /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 000000000..6950033ae 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 000000000..751766044 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 000000000..8b4642add Binary files /dev/null and b/tests/data/Reader/Ods/DefinedNames.apostrophe.ods differ