Skip to content

Commit

Permalink
Merge pull request #4360 from oleibman/issue4536
Browse files Browse the repository at this point in the history
Xlsx Reader Defined Name on Sheet with Apostrophe in Title
  • Loading branch information
oleibman authored Feb 21, 2025
2 parents 82ac7ab + 898ec44 commit d813d7e
Show file tree
Hide file tree
Showing 29 changed files with 387 additions and 99 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 8 additions & 6 deletions src/PhpSpreadsheet/Calculation/Calculation.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 .= '!';
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Calculation/Information/Value.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 1 addition & 2 deletions src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 !== '')
Expand Down
15 changes: 9 additions & 6 deletions src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,19 +56,22 @@ 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);

$startCell = $endCell = $cellAddress;
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();
Expand Down Expand Up @@ -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 !== '')
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Chart/DataSeriesValues.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions src/PhpSpreadsheet/Reader/Gnumeric.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,8 +525,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) {
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Reader/Ods/DefinedNames.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 8 additions & 9 deletions src/PhpSpreadsheet/Reader/Xls/LoadSpreadsheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions src/PhpSpreadsheet/Reader/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -1821,11 +1821,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");
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/PhpSpreadsheet/Style/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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');
}
Expand Down
9 changes: 5 additions & 4 deletions src/PhpSpreadsheet/Worksheet/Validations.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
Expand All @@ -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 ?? ''
Expand Down Expand Up @@ -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());
}
}

Expand Down
Loading

0 comments on commit d813d7e

Please sign in to comment.