From 5b7fce928618052f009cb1a7637d0be8d23caf92 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:29:30 -0800 Subject: [PATCH 1/4] Strip `_xlfn.` and `_xlfs.` In Formula Translations Fix #3819. Excel can add these prefixes (basically invisible to end-user). Formula translation in PhpSpreadsheet fails when dealing with these unexpected prefixes, and, even if it handled it correctly, the unexpected prefixes confuse the users. I have changed to strip those prefixes when translating to a locale. This is probably not perfect, but is almost certainly good enough. I could easily add the same change when translating from a locale to English, but I don't think there's a good use case for that, so am opting not to do so for now. The documentation mentions `translateFormulaToLocale` and `translateFormulaToEnglish`. Neither of these exist; both names are preceded by an underscore. I have changed the code to match the documentation rather than vice versa, retaining deprecated versions of the underscored routines which merely invoke the non-underscored routines. --- CHANGELOG.md | 3 ++- src/PhpSpreadsheet/Calculation/Calculation.php | 17 +++++++++++++++++ .../Calculation/TranslationTest.php | 2 +- tests/data/Calculation/Translations.php | 10 ++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f9746d41..caa11144ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Deprecated -- Nothing +- Functions `_translateFormulaToLocale` and `_translateFormulaEnglish` are replaced by versions without leading underscore. [PR #3829](https://github.com/PHPOffice/PhpSpreadsheet/pull/3829) ### Removed @@ -78,6 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771) - Case Insensitive Comparison for Sheet Names [PR #3791](https://github.com/PHPOffice/PhpSpreadsheet/pull/3791) - Performance improvement for Xlsx Reader. [Issue #3683](https://github.com/PHPOffice/PhpSpreadsheet/issues/3683) [PR #3810](https://github.com/PHPOffice/PhpSpreadsheet/pull/3810) +- Strip `xlfn.` and `xlws.` from Formula Translations. [Issue #3819](https://github.com/PHPOffice/PhpSpreadsheet/issues/3819) [PR #3829](https://github.com/PHPOffice/PhpSpreadsheet/pull/3829) - Prevent loop in Shared/File. [Issue #3807](https://github.com/PHPOffice/PhpSpreadsheet/issues/3807) [PR #3809](https://github.com/PHPOffice/PhpSpreadsheet/pull/3809) ## 1.29.0 - 2023-06-15 diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 960db939c2..3951894571 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -3326,8 +3326,17 @@ private static function translateFormula(array $from, array $to, string $formula /** @var ?array */ private static $functionReplaceToLocale; + /** + * @deprecated 1.30.0 use translateFormulaToLocale() instead + */ public function _translateFormulaToLocale(string $formula): string { + return $this->translateFormulaToLocale($formula); + } + + public function translateFormulaToLocale(string $formula): string + { + $formula = preg_replace('/_(xlfn|xlws)[.]/', '', $formula) ?? ''; // Build list of function names and constants for translation if (self::$functionReplaceFromExcel === null) { self::$functionReplaceFromExcel = []; @@ -3364,7 +3373,15 @@ public function _translateFormulaToLocale(string $formula): string /** @var ?array */ private static $functionReplaceToExcel; + /** + * @deprecated 1.30.0 use translateFormulaToEnglish() instead + */ public function _translateFormulaToEnglish(string $formula): string + { + return $this->translateFormulaToEnglish($formula); + } + + public function translateFormulaToEnglish(string $formula): string { if (self::$functionReplaceFromLocale === null) { self::$functionReplaceFromLocale = []; diff --git a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php index c14ee0a64a..714b938e9e 100644 --- a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php @@ -47,7 +47,7 @@ public function testTranslation(string $expectedResult, string $locale, string $ self::assertSame($expectedResult, $translatedFormula); $restoredFormula = Calculation::getInstance()->_translateFormulaToEnglish($translatedFormula); - self::assertSame($formula, $restoredFormula); + self::assertSame(preg_replace('/_(xlfn|xlws)[.]/', '', $formula), $restoredFormula); } public static function providerTranslations(): array diff --git a/tests/data/Calculation/Translations.php b/tests/data/Calculation/Translations.php index b0849ff274..dee3826625 100644 --- a/tests/data/Calculation/Translations.php +++ b/tests/data/Calculation/Translations.php @@ -92,4 +92,14 @@ 'fr', '=3*ROW(B1)', ], + 'handle _xlfn' => [ + '=MAXWENNS(C5:C10; C5:C10; "<30")', + 'de', + '=_xlfn.MAXIFS(C5:C10, C5:C10, "<30")', + ], + 'handle _xlfn and _xlws' => [ + '=ФИЛЬТР(A5:D20;C5:C20=H2;"")', + 'ru', + '=_xlfn._xlws.FILTER(A5:D20,C5:C20=H2,"")', + ], ]; From 3009873c4419aec9423ea00368141471612dda2f Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Thu, 14 Dec 2023 13:49:01 -0800 Subject: [PATCH 2/4] Fix PR Number in Change Log --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa11144ea..4d7f2b97dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Deprecated -- Functions `_translateFormulaToLocale` and `_translateFormulaEnglish` are replaced by versions without leading underscore. [PR #3829](https://github.com/PHPOffice/PhpSpreadsheet/pull/3829) +- Functions `_translateFormulaToLocale` and `_translateFormulaEnglish` are replaced by versions without leading underscore. [PR #3828](https://github.com/PHPOffice/PhpSpreadsheet/pull/3828) ### Removed @@ -78,7 +78,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). - Html omitting some charts. [Issue #3767](https://github.com/PHPOffice/PhpSpreadsheet/issues/3767) [PR #3771](https://github.com/PHPOffice/PhpSpreadsheet/pull/3771) - Case Insensitive Comparison for Sheet Names [PR #3791](https://github.com/PHPOffice/PhpSpreadsheet/pull/3791) - Performance improvement for Xlsx Reader. [Issue #3683](https://github.com/PHPOffice/PhpSpreadsheet/issues/3683) [PR #3810](https://github.com/PHPOffice/PhpSpreadsheet/pull/3810) -- Strip `xlfn.` and `xlws.` from Formula Translations. [Issue #3819](https://github.com/PHPOffice/PhpSpreadsheet/issues/3819) [PR #3829](https://github.com/PHPOffice/PhpSpreadsheet/pull/3829) +- Strip `xlfn.` and `xlws.` from Formula Translations. [Issue #3819](https://github.com/PHPOffice/PhpSpreadsheet/issues/3819) [PR #3828](https://github.com/PHPOffice/PhpSpreadsheet/pull/3828) - Prevent loop in Shared/File. [Issue #3807](https://github.com/PHPOffice/PhpSpreadsheet/issues/3807) [PR #3809](https://github.com/PHPOffice/PhpSpreadsheet/pull/3809) ## 1.29.0 - 2023-06-15 From b6aa45659cf1de29a01e58848bfa1d19db07ff0a Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 15 Dec 2023 02:21:41 -0800 Subject: [PATCH 3/4] Slightly Improved Regex Use lookahead assertion to find xlfn/xlws only when preceding function name. --- src/PhpSpreadsheet/Calculation/Calculation.php | 4 +++- .../Calculation/TranslationTest.php | 2 +- tests/data/Calculation/Translations.php | 10 ++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 3951894571..547d2175a3 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -34,6 +34,8 @@ class Calculation const CALCULATION_REGEXP_OPENBRACE = '\('; // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it) const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?(?:_xlws\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\('; + // Strip xlfn and xlws prefixes from function name + const CALCULATION_REGEXP_STRIP_XLFN_XLWS = '/(_xlfn[.])?(_xlws[.])?(?=[\p{L}][\p{L}\p{N}\.]*[\s]*[(])/'; // Cell reference (cell or range of cells, with or without a sheet reference) const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; // Cell reference (with or without a sheet reference) ensuring absolute/relative @@ -3336,7 +3338,7 @@ public function _translateFormulaToLocale(string $formula): string public function translateFormulaToLocale(string $formula): string { - $formula = preg_replace('/_(xlfn|xlws)[.]/', '', $formula) ?? ''; + $formula = preg_replace(self::CALCULATION_REGEXP_STRIP_XLFN_XLWS, '', $formula) ?? ''; // Build list of function names and constants for translation if (self::$functionReplaceFromExcel === null) { self::$functionReplaceFromExcel = []; diff --git a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php index 714b938e9e..ec9a2acaf7 100644 --- a/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/TranslationTest.php @@ -47,7 +47,7 @@ public function testTranslation(string $expectedResult, string $locale, string $ self::assertSame($expectedResult, $translatedFormula); $restoredFormula = Calculation::getInstance()->_translateFormulaToEnglish($translatedFormula); - self::assertSame(preg_replace('/_(xlfn|xlws)[.]/', '', $formula), $restoredFormula); + self::assertSame(preg_replace(Calculation::CALCULATION_REGEXP_STRIP_XLFN_XLWS, '', $formula), $restoredFormula); } public static function providerTranslations(): array diff --git a/tests/data/Calculation/Translations.php b/tests/data/Calculation/Translations.php index dee3826625..ce5370f392 100644 --- a/tests/data/Calculation/Translations.php +++ b/tests/data/Calculation/Translations.php @@ -102,4 +102,14 @@ 'ru', '=_xlfn._xlws.FILTER(A5:D20,C5:C20=H2,"")', ], + 'implicit intersection' => [ + '=@INDEKS(A1:A10;B1)', + 'nb', + '=@INDEX(A1:A10,B1)', + ], + 'preserve literal _xlfn.' => [ + '=@INDEKS(A1:A10;"_xlfn.")', + 'nb', + '=@INDEX(A1:A10,"_xlfn.")', + ], ]; From c4da79e09ae07d77487bf54aec3db8e60023a454 Mon Sep 17 00:00:00 2001 From: oleibman <10341515+oleibman@users.noreply.github.com> Date: Fri, 15 Dec 2023 08:18:35 -0800 Subject: [PATCH 4/4] Ignore Coverage of Deprecated Functions --- src/PhpSpreadsheet/Calculation/Calculation.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 547d2175a3..03800f08de 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -3330,6 +3330,8 @@ private static function translateFormula(array $from, array $to, string $formula /** * @deprecated 1.30.0 use translateFormulaToLocale() instead + * + * @codeCoverageIgnore */ public function _translateFormulaToLocale(string $formula): string { @@ -3377,6 +3379,8 @@ public function translateFormulaToLocale(string $formula): string /** * @deprecated 1.30.0 use translateFormulaToEnglish() instead + * + * @codeCoverageIgnore */ public function _translateFormulaToEnglish(string $formula): string {