| 1 |
<?php |
| 2 |
|
| 3 |
namespace TablePress\PhpOffice\PhpSpreadsheet\Calculation\Engine; |
| 4 |
|
| 5 |
use TablePress\PhpOffice\PhpSpreadsheet\Calculation\Calculation; |
| 6 |
use TablePress\PhpOffice\PhpSpreadsheet\Shared\StringHelper; |
| 7 |
|
| 8 |
class FormattedNumber |
| 9 |
{ |
| 10 |
/** Constants */ |
| 11 |
/** Regular Expressions */ |
| 12 |
private const STRING_REGEXP_FRACTION = '~^\s*(-?)((\d*)\s+)?(\d+\/\d+)\s*$~'; |
| 13 |
|
| 14 |
private const STRING_REGEXP_PERCENT = '~^(?:(?: *(?<PrefixedSign>[-+])? *\% *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+\.?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+\.?[0-9]*(?:E[-+]?[0-9]*)?) *\% *))$~i'; |
| 15 |
|
| 16 |
// preg_quoted string for major currency symbols, with a %s for locale currency |
| 17 |
private const CURRENCY_CONVERSION_LIST = '\$€£¥%s'; |
| 18 |
|
| 19 |
/** |
| 20 |
* Identify whether a string contains a formatted numeric value, |
| 21 |
* and convert it to a numeric if it is. |
| 22 |
* |
| 23 |
* @param float|string $operand string value to test |
| 24 |
*/ |
| 25 |
public static function convertToNumberIfFormatted(&$operand): bool |
| 26 |
{ |
| 27 |
return self::convertToNumberIfNumeric($operand) |
| 28 |
|| self::convertToNumberIfFraction($operand) |
| 29 |
|| self::convertToNumberIfPercent($operand) |
| 30 |
|| self::convertToNumberIfCurrency($operand); |
| 31 |
} |
| 32 |
|
| 33 |
/** |
| 34 |
* Identify whether a string contains a numeric value, |
| 35 |
* and convert it to a numeric if it is. |
| 36 |
* |
| 37 |
* @param float|string $operand string value to test |
| 38 |
*/ |
| 39 |
public static function convertToNumberIfNumeric(&$operand): bool |
| 40 |
{ |
| 41 |
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); |
| 42 |
$value = preg_replace(['/(\d)' . $thousandsSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1$2', '$1$2'], trim("$operand")); |
| 43 |
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); |
| 44 |
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); |
| 45 |
|
| 46 |
if (is_numeric($value)) { |
| 47 |
$operand = (float) $value; |
| 48 |
|
| 49 |
return true; |
| 50 |
} |
| 51 |
|
| 52 |
return false; |
| 53 |
} |
| 54 |
|
| 55 |
/** |
| 56 |
* Identify whether a string contains a fractional numeric value, |
| 57 |
* and convert it to a numeric if it is. |
| 58 |
* |
| 59 |
* @param string $operand string value to test |
| 60 |
*/ |
| 61 |
public static function convertToNumberIfFraction(&$operand): bool |
| 62 |
{ |
| 63 |
if (is_string($operand) && preg_match(self::STRING_REGEXP_FRACTION, $operand, $match)) { |
| 64 |
$sign = ($match[1] === '-') ? '-' : '+'; |
| 65 |
$wholePart = ($match[3] === '') ? '' : ($sign . $match[3]); |
| 66 |
$fractionFormula = '=' . $wholePart . $sign . $match[4]; |
| 67 |
/** @var string */ |
| 68 |
$operandx = Calculation::getInstance()->_calculateFormulaValue($fractionFormula); |
| 69 |
$operand = $operandx; |
| 70 |
|
| 71 |
return true; |
| 72 |
} |
| 73 |
|
| 74 |
return false; |
| 75 |
} |
| 76 |
|
| 77 |
/** |
| 78 |
* Identify whether a string contains a percentage, and if so, |
| 79 |
* convert it to a numeric. |
| 80 |
* |
| 81 |
* @param float|string $operand string value to test |
| 82 |
*/ |
| 83 |
public static function convertToNumberIfPercent(&$operand): bool |
| 84 |
{ |
| 85 |
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); |
| 86 |
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', trim("$operand")); |
| 87 |
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); |
| 88 |
$value = preg_replace(['/(\d)' . $decimalSeparator . '(\d)/u', '/([+-])\s+(\d)/u'], ['$1.$2', '$1$2'], $value ?? ''); |
| 89 |
|
| 90 |
$match = []; |
| 91 |
if ($value !== null && preg_match(self::STRING_REGEXP_PERCENT, $value, $match, PREG_UNMATCHED_AS_NULL)) { |
| 92 |
//Calculate the percentage |
| 93 |
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; |
| 94 |
$operand = (float) ($sign . ($match['PostfixedValue'] ?? $match['PrefixedValue'])) / 100; |
| 95 |
|
| 96 |
return true; |
| 97 |
} |
| 98 |
|
| 99 |
return false; |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Identify whether a string contains a currency value, and if so, |
| 104 |
* convert it to a numeric. |
| 105 |
* |
| 106 |
* @param float|string $operand string value to test |
| 107 |
*/ |
| 108 |
public static function convertToNumberIfCurrency(&$operand): bool |
| 109 |
{ |
| 110 |
$currencyRegexp = self::currencyMatcherRegexp(); |
| 111 |
$thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/'); |
| 112 |
$value = preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', "$operand"); |
| 113 |
|
| 114 |
$match = []; |
| 115 |
if ($value !== null && preg_match($currencyRegexp, $value, $match, PREG_UNMATCHED_AS_NULL)) { |
| 116 |
//Determine the sign |
| 117 |
$sign = ($match['PrefixedSign'] ?? $match['PrefixedSign2'] ?? $match['PostfixedSign']) ?? ''; |
| 118 |
$decimalSeparator = StringHelper::getDecimalSeparator(); |
| 119 |
//Cast to a float |
| 120 |
$intermediate = (string) ($match['PostfixedValue'] ?? $match['PrefixedValue']); |
| 121 |
$intermediate = str_replace($decimalSeparator, '.', $intermediate); |
| 122 |
if (is_numeric($intermediate)) { |
| 123 |
$operand = (float) ($sign . str_replace($decimalSeparator, '.', $intermediate)); |
| 124 |
|
| 125 |
return true; |
| 126 |
} |
| 127 |
} |
| 128 |
|
| 129 |
return false; |
| 130 |
} |
| 131 |
|
| 132 |
public static function currencyMatcherRegexp(): string |
| 133 |
{ |
| 134 |
$currencyCodes = sprintf(self::CURRENCY_CONVERSION_LIST, preg_quote(StringHelper::getCurrencyCode(), '/')); |
| 135 |
$decimalSeparator = preg_quote(StringHelper::getDecimalSeparator(), '/'); |
| 136 |
|
| 137 |
return '~^(?:(?: *(?<PrefixedSign>[-+])? *(?<PrefixedCurrency>[' . $currencyCodes . ']) *(?<PrefixedSign2>[-+])? *(?<PrefixedValue>[0-9]+[' . $decimalSeparator . ']?[0-9*]*(?:E[-+]?[0-9]*)?) *)|(?: *(?<PostfixedSign>[-+])? *(?<PostfixedValue>[0-9]+' . $decimalSeparator . '?[0-9]*(?:E[-+]?[0-9]*)?) *(?<PostfixedCurrency>[' . $currencyCodes . ']) *))$~ui'; |
| 138 |
} |
| 139 |
} |
| 140 |
|