PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.2.0
Independent Analytics – WordPress Analytics Plugin v2.2.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 2 years ago Resources 2 years ago Slugger 2 years ago AbstractString.php 2 years ago AbstractUnicodeString.php 2 years ago ByteString.php 2 years ago CodePointString.php 2 years ago LazyString.php 2 years ago UnicodeString.php 2 years ago
AbstractUnicodeString.php
515 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 IAWP_SCOPED\Symfony\Component\String;
12
13 use IAWP_SCOPED\Symfony\Component\String\Exception\ExceptionInterface;
14 use IAWP_SCOPED\Symfony\Component\String\Exception\InvalidArgumentException;
15 use IAWP_SCOPED\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', 'ʼ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 /**
73 * @return static
74 */
75 public static function fromCodePoints(int ...$codes) : self
76 {
77 $string = '';
78 foreach ($codes as $code) {
79 if (0x80 > ($code %= 0x200000)) {
80 $string .= \chr($code);
81 } elseif (0x800 > $code) {
82 $string .= \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f);
83 } elseif (0x10000 > $code) {
84 $string .= \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
85 } else {
86 $string .= \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
87 }
88 }
89 return new static($string);
90 }
91 /**
92 * Generic UTF-8 to ASCII transliteration.
93 *
94 * Install the intl extension for best results.
95 *
96 * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
97 */
98 public function ascii(array $rules = []) : self
99 {
100 $str = clone $this;
101 $s = $str->string;
102 $str->string = '';
103 \array_unshift($rules, 'nfd');
104 $rules[] = 'latin-ascii';
105 if (\function_exists('transliterator_transliterate')) {
106 $rules[] = 'any-latin/bgn';
107 }
108 $rules[] = 'nfkd';
109 $rules[] = '[:nonspacing mark:] remove';
110 while (\strlen($s) - 1 > ($i = \strspn($s, self::ASCII))) {
111 if (0 < --$i) {
112 $str->string .= \substr($s, 0, $i);
113 $s = \substr($s, $i);
114 }
115 if (!($rule = \array_shift($rules))) {
116 $rules = [];
117 // An empty rule interrupts the next ones
118 }
119 if ($rule instanceof \Transliterator) {
120 $s = $rule->transliterate($s);
121 } elseif ($rule instanceof \Closure) {
122 $s = $rule($s);
123 } elseif ($rule) {
124 if ('nfd' === ($rule = \strtolower($rule))) {
125 \normalizer_is_normalized($s, self::NFD) ?: ($s = \normalizer_normalize($s, self::NFD));
126 } elseif ('nfkd' === $rule) {
127 \normalizer_is_normalized($s, self::NFKD) ?: ($s = \normalizer_normalize($s, self::NFKD));
128 } elseif ('[:nonspacing mark:] remove' === $rule) {
129 $s = \preg_replace('/\\p{Mn}++/u', '', $s);
130 } elseif ('latin-ascii' === $rule) {
131 $s = \str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
132 } elseif ('de-ascii' === $rule) {
133 $s = \preg_replace("/([AUO])̈(?=\\p{Ll})/u", '$1e', $s);
134 $s = \str_replace(["", "", "", "", "", ""], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
135 } elseif (\function_exists('transliterator_transliterate')) {
136 if (null === ($transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule)))) {
137 if ('any-latin/bgn' === $rule) {
138 $rule = 'any-latin';
139 $transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule));
140 }
141 if (null === $transliterator) {
142 throw new InvalidArgumentException(\sprintf('Unknown transliteration rule "%s".', $rule));
143 }
144 self::$transliterators['any-latin/bgn'] = $transliterator;
145 }
146 $s = $transliterator->transliterate($s);
147 }
148 } elseif (!\function_exists('iconv')) {
149 $s = \preg_replace('/[^\\x00-\\x7F]/u', '?', $s);
150 } else {
151 $s = @\preg_replace_callback('/[^\\x00-\\x7F]/u', static function ($c) {
152 $c = (string) \iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
153 if ('' === $c && '' === \iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
154 throw new \LogicException(\sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
155 }
156 return 1 < \strlen($c) ? \ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
157 }, $s);
158 }
159 }
160 $str->string .= $s;
161 return $str;
162 }
163 public function camel() : parent
164 {
165 $str = clone $this;
166 $str->string = \str_replace(' ', '', \preg_replace_callback('/\\b.(?![A-Z]{2,})/u', static function ($m) use(&$i) {
167 return 1 === ++$i ? 'İ' === $m[0] ? '' : \mb_strtolower($m[0], 'UTF-8') : \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
168 }, \preg_replace('/[^\\pL0-9]++/u', ' ', $this->string)));
169 return $str;
170 }
171 /**
172 * @return int[]
173 */
174 public function codePointsAt(int $offset) : array
175 {
176 $str = $this->slice($offset, 1);
177 if ('' === $str->string) {
178 return [];
179 }
180 $codePoints = [];
181 foreach (\preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
182 $codePoints[] = \mb_ord($c, 'UTF-8');
183 }
184 return $codePoints;
185 }
186 public function folded(bool $compat = \true) : parent
187 {
188 $str = clone $this;
189 if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('IAWP_SCOPED\\Normalizer::NFKC_CF')) {
190 $str->string = \normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
191 $str->string = \mb_strtolower(\str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
192 } else {
193 $str->string = \normalizer_normalize($str->string, \Normalizer::NFKC_CF);
194 }
195 return $str;
196 }
197 public function join(array $strings, string $lastGlue = null) : parent
198 {
199 $str = clone $this;
200 $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue . \array_pop($strings) : '';
201 $str->string = \implode($this->string, $strings) . $tail;
202 if (!\preg_match('//u', $str->string)) {
203 throw new InvalidArgumentException('Invalid UTF-8 string.');
204 }
205 return $str;
206 }
207 public function lower() : parent
208 {
209 $str = clone $this;
210 $str->string = \mb_strtolower(\str_replace('İ', '', $str->string), 'UTF-8');
211 return $str;
212 }
213 public function match(string $regexp, int $flags = 0, int $offset = 0) : array
214 {
215 $match = (\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags ? 'preg_match_all' : 'preg_match';
216 if ($this->ignoreCase) {
217 $regexp .= 'i';
218 }
219 \set_error_handler(static function ($t, $m) {
220 throw new InvalidArgumentException($m);
221 });
222 try {
223 if (\false === $match($regexp . 'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
224 $lastError = \preg_last_error();
225 foreach (\get_defined_constants(\true)['pcre'] as $k => $v) {
226 if ($lastError === $v && '_ERROR' === \substr($k, -6)) {
227 throw new RuntimeException('Matching failed with ' . $k . '.');
228 }
229 }
230 throw new RuntimeException('Matching failed with unknown error code.');
231 }
232 } finally {
233 \restore_error_handler();
234 }
235 return $matches;
236 }
237 /**
238 * @return static
239 */
240 public function normalize(int $form = self::NFC) : self
241 {
242 if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
243 throw new InvalidArgumentException('Unsupported normalization form.');
244 }
245 $str = clone $this;
246 \normalizer_is_normalized($str->string, $form) ?: ($str->string = \normalizer_normalize($str->string, $form));
247 return $str;
248 }
249 public function padBoth(int $length, string $padStr = ' ') : parent
250 {
251 if ('' === $padStr || !\preg_match('//u', $padStr)) {
252 throw new InvalidArgumentException('Invalid UTF-8 string.');
253 }
254 $pad = clone $this;
255 $pad->string = $padStr;
256 return $this->pad($length, $pad, \STR_PAD_BOTH);
257 }
258 public function padEnd(int $length, string $padStr = ' ') : parent
259 {
260 if ('' === $padStr || !\preg_match('//u', $padStr)) {
261 throw new InvalidArgumentException('Invalid UTF-8 string.');
262 }
263 $pad = clone $this;
264 $pad->string = $padStr;
265 return $this->pad($length, $pad, \STR_PAD_RIGHT);
266 }
267 public function padStart(int $length, string $padStr = ' ') : parent
268 {
269 if ('' === $padStr || !\preg_match('//u', $padStr)) {
270 throw new InvalidArgumentException('Invalid UTF-8 string.');
271 }
272 $pad = clone $this;
273 $pad->string = $padStr;
274 return $this->pad($length, $pad, \STR_PAD_LEFT);
275 }
276 public function replaceMatches(string $fromRegexp, $to) : parent
277 {
278 if ($this->ignoreCase) {
279 $fromRegexp .= 'i';
280 }
281 if (\is_array($to) || $to instanceof \Closure) {
282 if (!\is_callable($to)) {
283 throw new \TypeError(\sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
284 }
285 $replace = 'preg_replace_callback';
286 $to = static function (array $m) use($to) : string {
287 $to = $to($m);
288 if ('' !== $to && (!\is_string($to) || !\preg_match('//u', $to))) {
289 throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
290 }
291 return $to;
292 };
293 } elseif ('' !== $to && !\preg_match('//u', $to)) {
294 throw new InvalidArgumentException('Invalid UTF-8 string.');
295 } else {
296 $replace = 'preg_replace';
297 }
298 \set_error_handler(static function ($t, $m) {
299 throw new InvalidArgumentException($m);
300 });
301 try {
302 if (null === ($string = $replace($fromRegexp . 'u', $to, $this->string))) {
303 $lastError = \preg_last_error();
304 foreach (\get_defined_constants(\true)['pcre'] as $k => $v) {
305 if ($lastError === $v && '_ERROR' === \substr($k, -6)) {
306 throw new RuntimeException('Matching failed with ' . $k . '.');
307 }
308 }
309 throw new RuntimeException('Matching failed with unknown error code.');
310 }
311 } finally {
312 \restore_error_handler();
313 }
314 $str = clone $this;
315 $str->string = $string;
316 return $str;
317 }
318 public function reverse() : parent
319 {
320 $str = clone $this;
321 $str->string = \implode('', \array_reverse(\preg_split('/(\\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
322 return $str;
323 }
324 public function snake() : parent
325 {
326 $str = $this->camel();
327 $str->string = \mb_strtolower(\preg_replace(['/(\\p{Lu}+)(\\p{Lu}\\p{Ll})/u', '/([\\p{Ll}0-9])(\\p{Lu})/u'], 'IAWP_SCOPED\\1_\\2', $str->string), 'UTF-8');
328 return $str;
329 }
330 public function title(bool $allWords = \false) : parent
331 {
332 $str = clone $this;
333 $limit = $allWords ? -1 : 1;
334 $str->string = \preg_replace_callback('/\\b./u', static function (array $m) : string {
335 return \mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
336 }, $str->string, $limit);
337 return $str;
338 }
339 public function trim(string $chars = " \t\n\r\x00\v\f ") : parent
340 {
341 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
342 throw new InvalidArgumentException('Invalid UTF-8 chars.');
343 }
344 $chars = \preg_quote($chars);
345 $str = clone $this;
346 $str->string = \preg_replace("{^[{$chars}]++|[{$chars}]++\$}uD", '', $str->string);
347 return $str;
348 }
349 public function trimEnd(string $chars = " \t\n\r\x00\v\f ") : parent
350 {
351 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
352 throw new InvalidArgumentException('Invalid UTF-8 chars.');
353 }
354 $chars = \preg_quote($chars);
355 $str = clone $this;
356 $str->string = \preg_replace("{[{$chars}]++\$}uD", '', $str->string);
357 return $str;
358 }
359 public function trimPrefix($prefix) : parent
360 {
361 if (!$this->ignoreCase) {
362 return parent::trimPrefix($prefix);
363 }
364 $str = clone $this;
365 if ($prefix instanceof \Traversable) {
366 $prefix = \iterator_to_array($prefix, \false);
367 } elseif ($prefix instanceof parent) {
368 $prefix = $prefix->string;
369 }
370 $prefix = \implode('|', \array_map('preg_quote', (array) $prefix));
371 $str->string = \preg_replace("{^(?:{$prefix})}iuD", '', $this->string);
372 return $str;
373 }
374 public function trimStart(string $chars = " \t\n\r\x00\v\f ") : parent
375 {
376 if (" \t\n\r\x00\v\f " !== $chars && !\preg_match('//u', $chars)) {
377 throw new InvalidArgumentException('Invalid UTF-8 chars.');
378 }
379 $chars = \preg_quote($chars);
380 $str = clone $this;
381 $str->string = \preg_replace("{^[{$chars}]++}uD", '', $str->string);
382 return $str;
383 }
384 public function trimSuffix($suffix) : parent
385 {
386 if (!$this->ignoreCase) {
387 return parent::trimSuffix($suffix);
388 }
389 $str = clone $this;
390 if ($suffix instanceof \Traversable) {
391 $suffix = \iterator_to_array($suffix, \false);
392 } elseif ($suffix instanceof parent) {
393 $suffix = $suffix->string;
394 }
395 $suffix = \implode('|', \array_map('preg_quote', (array) $suffix));
396 $str->string = \preg_replace("{(?:{$suffix})\$}iuD", '', $this->string);
397 return $str;
398 }
399 public function upper() : parent
400 {
401 $str = clone $this;
402 $str->string = \mb_strtoupper($str->string, 'UTF-8');
403 if (\PHP_VERSION_ID < 70300) {
404 $str->string = \str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
405 }
406 return $str;
407 }
408 public function width(bool $ignoreAnsiDecoration = \true) : int
409 {
410 $width = 0;
411 $s = \str_replace(["\x00", "\x05", "\x07"], '', $this->string);
412 if (\false !== \strpos($s, "\r")) {
413 $s = \str_replace(["\r\n", "\r"], "\n", $s);
414 }
415 if (!$ignoreAnsiDecoration) {
416 $s = \preg_replace('/[\\p{Cc}\\x7F]++/u', '', $s);
417 }
418 foreach (\explode("\n", $s) as $s) {
419 if ($ignoreAnsiDecoration) {
420 $s = \preg_replace('/(?:\\x1B(?:
421 \\[ [\\x30-\\x3F]*+ [\\x20-\\x2F]*+ [\\x40-\\x7E]
422 | [P\\]X^_] .*? \\x1B\\\\
423 | [\\x41-\\x7E]
424 )|[\\p{Cc}\\x7F]++)/xu', '', $s);
425 }
426 $lineWidth = $this->wcswidth($s);
427 if ($lineWidth > $width) {
428 $width = $lineWidth;
429 }
430 }
431 return $width;
432 }
433 /**
434 * @return static
435 */
436 private function pad(int $len, self $pad, int $type) : parent
437 {
438 $sLen = $this->length();
439 if ($len <= $sLen) {
440 return clone $this;
441 }
442 $padLen = $pad->length();
443 $freeLen = $len - $sLen;
444 $len = $freeLen % $padLen;
445 switch ($type) {
446 case \STR_PAD_RIGHT:
447 return $this->append(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
448 case \STR_PAD_LEFT:
449 return $this->prepend(\str_repeat($pad->string, \intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
450 case \STR_PAD_BOTH:
451 $freeLen /= 2;
452 $rightLen = \ceil($freeLen);
453 $len = $rightLen % $padLen;
454 $str = $this->append(\str_repeat($pad->string, \intdiv($rightLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
455 $leftLen = \floor($freeLen);
456 $len = $leftLen % $padLen;
457 return $str->prepend(\str_repeat($pad->string, \intdiv($leftLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
458 default:
459 throw new InvalidArgumentException('Invalid padding type.');
460 }
461 }
462 /**
463 * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
464 */
465 private function wcswidth(string $string) : int
466 {
467 $width = 0;
468 foreach (\preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
469 $codePoint = \mb_ord($c, 'UTF-8');
470 if (0 === $codePoint || 0x34f === $codePoint || 0x200b <= $codePoint && 0x200f >= $codePoint || 0x2028 === $codePoint || 0x2029 === $codePoint || 0x202a <= $codePoint && 0x202e >= $codePoint || 0x2060 <= $codePoint && 0x2063 >= $codePoint) {
471 continue;
472 }
473 // Non printable characters
474 if (32 > $codePoint || 0x7f <= $codePoint && 0xa0 > $codePoint) {
475 return -1;
476 }
477 if (null === self::$tableZero) {
478 self::$tableZero = (require __DIR__ . '/Resources/data/wcswidth_table_zero.php');
479 }
480 if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
481 $lbound = 0;
482 while ($ubound >= $lbound) {
483 $mid = \floor(($lbound + $ubound) / 2);
484 if ($codePoint > self::$tableZero[$mid][1]) {
485 $lbound = $mid + 1;
486 } elseif ($codePoint < self::$tableZero[$mid][0]) {
487 $ubound = $mid - 1;
488 } else {
489 continue 2;
490 }
491 }
492 }
493 if (null === self::$tableWide) {
494 self::$tableWide = (require __DIR__ . '/Resources/data/wcswidth_table_wide.php');
495 }
496 if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
497 $lbound = 0;
498 while ($ubound >= $lbound) {
499 $mid = \floor(($lbound + $ubound) / 2);
500 if ($codePoint > self::$tableWide[$mid][1]) {
501 $lbound = $mid + 1;
502 } elseif ($codePoint < self::$tableWide[$mid][0]) {
503 $ubound = $mid - 1;
504 } else {
505 $width += 2;
506 continue 2;
507 }
508 }
509 }
510 ++$width;
511 }
512 return $width;
513 }
514 }
515