Resolve `translateSeparator()` method to handle separators (row and column) for array functions as well as for function argument separators; and cleanly handle nesting levels
Note that this method is used when translating Excel functions between en and other locale languages, as well as when converting formulae between different spreadsheet formats (e.g. Ods to Excel) Nor is this a perfect solution, as there may still be issues when function calls have array arguments that themselves contain function calls; but it's still better than the current logic
This commit is contained in:
parent
e6047cfa9d
commit
99f488efc6
|
|
@ -162,7 +162,7 @@ parameters:
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
|
message: "#^Parameter \\#3 \\$formula of static method PhpOffice\\\\PhpSpreadsheet\\\\Calculation\\\\Calculation\\:\\:translateSeparator\\(\\) expects string, string\\|null given\\.$#"
|
||||||
count: 2
|
count: 1
|
||||||
path: src/PhpSpreadsheet/Calculation/Calculation.php
|
path: src/PhpSpreadsheet/Calculation/Calculation.php
|
||||||
|
|
||||||
-
|
-
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,10 @@ class Calculation
|
||||||
const RETURN_ARRAY_AS_VALUE = 'value';
|
const RETURN_ARRAY_AS_VALUE = 'value';
|
||||||
const RETURN_ARRAY_AS_ARRAY = 'array';
|
const RETURN_ARRAY_AS_ARRAY = 'array';
|
||||||
|
|
||||||
const FORMULA_OPEN_FUNCTION_BRACE = '{';
|
const FORMULA_OPEN_FUNCTION_BRACE = '(';
|
||||||
const FORMULA_CLOSE_FUNCTION_BRACE = '}';
|
const FORMULA_CLOSE_FUNCTION_BRACE = ')';
|
||||||
|
const FORMULA_OPEN_MATRIX_BRACE = '{';
|
||||||
|
const FORMULA_CLOSE_MATRIX_BRACE = '}';
|
||||||
const FORMULA_STRING_QUOTE = '"';
|
const FORMULA_STRING_QUOTE = '"';
|
||||||
|
|
||||||
private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
|
private static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE;
|
||||||
|
|
@ -3084,30 +3086,28 @@ class Calculation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static function translateSeparator(
|
||||||
* @param string $fromSeparator
|
string $fromSeparator,
|
||||||
* @param string $toSeparator
|
string $toSeparator,
|
||||||
* @param string $formula
|
string $formula,
|
||||||
* @param bool $inBraces
|
int &$inBracesLevel,
|
||||||
*
|
string $openBrace = self::FORMULA_OPEN_FUNCTION_BRACE,
|
||||||
* @return string
|
string $closeBrace = self::FORMULA_CLOSE_FUNCTION_BRACE
|
||||||
*/
|
): string {
|
||||||
public static function translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
|
|
||||||
{
|
|
||||||
$strlen = mb_strlen($formula);
|
$strlen = mb_strlen($formula);
|
||||||
for ($i = 0; $i < $strlen; ++$i) {
|
for ($i = 0; $i < $strlen; ++$i) {
|
||||||
$chr = mb_substr($formula, $i, 1);
|
$chr = mb_substr($formula, $i, 1);
|
||||||
switch ($chr) {
|
switch ($chr) {
|
||||||
case self::FORMULA_OPEN_FUNCTION_BRACE:
|
case $openBrace:
|
||||||
$inBraces = true;
|
++$inBracesLevel;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case self::FORMULA_CLOSE_FUNCTION_BRACE:
|
case $closeBrace:
|
||||||
$inBraces = false;
|
--$inBracesLevel;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case $fromSeparator:
|
case $fromSeparator:
|
||||||
if (!$inBraces) {
|
if ($inBracesLevel > 0) {
|
||||||
$formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
|
$formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3116,31 +3116,47 @@ class Calculation
|
||||||
return $formula;
|
return $formula;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static function translateFormulaBlock(
|
||||||
* @param string[] $from
|
array $from,
|
||||||
* @param string[] $to
|
array $to,
|
||||||
* @param string $formula
|
string $formula,
|
||||||
* @param string $fromSeparator
|
int &$inFunctionBracesLevel,
|
||||||
* @param string $toSeparator
|
int &$inMatrixBracesLevel,
|
||||||
*
|
string $fromSeparator,
|
||||||
* @return string
|
string $toSeparator
|
||||||
*/
|
): string {
|
||||||
private static function translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)
|
// Function Names
|
||||||
|
$formula = preg_replace($from, $to, $formula);
|
||||||
|
|
||||||
|
// Temporarily adjust matrix separators so that they won't be confused with function arguments
|
||||||
|
$formula = self::translateSeparator(';', '|', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
$formula = self::translateSeparator(',', '!', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
// Function Argument Separators
|
||||||
|
$formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inFunctionBracesLevel);
|
||||||
|
// Restore matrix separators
|
||||||
|
$formula = self::translateSeparator('|', ';', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
$formula = self::translateSeparator('!', ',', $formula, $inMatrixBracesLevel, self::FORMULA_OPEN_MATRIX_BRACE, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
|
||||||
|
return $formula;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function translateFormula(array $from, array $to, string $formula, string $fromSeparator, string $toSeparator): string
|
||||||
{
|
{
|
||||||
// Convert any Excel function names to the required language
|
// Convert any Excel function names and constant names to the required language;
|
||||||
|
// and adjust function argument separators
|
||||||
if (self::$localeLanguage !== 'en_us') {
|
if (self::$localeLanguage !== 'en_us') {
|
||||||
$inBraces = false;
|
$inFunctionBracesLevel = 0;
|
||||||
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
$inMatrixBracesLevel = 0;
|
||||||
|
// If there is the possibility of separators within a quoted string, then we treat them as literals
|
||||||
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
||||||
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
// So instead we skip replacing in any quoted strings by only replacing in every other array element
|
||||||
// the formula
|
// after we've exploded the formula
|
||||||
$temp = explode(self::FORMULA_STRING_QUOTE, $formula);
|
$temp = explode(self::FORMULA_STRING_QUOTE, $formula);
|
||||||
$i = false;
|
$i = false;
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only count/replace in alternating array entries
|
// Only adjust in alternating array entries
|
||||||
if ($i = !$i) {
|
if ($i = !$i) {
|
||||||
$value = preg_replace($from, $to, $value);
|
$value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
||||||
$value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unset($value);
|
unset($value);
|
||||||
|
|
@ -3148,8 +3164,7 @@ class Calculation
|
||||||
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
||||||
} else {
|
} else {
|
||||||
// If there's no quoted strings, then we do a simple count/replace
|
// If there's no quoted strings, then we do a simple count/replace
|
||||||
$formula = preg_replace($from, $to, $formula);
|
$formula = self::translateFormulaBlock($from, $to, $formula, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
||||||
$formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3162,6 +3177,7 @@ class Calculation
|
||||||
|
|
||||||
public function _translateFormulaToLocale($formula)
|
public function _translateFormulaToLocale($formula)
|
||||||
{
|
{
|
||||||
|
// Build list of function names and constants for translation
|
||||||
if (self::$functionReplaceFromExcel === null) {
|
if (self::$functionReplaceFromExcel === null) {
|
||||||
self::$functionReplaceFromExcel = [];
|
self::$functionReplaceFromExcel = [];
|
||||||
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
|
foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
|
||||||
|
|
@ -3798,11 +3814,11 @@ class Calculation
|
||||||
*/
|
*/
|
||||||
private function convertMatrixReferences($formula)
|
private function convertMatrixReferences($formula)
|
||||||
{
|
{
|
||||||
static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE];
|
static $matrixReplaceFrom = [self::FORMULA_OPEN_MATRIX_BRACE, ';', self::FORMULA_CLOSE_MATRIX_BRACE];
|
||||||
static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
|
static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
|
||||||
|
|
||||||
// Convert any Excel matrix references to the MKMATRIX() function
|
// Convert any Excel matrix references to the MKMATRIX() function
|
||||||
if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) {
|
if (strpos($formula, self::FORMULA_OPEN_MATRIX_BRACE) !== false) {
|
||||||
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
// If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
|
||||||
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
|
||||||
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
// So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
|
||||||
|
|
@ -3814,8 +3830,8 @@ class Calculation
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only count/replace in alternating array entries
|
// Only count/replace in alternating array entries
|
||||||
if ($i = !$i) {
|
if ($i = !$i) {
|
||||||
$openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE);
|
$openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE);
|
||||||
$closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE);
|
$closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
$value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
|
$value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3824,8 +3840,8 @@ class Calculation
|
||||||
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
$formula = implode(self::FORMULA_STRING_QUOTE, $temp);
|
||||||
} else {
|
} else {
|
||||||
// If there's no quoted strings, then we do a simple count/replace
|
// If there's no quoted strings, then we do a simple count/replace
|
||||||
$openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE);
|
$openCount = substr_count($formula, self::FORMULA_OPEN_MATRIX_BRACE);
|
||||||
$closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE);
|
$closeCount = substr_count($formula, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
$formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
|
$formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
|
||||||
}
|
}
|
||||||
// Trap for mismatched braces and trigger an appropriate error
|
// Trap for mismatched braces and trigger an appropriate error
|
||||||
|
|
|
||||||
|
|
@ -75,4 +75,9 @@ return [
|
||||||
'tr',
|
'tr',
|
||||||
'=WORKDAY.INTL(B1)',
|
'=WORKDAY.INTL(B1)',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'=STØRST(ABS({2,-3;-4,5}); ABS{-2,3;4,-5})',
|
||||||
|
'nb',
|
||||||
|
'=MAX(ABS({2,-3;-4,5}), ABS{-2,3;4,-5})',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue