diff --git a/src/PhpSpreadsheet/ReferenceHelper.php b/src/PhpSpreadsheet/ReferenceHelper.php index f448473fb..c12f6dea9 100644 --- a/src/PhpSpreadsheet/ReferenceHelper.php +++ b/src/PhpSpreadsheet/ReferenceHelper.php @@ -15,10 +15,12 @@ class ReferenceHelper { /** Constants */ /** Regular Expressions */ - const REFHELPER_REGEXP_CELLREF = '((\w*|\'[^!]*\')!)?(?garbageCollect(); } + private static function matchSheetName(?string $match, string $worksheetName): bool + { + return $match === null || $match === '' || $match === "'\u{fffc}'" || $match === "'\u{fffb}'" || strcasecmp(trim($match, "'"), $worksheetName) === 0; + } + + private static function sheetnameBeforeCells(string $match, string $worksheetName, string $cells): string + { + $toString = ($match > '') ? "$match!" : ''; + + return str_replace(["\u{fffc}", "'\u{fffb}'"], $worksheetName, $toString) . $cells; + } + /** * Update references within formulas. * @@ -565,6 +579,7 @@ class ReferenceHelper bool $includeAbsoluteReferences = false, bool $onlyAbsoluteReferences = false ): string { + $callback = fn (array $matches): string => (strcasecmp(trim($matches[2], "'"), $worksheetName) === 0) ? (($matches[2][0] === "'") ? "'\u{fffc}'!" : "'\u{fffb}'!") : "'\u{fffd}'!"; if ( $this->cellReferenceHelper === null || $this->cellReferenceHelper->refreshRequired($beforeCellAddress, $numberOfColumns, $numberOfRows) @@ -582,18 +597,17 @@ class ReferenceHelper $adjustCount = 0; $newCellTokens = $cellTokens = []; // Search for row ranges (e.g. 'Sheet1'!3:5 or 3:5) with or without $ absolutes (e.g. $3:5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $formulaBlockx = ' ' . (preg_replace_callback(self::SHEETNAME_PART_WITH_SLASHES, $callback, $formulaBlock) ?? $formulaBlock) . ' '; + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_ROWRANGE . '/mui', $formulaBlockx, $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { - $fromString = ($match[2] > '') ? $match[2] . '!' : ''; - $fromString .= $match[3] . ':' . $match[4]; + $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = substr($this->updateCellReference('$A' . $match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); $modified4 = substr($this->updateCellReference('$A' . $match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences), 2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { - $toString = ($match[2] > '') ? $match[2] . '!' : ''; - $toString .= $modified3 . ':' . $modified4; + if (self::matchSheetName($match[2], $worksheetName)) { + $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = 100000; $row = 10000000 + (int) trim($match[3], '$'); @@ -607,18 +621,17 @@ class ReferenceHelper } } // Search for column ranges (e.g. 'Sheet1'!C:E or C:E) with or without $ absolutes (e.g. $C:E) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $formulaBlockx = ' ' . (preg_replace_callback(self::SHEETNAME_PART_WITH_SLASHES, $callback, $formulaBlock) ?? $formulaBlock) . ' '; + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_COLRANGE . '/mui', $formulaBlockx, $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { - $fromString = ($match[2] > '') ? $match[2] . '!' : ''; - $fromString .= $match[3] . ':' . $match[4]; + $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = substr($this->updateCellReference($match[3] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); $modified4 = substr($this->updateCellReference($match[4] . '$1', $includeAbsoluteReferences, $onlyAbsoluteReferences), 0, -2); if ($match[3] . ':' . $match[4] !== $modified3 . ':' . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { - $toString = ($match[2] > '') ? $match[2] . '!' : ''; - $toString .= $modified3 . ':' . $modified4; + if (self::matchSheetName($match[2], $worksheetName)) { + $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($match[3], '$')) + 100000; $row = 10000000; @@ -632,18 +645,17 @@ class ReferenceHelper } } // Search for cell ranges (e.g. 'Sheet1'!A3:C5 or A3:C5) with or without $ absolutes (e.g. $A1:C$5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + $formulaBlockx = ' ' . (preg_replace_callback(self::SHEETNAME_PART_WITH_SLASHES, $callback, "$formulaBlock") ?? "$formulaBlock") . ' '; + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLRANGE . '/mui', $formulaBlockx, $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { - $fromString = ($match[2] > '') ? $match[2] . '!' : ''; - $fromString .= $match[3] . ':' . $match[4]; + $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}:{$match[4]}"); $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); $modified4 = $this->updateCellReference($match[4], $includeAbsoluteReferences, $onlyAbsoluteReferences); if ($match[3] . $match[4] !== $modified3 . $modified4) { - if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { - $toString = ($match[2] > '') ? $match[2] . '!' : ''; - $toString .= $modified3 . ':' . $modified4; + if (self::matchSheetName($match[2], $worksheetName)) { + $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3:$modified4"); [$column, $row] = Coordinate::coordinateFromString($match[3]); // Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more $column = Coordinate::columnIndexFromString(trim($column, '$')) + 100000; @@ -658,18 +670,18 @@ class ReferenceHelper } } // Search for cell references (e.g. 'Sheet1'!A3 or C5) with or without $ absolutes (e.g. $A1 or C$5) - $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', ' ' . $formulaBlock . ' ', $matches, PREG_SET_ORDER); + + $formulaBlockx = ' ' . (preg_replace_callback(self::SHEETNAME_PART_WITH_SLASHES, $callback, $formulaBlock) ?? $formulaBlock) . ' '; + $matchCount = preg_match_all('/' . self::REFHELPER_REGEXP_CELLREF . '/mui', $formulaBlockx, $matches, PREG_SET_ORDER); if ($matchCount > 0) { foreach ($matches as $match) { - $fromString = ($match[2] > '') ? $match[2] . '!' : ''; - $fromString .= $match[3]; + $fromString = self::sheetnameBeforeCells($match[2], $worksheetName, "{$match[3]}"); $modified3 = $this->updateCellReference($match[3], $includeAbsoluteReferences, $onlyAbsoluteReferences); if ($match[3] !== $modified3) { - if (($match[2] == '') || (trim($match[2], "'") == $worksheetName)) { - $toString = ($match[2] > '') ? $match[2] . '!' : ''; - $toString .= $modified3; + if (self::matchSheetName($match[2], $worksheetName)) { + $toString = self::sheetnameBeforeCells($match[2], $worksheetName, "$modified3"); [$column, $row] = Coordinate::coordinateFromString($match[3]); $columnAdditionalIndex = $column[0] === '$' ? 1 : 0; $rowAdditionalIndex = $row[0] === '$' ? 1 : 0; diff --git a/tests/PhpSpreadsheetTests/ReferenceHelper4Test.php b/tests/PhpSpreadsheetTests/ReferenceHelper4Test.php new file mode 100644 index 000000000..c0dc75611 --- /dev/null +++ b/tests/PhpSpreadsheetTests/ReferenceHelper4Test.php @@ -0,0 +1,48 @@ +getActiveSheet(); + $dataSheet->setTitle($dataTitle); + $settingsSheet = $spreadsheet->createSheet(); + $settingsSheet->setTitle($settingsTitle); + $settingsSheet->getCell('A1')->setValue(10); + $settingsSheet->getCell('B1')->setValue(20); + $dataSheet->getCell('A5')->setValue($formula); + $dataSheet->getCell('A2')->setValue(1); + $dataSheet->insertNewColumnBefore('A'); + self::assertSame($expectedResult, $dataSheet->getCell('B5')->getValue()); + $spreadsheet->disconnectWorksheets(); + } + + public static function dataProvider(): array + { + return [ + ["=SUM(B2, 'F1 (SETTINGS)'!A1:B1)", 'F1 (SETTINGS)', "=SUM(A2, 'F1 (SETTINGS)'!A1:B1)"], + ["=SUM(B2, 'x F1 (SETTINGS)'!A1:B1)", 'x F1 (SETTINGS)', "=SUM(A2, 'x F1 (SETTINGS)'!A1:B1)"], + ["=SUM(B2, 'DATA'!B1)", 'F1 (SETTINGS)', "=SUM(A2, 'DATA'!A1)"], + ["=SUM(B2, 'DATA'!C1)", 'F1 (SETTINGS)', "=SUM(A2, 'data'!B1)"], + ['=SUM(B2, DATA!D1)', 'F1 (SETTINGS)', '=SUM(A2, data!C1)'], + ["=SUM(B2, 'DATA'!B1:C1)", 'F1 (SETTINGS)', "=SUM(A2, 'Data'!A1:B1)"], + ['=SUM(B2, DATA!B1:C1)', 'F1 (SETTINGS)', '=SUM(A2, DAta!A1:B1)'], + ["=SUM(B2, 'F1 Data'!C1)", 'F1 (SETTINGS)', "=SUM(A2, 'F1 Data'!B1)", 'F1 Data'], + ["=SUM(B2, 'x F1 Data'!C1)", 'F1 (SETTINGS)', "=SUM(A2, 'x F1 Data'!B1)", 'x F1 Data'], + ["=SUM(B2, 'x F1 Data'!C1)", 'F1 (SETTINGS)', "=SUM(A2, 'x F1 Data'!B1)", 'x F1 Data'], + ["=SUM(B2, 'x F1 Data'!C1:D2)", 'F1 (SETTINGS)', "=SUM(A2, 'x F1 Data'!B1:C2)", 'x F1 Data'], + ['=SUM(B2, definedname1A1)', 'F1 (SETTINGS)', '=SUM(A2, definedname1A1)', 'x F1 Data'], + ]; + } +}