From 625a87b2765dcc89957ff7bdd3eb7972ee2cd7e2 Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 13:16:13 +0200
Subject: [PATCH 1/7] Resolve use of UTF-8 in defined names in the calculation
 engine

---
 .../Calculation/Calculation.php               | 21 ++++++++++---------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index 2d67a1347a..01e1a9db93 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -24,11 +24,11 @@ class Calculation
     //    Opening bracket
     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\.)?([A-Z][A-Z0-9\.]*)[\s]*\(';
+    const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\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.])';
     //    Named Range of cells
-    const CALCULATION_REGEXP_NAMEDRANGE = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_A-Z][_A-Z0-9\.]*)';
+    const CALCULATION_REGEXP_NAMEDRANGE = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)';
     //    Error
     const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?';
 
@@ -3395,7 +3395,7 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                                 '|' . self::CALCULATION_REGEXP_OPENBRACE .
                                 '|' . self::CALCULATION_REGEXP_NAMEDRANGE .
                                 '|' . self::CALCULATION_REGEXP_ERROR .
-                                ')/si';
+                                ')/sui';
 
         //    Start with initialisation
         $index = 0;
@@ -3499,7 +3499,7 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                     --$parenthesisDepthMap[$pendingStoreKey];
                 }
 
-                if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
+                if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) {    //    Did this parenthesis just close a function?
                     if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) {
                         // we are closing an IF(
                         if ($d['value'] != 'IF(') {
@@ -3602,7 +3602,7 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                 }
                 // make sure there was a function
                 $d = $stack->last(2);
-                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $d['value'], $matches)) {
+                if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) {
                     return $this->raiseFormulaError('Formula Error: Unexpected ,');
                 }
                 $d = $stack->pop();
@@ -3625,7 +3625,7 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                 $expectingOperand = false;
                 $val = $match[1];
                 $length = strlen($val);
-                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $val, $matches)) {
+                if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) {
                     $val = preg_replace('/\s/u', '', $val);
                     if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) {    // it's a function
                         $valToUpper = strtoupper($val);
@@ -3733,7 +3733,8 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                     } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
                         $stackItemType = 'Constant';
                         $val = self::$excelConstants[$localeConstant];
-                    } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/Ui', $val, $match)) {
+                    } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/miu', $val, $match)) {
+                        echo "NAMED RANGE MATCH '{$val}'", PHP_EOL;
                         $stackItemType = 'Named Range';
                         $stackItemReference = $val;
                     }
@@ -3782,7 +3783,7 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                 if (($expectingOperator) &&
                     ((preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
                         ($output[count($output) - 1]['type'] == 'Cell Reference') ||
-                        (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/Ui', substr($formula, $index), $match)) &&
+                        (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/miu', substr($formula, $index), $match)) &&
                             ($output[count($output) - 1]['type'] == 'Named Range' || $output[count($output) - 1]['type'] == 'Value')
                     )) {
                     while ($stack->count() > 0 &&
@@ -4208,7 +4209,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
                 }
 
                 // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
-            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
+            } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token, $matches)) {
                 if ($pCellParent) {
                     $pCell->attach($pCellParent);
                 }
@@ -4306,7 +4307,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
                         $branchStore[$storeKey] = $token;
                     }
                     // if the token is a named range, push the named range name onto the stack
-                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
+                } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/miu', $token, $matches)) {
                     $namedRange = $matches[6];
                     $this->debugLog->writeDebugLog('Evaluating Named Range ', $namedRange);
 

From d9a50a762dfda92a32335f3ceac8862c98993d46 Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:05:52 +0200
Subject: [PATCH 2/7] Remove diagnostic output

---
 src/PhpSpreadsheet/Calculation/Calculation.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php
index 01e1a9db93..848f9068f9 100644
--- a/src/PhpSpreadsheet/Calculation/Calculation.php
+++ b/src/PhpSpreadsheet/Calculation/Calculation.php
@@ -3734,7 +3734,6 @@ private function _parseFormula($formula, ?Cell $pCell = null)
                         $stackItemType = 'Constant';
                         $val = self::$excelConstants[$localeConstant];
                     } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '.*/miu', $val, $match)) {
-                        echo "NAMED RANGE MATCH '{$val}'", PHP_EOL;
                         $stackItemType = 'Named Range';
                         $stackItemReference = $val;
                     }

From 01a0bc7da5113b86654ef8e0aca818be0d4f1d43 Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:08:36 +0200
Subject: [PATCH 3/7] Tests for UTF-8 Named Ranges

---
 .../Calculation/Engine/RangeTest.php          | 30 +++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
