PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.15.0
Independent Analytics – WordPress Analytics Plugin v2.15.0
2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / symfony / string / AbstractUnicodeString.php
independent-analytics / vendor / symfony / string Last commit date
Exception 2 years ago Inflector 5 days ago Resources 5 days ago Slugger 5 days ago AbstractString.php 5 days ago AbstractUnicodeString.php 5 days ago ByteString.php 5 days ago CodePointString.php 5 days ago LazyString.php 5 days ago UnicodeString.php 5 days ago
AbstractUnicodeString.php
500 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace IAWPSCOPED\Symfony\Component\String;
12
13 use IAWPSCOPED\Symfony\Component\String\Exception\ExceptionInterface;
14 use IAWPSCOPED\Symfony\Component\String\Exception\InvalidArgumentException;
15 use IAWPSCOPED\Symfony\Component\String\Exception\RuntimeException;
16 /**
17 * Represents a string of abstract Unicode characters.
18 *
19 * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
20 * This class is the abstract type to use as a type-hint when the logic you want to
21 * implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
22 *
23 * @author Nicolas Grekas <p@tchwork.com>
24 *
25 * @throws ExceptionInterface
26 * @internal
27 */
28 abstract class AbstractUnicodeString extends AbstractString
29 {
30 public const NFC = \Normalizer::NFC;
31 public const NFD = \Normalizer::NFD;
32 public const NFKC = \Normalizer::NFKC;
33 public const NFKD = \Normalizer::NFKD;
34 // all ASCII letters sorted by typical frequency of occurrence
35 private const ASCII = " eiasntrolud][cmp'\ng|hv.fb,:=-q10C2*yx)(L9AS/P\"EjMIk3>5T<D4}B{8FwR67UGN;JzV#HOW_&!K?XQ%Y\\\tZ+~^\$@`\x00\x01\x02\x03\x04\x05\x06\x07\x08\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f";
36 // the subset of folded case mappings that is not in lower case mappings
37 private const FOLD_FROM = ['İ', 'µ', 'ſ', "�
38 ", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "ι", 'ß', 'İ', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', '�
39 ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ΐ', 'ῖ', 'ῗ', 'ῢ', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', '�
40 ', '', '', '', '', '', ''];
41 private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', '�
42 ι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', '�
43 ι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', '', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', '', 'ῆι', 'ηι', '', 'ΐ', '', '', '', 'ΰ', '', '', '', 'ὼι', 'ωι', 'ώι', '', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
44 // the subset of upper case mappings that map one code point to many code points
45 private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', '�
46 ', '', 'և', '', '', '', '', '', 'ʼn', 'ΐ', 'ΰ', 'ǰ', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
47 private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', '', '', '', '', '', '', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
48 // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
49 private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', '�
50 ', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', '�
51 ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', '�
52 ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '�
53 �', '�
54 �', '�
55 �', '�
56 �', '�
57 �', '�
58 �', '�
59 �', '�
60 �', '�
61 �', '�
62 �', '�
63 �', '�
64 �', '�
65 �', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '�
66 ', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '�
67 ', ''];
68 private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
69 private static $transliterators = [];
70 private static $tableZero;
71 private static $tableWide;
72 public static function fromCodePoints(int ...$codes) : static
73 {
74 $string = '';
75 foreach ($codes as $code) {
76 if (0x80 > ($code %= 0x200000)) {
77 $string .= \chr($code);
78 } elseif (0x800 > $code) {
79 $string .= \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f);
80 } elseif (0x10000 > $code) {
81 $string .= \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
82 } else {
83 $string .= \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
84 }
85 }
86 return new static($string);
87 }
88 /**
89 * Generic UTF-8 to ASCII transliteration.
90 *
91 * Install the intl extension for best results.
92 *
93 * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
94 */
95 public function ascii(array $rules = []) : self
96 {
97 $str = clone $this;
98 $s = $str->string;
99 $str->string = '';
100 \array_unshift($rules, 'nfd');
101 $rules[] = 'latin-ascii';
102 if (\function_exists('transliterator_transliterate')) {
103 $rules[] = 'any-latin/bgn';
104 }
105 $rules[] = 'nfkd';
106 $rules[] = '[:nonspacing mark:] remove';
107 while (\strlen($s) - 1 > ($i = \strspn($s, self::ASCII))) {
108 if (0 < --$i) {
109 $str->string .= \substr($s, 0, $i);
110 $s = \substr($s, $i);
111 }
112 if (!($rule = \array_shift($rules))) {
113 $rules = [];
114 // An empty rule interrupts the next ones
115 }
116 if ($rule instanceof \Transliterator) {
117 $s = $rule->transliterate($s);
118 } elseif ($rule instanceof \Closure) {
119 $s = $rule($s);
120 } elseif ($rule) {
121 if ('nfd' === ($rule = \strtolower($rule))) {
122 \normalizer_is_normalized($s, self::NFD) ?: ($s = \normalizer_normalize($s, self::NFD));
123 } elseif ('nfkd' === $rule) {
124 \normalizer_is_normalized($s, self::NFKD) ?: ($s = \normalizer_normalize($s, self::NFKD));
125 } elseif ('[:nonspacing mark:] remove' === $rule) {
126 $s = \preg_replace('/\\p{Mn}++/u', '', $s);
127 } elseif ('latin-ascii' === $rule) {
128 $s = \str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
129 } elseif ('de-ascii' === $rule) {
130 $s = \preg_replace("/([AUO])̈(?=\\p{Ll})/u", '$1e', $s);
131 $s = \str_replace(["", "", "", "", "", ""], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
132 } elseif (\function_exists('transliterator_transliterate')) {
133 if (null === ($transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule)))) {
134 if ('any-latin/bgn' === $rule) {
135 $rule = 'any-latin';
136 $transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule));
137 }
138 if (null === $transliterator) {
139 throw new InvalidArgumentException(\sprintf('Unknown transliteration rule "%s".', $rule));
140 }
141 self::$transliterators['any-latin/bgn'] = $transliterator;
142 }
143 $s = $transliterator->transliterate($s);
144 }
145 } elseif (!\function_exists('iconv')) {
146 $s = \preg_replace('/[^\\x00-\\x7F]/u', '?', $s);
147 } else {
148 $s = @\preg_replace_callback('/[^\\x00-\\x7F]/u', static function ($c) {
149 $c = (string) \iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
150 if ('' === $c && '' === \iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
151 throw new \LogicException(\sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
152 }
153 return 1 < \strlen($c) ? \ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
154 }, $s);
155 }
156 }
157 $str->string .= $s;
158 return $str;
159 }
160 public function camel() : static
161 {
162 $str = clone $this;
163 $str->string = \str_replace(' ', '', \preg_replace_callback('/\\b.(?![A-Z]{2,})/u', static function ($m) use(&$i) {
164 return 1 === ++$i ? 'İ' === $m[0] ? '' : \mb_strtolower($m[0], 'UTF-8') : \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
165 }, \preg_replace('/[^\\pL0-9]++/u', ' ', $this->string)));
166 return $str;
167 }
168 /**
169 * @return int[]
170 */
171 public function codePointsAt(int $offset) : array
172 {
173 $str = $this->slice($offset, 1);
174 if ('' === $str->string) {
175 return [];
176 }
177 $codePoints = [];
178 foreach (\preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
179 $codePoints[] = \mb_ord($c, 'UTF-8');
180 }
181 return $codePoints;
182 }
183 public function folded(bool $compat = \true) : static
184 {
185 $str = clone $this;
186 if (!$compat || !\defined('IAWPSCOPED\\Normalizer::NFKC_CF')) {
187 $str->string = \normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
188 $str->string = \mb_strtolower(\str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
189 } else {
190 $str->string = \normalizer_normalize($str->string, \Normalizer::NFKC_CF);
191 }
192 return $str;
193 }
194 public function join(array $strings, string $lastGlue = null) : static
195 {
196 $str = clone $this;
197 $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue . \array_pop($strings) : '';
198 $str->string = \implode($this->string, $strings) . $tail;
199 if (!\preg_match('//u', $str->string)) {
200 throw new InvalidArgumentException('Invalid UTF-8 string.');
201 }
202 return $str;
203 }
204 public function lower() : static
205 {
206 $str = clone $this;
207 $str->string = \mb_strtolower(\str_replace('İ', '', $str->string), 'UTF-8');
208 return $str;
209 }
210 public function match(string $regexp, int $flags = 0, int $offset = 0) : array
211 {
212 $match = (\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags ? 'preg_match_all' : 'preg_match';
213 if ($this->ignoreCase) {
214 $regexp .= 'i';
215 }
216 \set_error_handler(static function ($t, $m) {
217 throw new InvalidArgumentException($m);
218 });
219 try {
220 if (\false === $match($regexp . 'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
221 $lastError = \preg_last_error();
222 foreach (\get_defined_constants(\true)['pcre'] as $k => $v) {
223 if ($lastError === $v && '_ERROR' === \substr($k, -6)) {
224 throw new RuntimeException('Matching failed with ' . $k . '.');
225 }
226 }
227 throw new RuntimeException('Matching failed with unknown error code.');
228 }
229 } finally {
230 \restore_error_handler();
231 }
232 return $matches;
233 }
234 public function normalize(int $form = self::NFC) : static
235 {
236 if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
237 throw new InvalidArgumentException('Unsupported normalization form.');
238 }
239 $str = clone $this;
240 \normalizer_is_normalized($str->string, $form) ?: ($str->string = \normalizer_normalize($str->string, $form));
241 return $str;
242 }
243 public function padBoth(int $length, string $padStr = ' ') : static
244 {
245 if ('' === $padStr || !\preg_match('//u', $padStr)) {
246 throw new InvalidArgumentException('Invalid UTF-8 string.');
247 }
248 $pad = clone $this;
249 $pad->string = $padStr;
250 return $this->pad($length, $pad, \STR_PAD_BOTH);
251 }
252 public function padEnd(int $length, string $padStr = ' ') : static
253 {
254 if ('' === $padStr || !\preg_match('//u', $padStr)) {
255 throw new InvalidArgumentException('Invalid UTF-8 string.');
256 }
257 $pad = clone $this;
258 $pad->string = $padStr;
259 return $this->pad($length, $pad, \STR_PAD_RIGHT);
260 }
261 public function padStart(int $length, string $padStr = ' ') : static
262 {
263 if ('' === $padStr || !\preg_match('//u', $padStr)) {
264 throw new InvalidArgumentException('Invalid UTF-8 string.');
265 }
266 $pad = clone $this;
267 $pad->string = $padStr;
268 return $this->pad($length, $pad, \STR_PAD_LEFT);
269 }
270 public function replaceMatches(string $fromRegexp, string|callable $to) : static
271 {
272 if ($this->ignoreCase) {
273 $fromRegexp .= 'i';
274 }
275 if (\is_array($to) || $to instanceof \Closure) {
276 $replace = 'preg_replace_callback';
277 $to = static function (array $m) use($to) : string {
278 $to = $to($m);
279 if ('' !== $to && (!\is_string($to) || !\preg_match('//u', $to))) {
280 throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
281 }
282 return $to;
283 };
284 } elseif ('' !== $to && !\preg_match('//u', $to)) {
285 throw new InvalidArgumentException('Invalid UTF-8 string.');
286 } else {
287 $replace = 'preg_replace';
288 }
289 \set_error_handler(static function ($t, $m) {
290 throw new InvalidArgumentException($m);
291 });
292 try {
293 if (null === ($string = $replace($fromRegexp . 'u', $to, $this->string))) {
294 $lastError = \preg_last_error();
295 foreach (\get_defined_constants(\true)['pcre'] as $k => $v) {
296 if ($lastError === $v && '_ERROR' === \substr($k, -6)) {
297 throw new RuntimeException('Matching failed with ' . $k . '.');
298 }
299 }
300 throw new RuntimeException('Matching failed with unknown error code.');
301 }
302 } finally {
303 \restore_error_handler();
304 }
305 $str = clone $this;
306 $str->string = $string;
307 return $str;
308 }
309 public function reverse() : static
310 {
311 $str = clone $this;
312 $str->string = \implode('', \array_reverse(\preg_split('/(\\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
313 return $str;
314 }
315 public function snake() : static
316 {
317 $str = $this->camel();
318 $str->string = \mb_strtolower(\preg_replace(['/(\\p{Lu}+)(\\p{Lu}\\p{Ll})/u', '/([\\p{Ll}0-9])(\\p{Lu})/u'], 'IAWPSCOPED\\1_\\2', $str->string), 'UTF-8');
319 return $str;
320 }
321 public function title(bool $allWords = \false) : static
322 {
323 $str = clone $this;
324 $limit = $allWords ? -1 : 1;
325 $str->string = \preg_replace_callback('/\\b./u', static function (array $m) : string {
326 return \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
327 }, $str->string, $limit);
328 return $str;
329 }
330 public function trim(string $chars = " \t\n\r\x00\v\f ") : static
331 {
332 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
333 throw new InvalidArgumentException('Invalid UTF-8 chars.');
334 }
335 $chars = \preg_quote($chars);
336 $str = clone $this;
337 $str->string = \preg_replace("{^[{$chars}]++|[{$chars}]++\$}uD", '', $str->string);
338 return $str;
339 }
340 public function trimEnd(string $chars = " \t\n\r\x00\v\f ") : static
341 {
342 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
343 throw new InvalidArgumentException('Invalid UTF-8 chars.');
344 }
345 $chars = \preg_quote($chars);
346 $str = clone $this;
347 $str->string = \preg_replace("{[{$chars}]++\$}uD", '', $str->string);
348 return $str;
349 }
350 public function trimPrefix($prefix) : static
351 {
352 if (!$this->ignoreCase) {
353 return parent::trimPrefix($prefix);
354 }
355 $str = clone $this;
356 if ($prefix instanceof \Traversable) {
357 $prefix = \iterator_to_array($prefix, \false);
358 } elseif ($prefix instanceof parent) {
359 $prefix = $prefix->string;
360 }
361 $prefix = \implode('|', \array_map('preg_quote', (array) $prefix));
362 $str->string = \preg_replace("{^(?:{$prefix})}iuD", '', $this->string);
363 return $str;
364 }
365 public function trimStart(string $chars = " \t\n\r\x00\v\f ") : static
366 {
367 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
368 throw new InvalidArgumentException('Invalid UTF-8 chars.');
369 }
370 $chars = \preg_quote($chars);
371 $str = clone $this;
372 $str->string = \preg_replace("{^[{$chars}]++}uD", '', $str->string);
373 return $str;
374 }
375 public function trimSuffix($suffix) : static
376 {
377 if (!$this->ignoreCase) {
378 return parent::trimSuffix($suffix);
379 }
380 $str = clone $this;
381 if ($suffix instanceof \Traversable) {
382 $suffix = \iterator_to_array($suffix, \false);
383 } elseif ($suffix instanceof parent) {
384 $suffix = $suffix->string;
385 }
386 $suffix = \implode('|', \array_map('preg_quote', (array) $suffix));
387 $str->string = \preg_replace("{(?:{$suffix})\$}iuD", '', $this->string);
388 return $str;
389 }
390 public function upper() : static
391 {
392 $str = clone $this;
393 $str->string = \mb_strtoupper($str->string, 'UTF-8');
394 return $str;
395 }
396 public function width(bool $ignoreAnsiDecoration = \true) : int
397 {
398 $width = 0;
399 $s = \str_replace(["\x00", "\x05", "\x07"], '', $this->string);
400 if (\false !== \strpos($s, "\r")) {
401 $s = \str_replace(["\r\n", "\r"], "\n", $s);
402 }
403 if (!$ignoreAnsiDecoration) {
404 $s = \preg_replace('/[\\p{Cc}\\x7F]++/u', '', $s);
405 }
406 foreach (\explode("\n", $s) as $s) {
407 if ($ignoreAnsiDecoration) {
408 $s = \preg_replace('/(?:\\x1B(?:
409 \\[ [\\x30-\\x3F]*+ [\\x20-\\x2F]*+ [\\x40-\\x7E]
410 | [P\\]X^_] .*? \\x1B\\\\
411 | [\\x41-\\x7E]
412 )|[\\p{Cc}\\x7F]++)/xu', '', $s);
413 }
414 $lineWidth = $this->wcswidth($s);
415 if ($lineWidth > $width) {
416 $width = $lineWidth;
417 }
418 }
419 return $width;
420 }
421 private function pad(int $len, self $pad, int $type) : static
422 {
423 $sLen = $this->length();
424 if ($len <= $sLen) {
425 return clone $this;
426 }
427 $padLen = $pad->length();
428 $freeLen = $len - $sLen;
429 $len = $freeLen % $padLen;
430 switch ($type) {
431 case \STR_PAD_RIGHT:
432 return $this->append(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
433 case \STR_PAD_LEFT:
434 return $this->prepend(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
435 case \STR_PAD_BOTH:
436 $freeLen /= 2;
437 $rightLen = \ceil($freeLen);
438 $len = $rightLen % $padLen;
439 $str = $this->append(\str_repeat($pad->string, \intdiv($rightLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
440 $leftLen = \floor($freeLen);
441 $len = $leftLen % $padLen;
442 return $str->prepend(\str_repeat($pad->string, \intdiv($leftLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
443 default:
444 throw new InvalidArgumentException('Invalid padding type.');
445 }
446 }
447 /**
448 * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
449 */
450 private function wcswidth(string $string) : int
451 {
452 $width = 0;
453 foreach (\preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
454 $codePoint = \mb_ord($c, 'UTF-8');
455 if (0 === $codePoint || 0x34f === $codePoint || 0x200b <= $codePoint && 0x200f >= $codePoint || 0x2028 === $codePoint || 0x2029 === $codePoint || 0x202a <= $codePoint && 0x202e >= $codePoint || 0x2060 <= $codePoint && 0x2063 >= $codePoint) {
456 continue;
457 }
458 // Non printable characters
459 if (32 > $codePoint || 0x7f <= $codePoint && 0xa0 > $codePoint) {
460 return -1;
461 }
462 if (null === self::$tableZero) {
463 self::$tableZero = (require __DIR__ . '/Resources/data/wcswidth_table_zero.php');
464 }
465 if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
466 $lbound = 0;
467 while ($ubound >= $lbound) {
468 $mid = \floor(($lbound + $ubound) / 2);
469 if ($codePoint > self::$tableZero[$mid][1]) {
470 $lbound = $mid + 1;
471 } elseif ($codePoint < self::$tableZero[$mid][0]) {
472 $ubound = $mid - 1;
473 } else {
474 continue 2;
475 }
476 }
477 }
478 if (null === self::$tableWide) {
479 self::$tableWide = (require __DIR__ . '/Resources/data/wcswidth_table_wide.php');
480 }
481 if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
482 $lbound = 0;
483 while ($ubound >= $lbound) {
484 $mid = \floor(($lbound + $ubound) / 2);
485 if ($codePoint > self::$tableWide[$mid][1]) {
486 $lbound = $mid + 1;
487 } elseif ($codePoint < self::$tableWide[$mid][0]) {
488 $ubound = $mid - 1;
489 } else {
490 $width += 2;
491 continue 2;
492 }
493 }
494 }
495 ++$width;
496 }
497 return $width;
498 }
499 }
500