generated from jric11/baseProject
Codex's updates...
This commit is contained in:
+180
-37
@@ -3,6 +3,8 @@
|
||||
namespace PhpOffice\PhpSpreadsheet\Shared;
|
||||
|
||||
use Composer\Pcre\Preg;
|
||||
use IntlCalendar;
|
||||
use NumberFormatter;
|
||||
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
|
||||
use PhpOffice\PhpSpreadsheet\Exception as SpreadsheetException;
|
||||
use Stringable;
|
||||
@@ -237,57 +239,76 @@ class StringHelper
|
||||
/**
|
||||
* Decimal separator.
|
||||
*/
|
||||
private static ?string $decimalSeparator = null;
|
||||
protected static ?string $decimalSeparator = null;
|
||||
|
||||
/**
|
||||
* Thousands separator.
|
||||
*/
|
||||
private static ?string $thousandsSeparator = null;
|
||||
protected static ?string $thousandsSeparator = null;
|
||||
|
||||
/**
|
||||
* Currency code.
|
||||
*/
|
||||
private static ?string $currencyCode = null;
|
||||
protected static ?string $currencyCode = null;
|
||||
|
||||
/**
|
||||
* Is iconv extension avalable?
|
||||
* Is iconv extension available?
|
||||
*/
|
||||
private static ?bool $isIconvEnabled = null;
|
||||
protected static ?bool $isIconvEnabled = null;
|
||||
|
||||
/**
|
||||
* iconv options.
|
||||
*/
|
||||
private static string $iconvOptions = '//IGNORE//TRANSLIT';
|
||||
protected static string $iconvOptions = '//IGNORE//TRANSLIT';
|
||||
|
||||
/** @var string[] */
|
||||
protected static array $iconvOptionsArray = ['//IGNORE//TRANSLIT', '//IGNORE'];
|
||||
|
||||
/** @internal */
|
||||
protected static string $iconvName = 'iconv';
|
||||
|
||||
/** @internal */
|
||||
protected static bool $iconvTest2 = false;
|
||||
|
||||
/** @internal */
|
||||
protected static bool $iconvTest3 = false;
|
||||
|
||||
/**
|
||||
* Get whether iconv extension is available.
|
||||
*/
|
||||
public static function getIsIconvEnabled(): bool
|
||||
{
|
||||
if (isset(self::$isIconvEnabled)) {
|
||||
return self::$isIconvEnabled;
|
||||
if (isset(static::$isIconvEnabled)) {
|
||||
return static::$isIconvEnabled;
|
||||
}
|
||||
|
||||
// Assume no problems with iconv
|
||||
self::$isIconvEnabled = true;
|
||||
static::$isIconvEnabled = true;
|
||||
|
||||
// Fail if iconv doesn't exist
|
||||
if (!function_exists('iconv')) {
|
||||
self::$isIconvEnabled = false;
|
||||
} elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
|
||||
if (!function_exists(static::$iconvName)) {
|
||||
static::$isIconvEnabled = false;
|
||||
} elseif (static::$iconvTest2 || !@iconv('UTF-8', 'UTF-16LE', 'x')) {
|
||||
// Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
|
||||
self::$isIconvEnabled = false;
|
||||
} elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
|
||||
static::$isIconvEnabled = false;
|
||||
} elseif (static::$iconvTest3 || (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0))) {
|
||||
// CUSTOM: IBM AIX iconv() does not work
|
||||
self::$isIconvEnabled = false;
|
||||
static::$isIconvEnabled = false;
|
||||
}
|
||||
|
||||
// Deactivate iconv default options if they fail (as seen on IMB i)
|
||||
if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
|
||||
self::$iconvOptions = '';
|
||||
// Deactivate iconv default options if they fail (as seen on IBM i-series)
|
||||
if (static::$isIconvEnabled) {
|
||||
static::$iconvOptions = '';
|
||||
foreach (static::$iconvOptionsArray as $option) {
|
||||
if (@iconv('UTF-8', 'UTF-16LE' . $option, 'x') !== false) {
|
||||
static::$iconvOptions = $option;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self::$isIconvEnabled;
|
||||
return static::$isIconvEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -305,7 +326,47 @@ class StringHelper
|
||||
*/
|
||||
public static function controlCharacterOOXML2PHP(string $textValue): string
|
||||
{
|
||||
return str_replace(self::CONTROL_CHARACTERS_VALUES, self::CONTROL_CHARACTERS_KEYS, $textValue);
|
||||
return Preg::replaceCallback('/_x[0-9A-F]{4}_(_xD[CDEF][0-9A-F]{2}_)?/', self::toOutChar(...), $textValue);
|
||||
}
|
||||
|
||||
private static function toHexVal(string $char): int
|
||||
{
|
||||
if ($char >= '0' && $char <= '9') {
|
||||
return ord($char) - ord('0');
|
||||
}
|
||||
|
||||
return ord($char) - ord('A') + 10;
|
||||
}
|
||||
|
||||
/** @param array<?string> $match */
|
||||
private static function toOutChar(array $match): string
|
||||
{
|
||||
/** @var string */
|
||||
$chars = $match[0];
|
||||
$h = ((self::toHexVal($chars[2]) << 12)
|
||||
| (self::toHexVal($chars[3]) << 8)
|
||||
| (self::toHexVal($chars[4]) << 4)
|
||||
| (self::toHexVal($chars[5])));
|
||||
if (strlen($chars) === 7) { // no low surrogate
|
||||
if ($chars[2] === 'D' && in_array($chars[3], ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F'], true)) {
|
||||
return '�';
|
||||
}
|
||||
|
||||
return mb_chr($h, 'UTF-8');
|
||||
}
|
||||
if ($chars[2] === 'D' && in_array($chars[3], ['C', 'D', 'D', 'F'], true)) {
|
||||
return '�'; // Excel interprets as one substitute, not 2
|
||||
}
|
||||
if ($chars[2] !== 'D' || !in_array($chars[3], ['8', '9', 'A', 'B'], true)) {
|
||||
return mb_chr($h, 'UTF-8') . '�';
|
||||
}
|
||||
$l = ((self::toHexVal($chars[9]) << 12)
|
||||
| (self::toHexVal($chars[10]) << 8)
|
||||
| (self::toHexVal($chars[11]) << 4)
|
||||
| (self::toHexVal($chars[12])));
|
||||
$result = 0x10000 + ($h - 0xD800) * 0x400 + ($l - 0xDC00);
|
||||
|
||||
return mb_chr($result, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -323,6 +384,8 @@ class StringHelper
|
||||
*/
|
||||
public static function controlCharacterPHP2OOXML(string $textValue): string
|
||||
{
|
||||
$textValue = Preg::replace('/_(x[0-9A-F]{4}_)/', '_x005F_$1', $textValue);
|
||||
|
||||
return str_replace(self::CONTROL_CHARACTERS_KEYS, self::CONTROL_CHARACTERS_VALUES, $textValue);
|
||||
}
|
||||
|
||||
@@ -418,10 +481,10 @@ class StringHelper
|
||||
* @param string $to Encoding to convert to, e.g. 'UTF-8'
|
||||
* @param string $from Encoding to convert from, e.g. 'UTF-16LE'
|
||||
*/
|
||||
public static function convertEncoding(string $textValue, string $to, string $from): string
|
||||
public static function convertEncoding(string $textValue, string $to, string $from, ?string $options = null): string
|
||||
{
|
||||
if (static::getIsIconvEnabled()) {
|
||||
$result = iconv($from, $to . self::$iconvOptions, $textValue);
|
||||
$result = iconv($from, $to . ($options ?? static::$iconvOptions), $textValue);
|
||||
if (false !== $result) {
|
||||
return $result;
|
||||
}
|
||||
@@ -561,11 +624,11 @@ class StringHelper
|
||||
*/
|
||||
public static function getDecimalSeparator(): string
|
||||
{
|
||||
if (!isset(self::$decimalSeparator)) {
|
||||
self::$decimalSeparator = self::getLocaleValue('decimal_point', 'mon_decimal_point', '.');
|
||||
if (!isset(static::$decimalSeparator)) {
|
||||
static::$decimalSeparator = self::getLocaleValue('decimal_point', 'mon_decimal_point', '.');
|
||||
}
|
||||
|
||||
return self::$decimalSeparator;
|
||||
return static::$decimalSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -576,7 +639,7 @@ class StringHelper
|
||||
*/
|
||||
public static function setDecimalSeparator(?string $separator): void
|
||||
{
|
||||
self::$decimalSeparator = $separator;
|
||||
static::$decimalSeparator = $separator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -585,11 +648,11 @@ class StringHelper
|
||||
*/
|
||||
public static function getThousandsSeparator(): string
|
||||
{
|
||||
if (!isset(self::$thousandsSeparator)) {
|
||||
self::$thousandsSeparator = self::getLocaleValue('thousands_sep', 'mon_thousands_sep', ',');
|
||||
if (!isset(static::$thousandsSeparator)) {
|
||||
static::$thousandsSeparator = self::getLocaleValue('thousands_sep', 'mon_thousands_sep', ',');
|
||||
}
|
||||
|
||||
return self::$thousandsSeparator;
|
||||
return static::$thousandsSeparator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -600,7 +663,7 @@ class StringHelper
|
||||
*/
|
||||
public static function setThousandsSeparator(?string $separator): void
|
||||
{
|
||||
self::$thousandsSeparator = $separator;
|
||||
static::$thousandsSeparator = $separator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,11 +672,11 @@ class StringHelper
|
||||
*/
|
||||
public static function getCurrencyCode(bool $trimAlt = false): string
|
||||
{
|
||||
if (!isset(self::$currencyCode)) {
|
||||
self::$currencyCode = self::getLocaleValue('currency_symbol', 'int_curr_symbol', '$', $trimAlt);
|
||||
if (!isset(static::$currencyCode)) {
|
||||
static::$currencyCode = self::getLocaleValue('currency_symbol', 'int_curr_symbol', '$', $trimAlt);
|
||||
}
|
||||
|
||||
return self::$currencyCode;
|
||||
return static::$currencyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -624,7 +687,7 @@ class StringHelper
|
||||
*/
|
||||
public static function setCurrencyCode(?string $currencyCode): void
|
||||
{
|
||||
self::$currencyCode = $currencyCode;
|
||||
static::$currencyCode = $currencyCode;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -724,16 +787,96 @@ class StringHelper
|
||||
* Php introduced str_increment with Php8.3,
|
||||
* but didn't issue deprecation notices till 8.5.
|
||||
*
|
||||
* @param-out string $str
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
public static function stringIncrement(string &$str): string
|
||||
{
|
||||
if (function_exists('str_increment')) {
|
||||
$str = str_increment($str); // @phpstan-ignore-line
|
||||
/** @var non-empty-string $str */
|
||||
$str2 = str_increment($str);
|
||||
} else {
|
||||
++$str; // @phpstan-ignore-line
|
||||
$str1 = $str;
|
||||
/**
|
||||
* This is an outright lie, but I don't know how else to satisfy Phpstan for Php8.5+.
|
||||
*
|
||||
* @var numeric-string $str1
|
||||
*/
|
||||
++$str1;
|
||||
$str2 = "$str1";
|
||||
}
|
||||
|
||||
return $str; // @phpstan-ignore-line
|
||||
/**
|
||||
* This is demonstrably the case. I don't know why Phpstan thinks $str2 is mixed at this point.
|
||||
*
|
||||
* @var string $str2 set above by str_increment or ="..."
|
||||
*/
|
||||
$str = $str2;
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
protected static string $testClass = IntlCalendar::class;
|
||||
|
||||
/**
|
||||
* Set all of currencyCode, thousandsSeparator, decimalSeparator,
|
||||
* and Calculation locale with a single call.
|
||||
* The main point here is avoid the use of Php setlocale,
|
||||
* which is not threadsafe. It uses the Intl extension instead,
|
||||
* which is not a requirement for PhpSpreadsheet.
|
||||
* Because of that, the function returns a bool which will
|
||||
* be false if Intl is not available, or the supplied locale
|
||||
* is not valid according to Intl.
|
||||
*/
|
||||
public static function setLocale(?string $locale): bool
|
||||
{
|
||||
if ($locale === null) {
|
||||
self::$currencyCode = null;
|
||||
self::$thousandsSeparator = null;
|
||||
self::$decimalSeparator = null;
|
||||
Calculation::getInstance()->setLocale('en_us');
|
||||
|
||||
return true;
|
||||
}
|
||||
$localeCalc = $locale;
|
||||
if (Preg::isMatch('/^([a-z][a-z])_([a-z][a-z])(?:[.]utf-8)?$/i', $locale, $matches)) {
|
||||
$locale = strtolower($matches[1]) . '_' . strtoupper($matches[2]);
|
||||
$localeCalc = strtolower($matches[1]) . '_' . strtolower($matches[2]);
|
||||
}
|
||||
if (!class_exists(static::$testClass)) {
|
||||
return false;
|
||||
}
|
||||
// NumberFormatter constructor succeeds even with
|
||||
// bad locale before Php8.4, so try to validate
|
||||
// the locale beforehand.
|
||||
$locales = IntlCalendar::getAvailableLocales();
|
||||
if (!in_array($locale, $locales, true)) {
|
||||
return false;
|
||||
}
|
||||
$formatter = new NumberFormatter(
|
||||
$locale,
|
||||
NumberFormatter::CURRENCY
|
||||
);
|
||||
$currency = $formatter->getSymbol(
|
||||
NumberFormatter::CURRENCY_SYMBOL
|
||||
);
|
||||
$formatter = new NumberFormatter(
|
||||
$locale,
|
||||
NumberFormatter::DECIMAL
|
||||
);
|
||||
$thousands = $formatter->getSymbol(
|
||||
NumberFormatter::GROUPING_SEPARATOR_SYMBOL
|
||||
);
|
||||
$decimal = $formatter->getSymbol(
|
||||
NumberFormatter::DECIMAL_SEPARATOR_SYMBOL
|
||||
);
|
||||
self::$currencyCode = $currency;
|
||||
self::$thousandsSeparator = $thousands;
|
||||
self::$decimalSeparator = $decimal;
|
||||
Calculation::getInstance()->setLocale($localeCalc);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user