index ee566db26b..64125c8c55 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
@@ -97,6 +97,36 @@ public function providerNamedRangeEvaluation()
         ];
     }
 
+    /**
+     * @dataProvider providerUTF8NamedRangeEvaluation
+     *
+     * @param string $group1
+     * @param string $group2
+     * @param string $formula
+     * @param int $expectedResult
+     */
+    public function testUTF8NamedRangeEvaluation($names, $ranges,$formula, $expectedResult): void
+    {
+        $workSheet = $this->spreadSheet->getActiveSheet();
+        foreach ($names as $index => $name) {
+            $range = $ranges[$index];
+            $this->spreadSheet->addNamedRange(new NamedRange($name, $workSheet, $range));
+        }
+        $workSheet->setCellValue('E1', $formula);
+
+        $sumRresult = $workSheet->getCell('E1')->getCalculatedValue();
+        self::assertSame($expectedResult, $sumRresult);
+    }
+
+    public function providerUTF8NamedRangeEvaluation()
+    {
+        return[
+            [['Γειά', 'σου', 'Κόσμε'], ['A1', 'B1:B2', 'C1:C3'], '=SUM(Γειά,σου,Κόσμε)', 26],
+            [['Γειά', 'σου', 'Κόσμε'], ['A1', 'B1:B2', 'C1:C3'], '=COUNT(Γειά,σου,Κόσμε)', 6],
+            [['Здравствуй', 'мир'], ['A1:A3', 'C1:C3'], '=SUM(Здравствуй,мир)', 30]
+        ];
+    }
+
     /**
      * @dataProvider providerCompositeNamedRangeEvaluation
      *

From c3392d79c7f1b5e325f93fc9bf733657eb5bf155 Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:10:25 +0200
Subject: [PATCH 4/7] Docblock fixes

---
 tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
index 64125c8c55..2600a34742 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
@@ -100,8 +100,8 @@ public function providerNamedRangeEvaluation()
     /**
      * @dataProvider providerUTF8NamedRangeEvaluation
      *
-     * @param string $group1
-     * @param string $group2
+     * @param string $names
+     * @param string $ranges
      * @param string $formula
      * @param int $expectedResult
      */

From a75bc565278bb043f27dfd5e82784dec711011cd Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:17:10 +0200
Subject: [PATCH 5/7] Fix phpcs error

---
 tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
index 2600a34742..d33db6d63e 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
@@ -105,7 +105,7 @@ public function providerNamedRangeEvaluation()
      * @param string $formula
      * @param int $expectedResult
      */
-    public function testUTF8NamedRangeEvaluation($names, $ranges,$formula, $expectedResult): void
+    public function testUTF8NamedRangeEvaluation($names, $ranges, $formula, $expectedResult): void
     {
         $workSheet = $this->spreadSheet->getActiveSheet();
         foreach ($names as $index => $name) {

From ac192b824d5a98fe170ae4712fe63e9fc143c56d Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:25:16 +0200
Subject: [PATCH 6/7] Fix phpcs error (trailing comma)

---
 tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
index d33db6d63e..e2e6e11d23 100644
--- a/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
+++ b/tests/PhpSpreadsheetTests/Calculation/Engine/RangeTest.php
@@ -123,7 +123,7 @@ public function providerUTF8NamedRangeEvaluation()
         return[
             [['Γειά', 'σου', 'Κόσμε'], ['A1', 'B1:B2', 'C1:C3'], '=SUM(Γειά,σου,Κόσμε)', 26],
             [['Γειά', 'σου', 'Κόσμε'], ['A1', 'B1:B2', 'C1:C3'], '=COUNT(Γειά,σου,Κόσμε)', 6],
-            [['Здравствуй', 'мир'], ['A1:A3', 'C1:C3'], '=SUM(Здравствуй,мир)', 30]
+            [['Здравствуй', 'мир'], ['A1:A3', 'C1:C3'], '=SUM(Здравствуй,мир)', 30],
         ];
     }
 

From cd3d5468ee4bdeb60eda8105cf05433b90769c3c Mon Sep 17 00:00:00 2001
From: MarkBaker <mark@lange.demon.co.uk>
Date: Sat, 13 Jun 2020 14:34:36 +0200
Subject: [PATCH 7/7] Update changelog

---
 CHANGELOG.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c13985021..37f4f3dc15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com)
 and this project adheres to [Semantic Versioning](https://semver.org).
 
+## [Unreleased]
+
+### Fixed
+
+- Resolve evaluation of utf-8 named ranges in calculation engine [#1522](https://github.com/PHPOffice/PhpSpreadsheet/pull/1522)
+
 ## [1.13.0] - 2020-05-31
 
 ### Added