PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / vendor / prefixed / symfony / string / AbstractUnicodeString.php
matomo / app / vendor / prefixed / symfony / string Last commit date
Exception 2 years ago Inflector 1 year ago Resources 1 year ago Slugger 1 year ago AbstractString.php 1 year ago AbstractUnicodeString.php 1 year ago ByteString.php 1 year ago CodePointString.php 1 year ago LICENSE 2 years ago LazyString.php 2 years ago README.md 2 years ago UnicodeString.php 1 year ago
AbstractUnicodeString.php
514 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 Matomo\Dependencies\Symfony\Component\String;
12
13 use Matomo\Dependencies\Symfony\Component\String\Exception\ExceptionInterface;
14 use Matomo\Dependencies\Symfony\Component\String\Exception\InvalidArgumentException;
15 use Matomo\Dependencies\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 */
27 abstract class AbstractUnicodeString extends AbstractString
28 {
29 public const NFC = \Normalizer::NFC;
30 public const NFD = \Normalizer::NFD;
31 public const NFKC = \Normalizer::NFKC;
32 public const NFKD = \Normalizer::NFKD;
33 // all ASCII letters sorted by typical frequency of occurrence
34 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";
35 // the subset of folded case mappings that is not in lower case mappings
36 private const FOLD_FROM = ['İ', 'µ', 'ſ', "�
37 ", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "ι", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', '�
38 ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', '�
39 ', '', '', '', '', '', ''];
40 private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', '�
41 ι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', '�
42 ι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', '', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', '', 'ῆι', 'ηι', '', '', '', '', '', '', '', 'ὼι', 'ωι', 'ώι', '', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
43 // the subset of upper case mappings that map one code point to many code points
44 private const UPPER_FROM = ['ß', 'ff', 'fi', 'fl', 'ffi', 'ffl', '�
45 ', '', 'և', '', '', '', '', '', 'ʼn', 'ΐ', 'ΰ', 'ǰ', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''];
46 private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ԵՒ', 'ՄՆ', 'ՄԵ', 'ՄԻ', 'ՎՆ', 'ՄԽ', 'ʼN', 'Ϊ́', 'Ϋ́', '', '', '', '', '', '', 'Υ̓', 'Υ̓̀', 'Υ̓́', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'Ϊ́', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'Ϋ́', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
47 // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
48 private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', '�
49 ', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', '�
50 ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', '�
51 ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '�
52 �', '�
53 �', '�
54 �', '�
55 �', '�
56 �', '�
57 �', '�
58 �', '�
59 �', '�
60 �', '�
61 �', '�
62 �', '�
63 �', '�
64 �', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '�
65 ', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '�
66 ', ''];
67 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', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
68 private static $transliterators = [];
69 private static $tableZero;
70 private static $tableWide;
71 /**
72 * @return static
73 */
74 public static function fromCodePoints(int ...$codes) : self
75 {
76 $string = '';
77 foreach ($codes as $code) {
78 if (0x80 > ($code %= 0x200000)) {
79 $string .= \chr($code);
80 } elseif (0x800 > $code) {
81 $string .= \chr(0xc0 | $code >> 6) . \chr(0x80 | $code & 0x3f);
82 } elseif (0x10000 > $code) {
83 $string .= \chr(0xe0 | $code >> 12) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
84 } else {
85 $string .= \chr(0xf0 | $code >> 18) . \chr(0x80 | $code >> 12 & 0x3f) . \chr(0x80 | $code >> 6 & 0x3f) . \chr(0x80 | $code & 0x3f);
86 }
87 }
88 return new static($string);
89 }
90 /**
91 * Generic UTF-8 to ASCII transliteration.
92 *
93 * Install the intl extension for best results.
94 *
95 * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
96 */
97 public function ascii(array $rules = []) : self
98 {
99 $str = clone $this;
100 $s = $str->string;
101 $str->string = '';
102 array_unshift($rules, 'nfd');
103 $rules[] = 'latin-ascii';
104 if (\function_exists('transliterator_transliterate')) {
105 $rules[] = 'any-latin/bgn';
106 }
107 $rules[] = 'nfkd';
108 $rules[] = '[:nonspacing mark:] remove';
109 while (\strlen($s) - 1 > ($i = strspn($s, self::ASCII))) {
110 if (0 < --$i) {
111 $str->string .= substr($s, 0, $i);
112 $s = substr($s, $i);
113 }
114 if (!($rule = array_shift($rules))) {
115 $rules = [];
116 // An empty rule interrupts the next ones
117 }
118 if ($rule instanceof \Transliterator) {
119 $s = $rule->transliterate($s);
120 } elseif ($rule instanceof \Closure) {
121 $s = $rule($s);
122 } elseif ($rule) {
123 if ('nfd' === ($rule = strtolower($rule))) {
124 normalizer_is_normalized($s, self::NFD) ?: ($s = normalizer_normalize($s, self::NFD));
125 } elseif ('nfkd' === $rule) {
126 normalizer_is_normalized($s, self::NFKD) ?: ($s = normalizer_normalize($s, self::NFKD));
127 } elseif ('[:nonspacing mark:] remove' === $rule) {
128 $s = preg_replace('/\\p{Mn}++/u', '', $s);
129 } elseif ('latin-ascii' === $rule) {
130 $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
131 } elseif ('de-ascii' === $rule) {
132 $s = preg_replace("/([AUO])̈(?=\\p{Ll})/u", '$1e', $s);
133 $s = str_replace(["", "", "", "", "", ""], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
134 } elseif (\function_exists('transliterator_transliterate')) {
135 if (null === ($transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule)))) {
136 if ('any-latin/bgn' === $rule) {
137 $rule = 'any-latin';
138 $transliterator = self::$transliterators[$rule] ?? (self::$transliterators[$rule] = \Transliterator::create($rule));
139 }
140 if (null === $transliterator) {
141 throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
142 }
143 self::$transliterators['any-latin/bgn'] = $transliterator;
144 }
145 $s = $transliterator->transliterate($s);
146 }
147 } elseif (!\function_exists('iconv')) {
148 $s = preg_replace('/[^\\x00-\\x7F]/u', '?', $s);
149 } else {
150 $s = @preg_replace_callback('/[^\\x00-\\x7F]/u', static function ($c) {
151 $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
152 if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
153 throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
154 }
155 return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
156 }, $s);
157 }
158 }
159 $str->string .= $s;
160 return $str;
161 }
162 public function camel() : parent
163 {
164 $str = clone $this;
165 $str->string = str_replace(' ', '', preg_replace_callback('/\\b.(?!\\p{Lu})/u', static function ($m) use(&$i) {
166 return 1 === ++$i ? 'İ' === $m[0] ? '' : mb_strtolower($m[0], 'UTF-8') : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
167 }, preg_replace('/[^\\pL0-9]++/u', ' ', $this->string)));
168 return $str;
169 }
170 /**
171 * @return int[]
172 */
173 public function codePointsAt(int $offset) : array
174 {
175 $str = $this->slice($offset, 1);
176 if ('' === $str->string) {
177 return [];
178 }
179 $codePoints = [];
180 foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
181 $codePoints[] = mb_ord($c, 'UTF-8');
182 }
183 return $codePoints;
184 }
185 public function folded(bool $compat = \true) : parent
186 {
187 $str = clone $this;
188 if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) {
189 $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
190 $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $str->string), 'UTF-8');
191 } else {
192 $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
193 }
194 return $str;
195 }
196 public function join(array $strings, ?string $lastGlue = null) : parent
197 {
198 $str = clone $this;
199 $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue . array_pop($strings) : '';
200 $str->string = implode($this->string, $strings) . $tail;
201 if (!preg_match('//u', $str->string)) {
202 throw new InvalidArgumentException('Invalid UTF-8 string.');
203 }
204 return $str;
205 }
206 public function lower() : parent
207 {
208 $str = clone $this;
209 $str->string = mb_strtolower(str_replace('İ', '', $str->string), 'UTF-8');
210 return $str;
211 }
212 public function match(string $regexp, int $flags = 0, int $offset = 0) : array
213 {
214 $match = (\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags ? 'preg_match_all' : 'preg_match';
215 if ($this->ignoreCase) {
216 $regexp .= 'i';
217 }
218 set_error_handler(static function ($t, $m) {
219 throw new InvalidArgumentException($m);
220 });
221 try {
222 if (\false === $match($regexp . 'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
223 $lastError = preg_last_error();
224 foreach (get_defined_constants(\true)['pcre'] as $k => $v) {
225 if ($lastError === $v && '_ERROR' === substr($k, -6)) {
226 throw new RuntimeException('Matching failed with ' . $k . '.');
227 }
228 }
229 throw new RuntimeException('Matching failed with unknown error code.');
230 }
231 } finally {
232 restore_error_handler();
233 }
234 return $matches;
235 }
236 /**
237 * @return static
238 */
239 public function normalize(int $form = self::NFC) : self
240 {
241 if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
242 throw new InvalidArgumentException('Unsupported normalization form.');
243 }
244 $str = clone $this;
245 normalizer_is_normalized($str->string, $form) ?: ($str->string = normalizer_normalize($str->string, $form));
246 return $str;
247 }
248 public function padBoth(int $length, string $padStr = ' ') : parent
249 {
250 if ('' === $padStr || !preg_match('//u', $padStr)) {
251 throw new InvalidArgumentException('Invalid UTF-8 string.');
252 }
253 $pad = clone $this;
254 $pad->string = $padStr;
255 return $this->pad($length, $pad, \STR_PAD_BOTH);
256 }
257 public function padEnd(int $length, string $padStr = ' ') : parent
258 {
259 if ('' === $padStr || !preg_match('//u', $padStr)) {
260 throw new InvalidArgumentException('Invalid UTF-8 string.');
261 }
262 $pad = clone $this;
263 $pad->string = $padStr;
264 return $this->pad($length, $pad, \STR_PAD_RIGHT);
265 }
266 public function padStart(int $length, string $padStr = ' ') : parent
267 {
268 if ('' === $padStr || !preg_match('//u', $padStr)) {
269 throw new InvalidArgumentException('Invalid UTF-8 string.');
270 }
271 $pad = clone $this;
272 $pad->string = $padStr;
273 return $this->pad($length, $pad, \STR_PAD_LEFT);
274 }
275 public function replaceMatches(string $fromRegexp, $to) : parent
276 {
277 if ($this->ignoreCase) {
278 $fromRegexp .= 'i';
279 }
280 if (\is_array($to) || $to instanceof \Closure) {
281 if (!\is_callable($to)) {
282 throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
283 }
284 $replace = 'preg_replace_callback';
285 $to = static function (array $m) use($to) : string {
286 $to = $to($m);
287 if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
288 throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
289 }
290 return $to;
291 };
292 } elseif ('' !== $to && !preg_match('//u', $to)) {
293 throw new InvalidArgumentException('Invalid UTF-8 string.');
294 } else {
295 $replace = 'preg_replace';
296 }
297 set_error_handler(static function ($t, $m) {
298 throw new InvalidArgumentException($m);
299 });
300 try {
301 if (null === ($string = $replace($fromRegexp . 'u', $to, $this->string))) {
302 $lastError = preg_last_error();
303 foreach (get_defined_constants(\true)['pcre'] as $k => $v) {
304 if ($lastError === $v && '_ERROR' === substr($k, -6)) {
305 throw new RuntimeException('Matching failed with ' . $k . '.');
306 }
307 }
308 throw new RuntimeException('Matching failed with unknown error code.');
309 }
310 } finally {
311 restore_error_handler();
312 }
313 $str = clone $this;
314 $str->string = $string;
315 return $str;
316 }
317 public function reverse() : parent
318 {
319 $str = clone $this;
320 $str->string = implode('', array_reverse(preg_split('/(\\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
321 return $str;
322 }
323 public function snake() : parent
324 {
325 $str = $this->camel();
326 $str->string = mb_strtolower(preg_replace(['/(\\p{Lu}+)(\\p{Lu}\\p{Ll})/u', '/([\\p{Ll}0-9])(\\p{Lu})/u'], '\\1_\\2', $str->string), 'UTF-8');
327 return $str;
328 }
329 public function title(bool $allWords = \false) : parent
330 {
331 $str = clone $this;
332 $limit = $allWords ? -1 : 1;
333 $str->string = preg_replace_callback('/\\b./u', static function (array $m) : string {
334 return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
335 }, $str->string, $limit);
336 return $str;
337 }
338 public function trim(string $chars = " \t\n\r\x00\v\f\xC2\xA0") : parent
339 {
340 if (" \t\n\r\x00\v\f\xC2\xA0" !== $chars && !preg_match('//u', $chars)) {
341 throw new InvalidArgumentException('Invalid UTF-8 chars.');
342 }
343 $chars = preg_quote($chars);
344 $str = clone $this;
345 $str->string = preg_replace("{^[{$chars}]++|[{$chars}]++\$}uD", '', $str->string);
346 return $str;
347 }
348 public function trimEnd(string $chars = " \t\n\r\x00\v\f\xC2\xA0") : parent
349 {
350 if (" \t\n\r\x00\v\f\xC2\xA0" !== $chars && !preg_match('//u', $chars)) {
351 throw new InvalidArgumentException('Invalid UTF-8 chars.');
352 }
353 $chars = preg_quote($chars);
354 $str = clone $this;
355 $str->string = preg_replace("{[{$chars}]++\$}uD", '', $str->string);
356 return $str;
357 }
358 public function trimPrefix($prefix) : parent
359 {
360 if (!$this->ignoreCase) {
361 return parent::trimPrefix($prefix);
362 }
363 $str = clone $this;
364 if ($prefix instanceof \Traversable) {
365 $prefix = iterator_to_array($prefix, \false);
366 } elseif ($prefix instanceof parent) {
367 $prefix = $prefix->string;
368 }
369 $prefix = implode('|', array_map('preg_quote', (array) $prefix));
370 $str->string = preg_replace("{^(?:{$prefix})}iuD", '', $this->string);
371 return $str;
372 }
373 public function trimStart(string $chars = " \t\n\r\x00\v\f\xC2\xA0") : parent
374 {
375 if (" \t\n\r\x00\v\f\xC2\xA0" !== $chars && !preg_match('//u', $chars)) {
376 throw new InvalidArgumentException('Invalid UTF-8 chars.');
377 }
378 $chars = preg_quote($chars);
379 $str = clone $this;
380 $str->string = preg_replace("{^[{$chars}]++}uD", '', $str->string);
381 return $str;
382 }
383 public function trimSuffix($suffix) : parent
384 {
385 if (!$this->ignoreCase) {
386 return parent::trimSuffix($suffix);
387 }
388 $str = clone $this;
389 if ($suffix instanceof \Traversable) {
390 $suffix = iterator_to_array($suffix, \false);
391 } elseif ($suffix instanceof parent) {
392 $suffix = $suffix->string;
393 }
394 $suffix = implode('|', array_map('preg_quote', (array) $suffix));
395 $str->string = preg_replace("{(?:{$suffix})\$}iuD", '', $this->string);
396 return $str;
397 }
398 public function upper() : parent
399 {
400 $str = clone $this;
401 $str->string = mb_strtoupper($str->string, 'UTF-8');
402 if (\PHP_VERSION_ID < 70300) {
403 $str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
404 }
405 return $str;
406 }
407 public function width(bool $ignoreAnsiDecoration = \true) : int
408 {
409 $width = 0;
410 $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
411 if (\false !== strpos($s, "\r")) {
412 $s = str_replace(["\r\n", "\r"], "\n", $s);
413 }
414 if (!$ignoreAnsiDecoration) {
415 $s = preg_replace('/[\\p{Cc}\\x7F]++/u', '', $s);
416 }
417 foreach (explode("\n", $s) as $s) {
418 if ($ignoreAnsiDecoration) {
419 $s = preg_replace('/(?:\\x1B(?:
420 \\[ [\\x30-\\x3F]*+ [\\x20-\\x2F]*+ [\\x40-\\x7E]
421 | [P\\]X^_] .*? \\x1B\\\\
422 | [\\x41-\\x7E]
423 )|[\\p{Cc}\\x7F]++)/xu', '', $s);
424 }
425 $lineWidth = $this->wcswidth($s);
426 if ($lineWidth > $width) {
427 $width = $lineWidth;
428 }
429 }
430 return $width;
431 }
432 /**
433 * @return static
434 */
435 private function pad(int $len, self $pad, int $type) : parent
436 {
437 $sLen = $this->length();
438 if ($len <= $sLen) {
439 return clone $this;
440 }
441 $padLen = $pad->length();
442 $freeLen = $len - $sLen;
443 $len = $freeLen % $padLen;
444 switch ($type) {
445 case \STR_PAD_RIGHT:
446 return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
447 case \STR_PAD_LEFT:
448 return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
449 case \STR_PAD_BOTH:
450 $freeLen /= 2;
451 $rightLen = ceil($freeLen);
452 $len = $rightLen % $padLen;
453 $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
454 $leftLen = floor($freeLen);
455 $len = $leftLen % $padLen;
456 return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)) . ($len ? $pad->slice(0, $len) : ''));
457 default:
458 throw new InvalidArgumentException('Invalid padding type.');
459 }
460 }
461 /**
462 * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
463 */
464 private function wcswidth(string $string) : int
465 {
466 $width = 0;
467 foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
468 $codePoint = mb_ord($c, 'UTF-8');
469 if (0 === $codePoint || 0x34f === $codePoint || 0x200b <= $codePoint && 0x200f >= $codePoint || 0x2028 === $codePoint || 0x2029 === $codePoint || 0x202a <= $codePoint && 0x202e >= $codePoint || 0x2060 <= $codePoint && 0x2063 >= $codePoint) {
470 continue;
471 }
472 // Non printable characters
473 if (32 > $codePoint || 0x7f <= $codePoint && 0xa0 > $codePoint) {
474 return -1;
475 }
476 if (null === self::$tableZero) {
477 self::$tableZero = (require __DIR__ . '/Resources/data/wcswidth_table_zero.php');
478 }
479 if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
480 $lbound = 0;
481 while ($ubound >= $lbound) {
482 $mid = floor(($lbound + $ubound) / 2);
483 if ($codePoint > self::$tableZero[$mid][1]) {
484 $lbound = $mid + 1;
485 } elseif ($codePoint < self::$tableZero[$mid][0]) {
486 $ubound = $mid - 1;
487 } else {
488 continue 2;
489 }
490 }
491 }
492 if (null === self::$tableWide) {
493 self::$tableWide = (require __DIR__ . '/Resources/data/wcswidth_table_wide.php');
494 }
495 if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
496 $lbound = 0;
497 while ($ubound >= $lbound) {
498 $mid = floor(($lbound + $ubound) / 2);
499 if ($codePoint > self::$tableWide[$mid][1]) {
500 $lbound = $mid + 1;
501 } elseif ($codePoint < self::$tableWide[$mid][0]) {
502 $ubound = $mid - 1;
503 } else {
504 $width += 2;
505 continue 2;
506 }
507 }
508 }
509 ++$width;
510 }
511 return $width;
512 }
513 }
514